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 * @version $Id: FileAlterationMonitor.java 1415850 2012-11-30 20:51:39Z ggregory $
029 * @since 2.0
030 */
031public final class FileAlterationMonitor implements Runnable {
032
033    private final long interval;
034    private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<FileAlterationObserver>();
035    private Thread thread = null;
036    private ThreadFactory threadFactory;
037    private volatile boolean running = false;
038
039    /**
040     * Construct a monitor with a default interval of 10 seconds.
041     */
042    public FileAlterationMonitor() {
043        this(10000);
044    }
045
046    /**
047     * Construct a monitor with the specified interval.
048     *
049     * @param interval The amount of time in miliseconds to wait between
050     * checks of the file system
051     */
052    public FileAlterationMonitor(final long interval) {
053        this.interval = interval;
054    }
055
056    /**
057     * Construct a monitor with the specified interval and set of observers.
058     *
059     * @param interval The amount of time in miliseconds to wait between
060     * checks of the file system
061     * @param observers The set of observers to add to the monitor.
062     */
063    public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) {
064        this(interval);
065        if (observers != null) {
066            for (final FileAlterationObserver observer : observers) {
067                addObserver(observer);
068            }
069        }
070    }
071
072    /**
073     * Return the interval.
074     *
075     * @return the interval
076     */
077    public long getInterval() {
078        return interval;
079    }
080
081    /**
082     * Set the thread factory.
083     *
084     * @param threadFactory the thread factory
085     */
086    public synchronized void setThreadFactory(final ThreadFactory threadFactory) {
087        this.threadFactory = threadFactory;
088    }
089
090    /**
091     * Add a file system observer to this monitor.
092     *
093     * @param observer The file system observer to add
094     */
095    public void addObserver(final FileAlterationObserver observer) {
096        if (observer != null) {
097            observers.add(observer);
098        }
099    }
100
101    /**
102     * Remove a file system observer from this monitor.
103     *
104     * @param observer The file system observer to remove
105     */
106    public void removeObserver(final FileAlterationObserver observer) {
107        if (observer != null) {
108            while (observers.remove(observer)) {
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     * Start 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     * Stop 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     * Stop 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.join(stopInterval);
168        } catch (final InterruptedException e) {
169            Thread.currentThread().interrupt();
170        }
171        for (final FileAlterationObserver observer : observers) {
172            observer.destroy();
173        }
174    }
175
176    /**
177     * Run.
178     */
179    public void run() {
180        while (running) {
181            for (final FileAlterationObserver observer : observers) {
182                observer.checkAndNotify();
183            }
184            if (!running) {
185                break;
186            }
187            try {
188                Thread.sleep(interval);
189            } catch (final InterruptedException ignored) {
190            }
191        }
192    }
193}