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     */
017    package org.apache.commons.io.monitor;
018    
019    import java.util.List;
020    import java.util.concurrent.CopyOnWriteArrayList;
021    import 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 1304052 2012-03-22 20:55:29Z ggregory $
029     * @since 2.0
030     */
031    public 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(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(long interval, FileAlterationObserver... observers) {
064            this(interval);
065            if (observers != null) {
066                for (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(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 (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(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 (InterruptedException e) {
169                Thread.currentThread().interrupt();
170            }
171            for (FileAlterationObserver observer : observers) {
172                observer.destroy();
173            }
174        }
175    
176        /**
177         * Run.
178         */
179        public void run() {
180            while (running) {
181                for (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    }