    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io.monitor;
    018
    019import java.util.List;
    020import java.util.concurrent.CopyOnWriteArrayList;
    021import java.util.concurrent.ThreadFactory;
    022
    023/**
    024 * A runnable that spawns a monitoring thread triggering any
    025 * registered {@link FileAlterationObserver} at a specified interval.
    026 *
    027 * @see FileAlterationObserver
    028 * @since 2.0
    029 */
    030public final class FileAlterationMonitor implements Runnable {
    031
    032    private final long interval;
    033    private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<>();
    034    private Thread thread = null;
    035    private ThreadFactory threadFactory;
    036    private volatile boolean running = false;
    037
    038    /**
    039     * Constructs a monitor with a default interval of 10 seconds.
    040     */
    041    public FileAlterationMonitor() {
    042        this(10000);
    043    }
    044
    045    /**
    046     * Constructs a monitor with the specified interval.
    047     *
    048     * @param interval The amount of time in milliseconds to wait between
    049     * checks of the file system
    050     */
    051    public FileAlterationMonitor(final long interval) {
    052        this.interval = interval;
    053    }
    054
    055    /**
    056     * Constructs a monitor with the specified interval and set of observers.
    057     *
    058     * @param interval The amount of time in milliseconds to wait between
    059     * checks of the file system
    060     * @param observers The set of observers to add to the monitor.
    061     */
    062    public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) {
    063        this(interval);
    064        if (observers != null) {
    065            for (final FileAlterationObserver observer : observers) {
    066                addObserver(observer);
    067            }
    068        }
    069    }
    070
    071    /**
    072     * Returns the interval.
    073     *
    074     * @return the interval
    075     */
    076    public long getInterval() {
    077        return interval;
    078    }
    079
    080    /**
    081     * Sets the thread factory.
    082     *
    083     * @param threadFactory the thread factory
    084     */
    085    public synchronized void setThreadFactory(final ThreadFactory threadFactory) {
    086        this.threadFactory = threadFactory;
    087    }
    088
    089    /**
    090     * Adds a file system observer to this monitor.
    091     *
    092     * @param observer The file system observer to add
    093     */
    094    public void addObserver(final FileAlterationObserver observer) {
    095        if (observer != null) {
    096            observers.add(observer);
    097        }
    098    }
    099
    100    /**
    101     * Removes a file system observer from this monitor.
    102     *
    103     * @param observer The file system observer to remove
    104     */
    105    public void removeObserver(final FileAlterationObserver observer) {
    106        if (observer != null) {
    107            while (observers.remove(observer)) {
    108                // empty
    109            }
    110        }
    111    }
    112
    113    /**
    114     * Returns the set of {@link FileAlterationObserver} registered with
    115     * this monitor.
    116     *
    117     * @return The set of {@link FileAlterationObserver}
    118     */
    119    public Iterable<FileAlterationObserver> getObservers() {
    120        return observers;
    121    }
    122
    123    /**
    124     * Starts monitoring.
    125     *
    126     * @throws Exception if an error occurs initializing the observer
    127     */
    128    public synchronized void start() throws Exception {
    129        if (running) {
    130            throw new IllegalStateException("Monitor is already running");
    131        }
    132        for (final FileAlterationObserver observer : observers) {
    133            observer.initialize();
    134        }
    135        running = true;
    136        if (threadFactory != null) {
    137            thread = threadFactory.newThread(this);
    138        } else {
    139            thread = new Thread(this);
    140        }
    141        thread.start();
    142    }
    143
    144    /**
    145     * Stops monitoring.
    146     *
    147     * @throws Exception if an error occurs initializing the observer
    148     */
    149    public synchronized void stop() throws Exception {
    150        stop(interval);
    151    }
    152
    153    /**
    154     * Stops monitoring.
    155     *
    156     * @param stopInterval the amount of time in milliseconds to wait for the thread to finish.
    157     * A value of zero will wait until the thread is finished (see {@link Thread#join(long)}).
    158     * @throws Exception if an error occurs initializing the observer
    159     * @since 2.1
    160     */
    161    public synchronized void stop(final long stopInterval) throws Exception {
    162        if (running == false) {
    163            throw new IllegalStateException("Monitor is not running");
    164        }
    165        running = false;
    166        try {
    167            thread.interrupt();
    168            thread.join(stopInterval);
    169        } catch (final InterruptedException e) {
    170            Thread.currentThread().interrupt();
    171        }
    172        for (final FileAlterationObserver observer : observers) {
    173            observer.destroy();
    174        }
    175    }
    176
    177    /**
    178     * Runs this monitor.
    179     */
    180    @Override
    181    public void run() {
    182        while (running) {
    183            for (final FileAlterationObserver observer : observers) {
    184                observer.checkAndNotify();
    185            }
    186            if (!running) {
    187                break;
    188            }
    189            try {
    190                Thread.sleep(interval);
    191            } catch (final InterruptedException ignored) {
    192                // ignore
    193            }
    194        }
    195    }
    196}