View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.monitor;
18  
19  import java.time.Duration;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.concurrent.CopyOnWriteArrayList;
26  import java.util.concurrent.ThreadFactory;
27  import java.util.stream.Stream;
28  
29  import org.apache.commons.io.ThreadUtils;
30  
31  /**
32   * A runnable that spawns a monitoring thread triggering any
33   * registered {@link FileAlterationObserver} at a specified interval.
34   *
35   * @see FileAlterationObserver
36   * @since 2.0
37   */
38  public final class FileAlterationMonitor implements Runnable {
39  
40      private static final FileAlterationObserver[] EMPTY_ARRAY = {};
41  
42      private final long intervalMillis;
43      private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<>();
44      private Thread thread;
45      private ThreadFactory threadFactory;
46      private volatile boolean running;
47  
48      /**
49       * Constructs a monitor with a default interval of 10 seconds.
50       */
51      public FileAlterationMonitor() {
52          this(10_000);
53      }
54  
55      /**
56       * Constructs a monitor with the specified interval.
57       *
58       * @param intervalMillis The amount of time in milliseconds to wait between
59       * checks of the file system.
60       */
61      public FileAlterationMonitor(final long intervalMillis) {
62          this.intervalMillis = intervalMillis;
63      }
64  
65      /**
66       * Constructs a monitor with the specified interval and collection of observers.
67       *
68       * @param interval The amount of time in milliseconds to wait between
69       * checks of the file system.
70       * @param observers The collection of observers to add to the monitor.
71       * @since 2.9.0
72       */
73      public FileAlterationMonitor(final long interval, final Collection<FileAlterationObserver> observers) {
74          // @formatter:off
75          this(interval,
76              Optional
77                  .ofNullable(observers)
78                  .orElse(Collections.emptyList())
79                  .toArray(EMPTY_ARRAY)
80          );
81          // @formatter:on
82      }
83  
84      /**
85       * Constructs a monitor with the specified interval and set of observers.
86       *
87       * @param interval The amount of time in milliseconds to wait between
88       * checks of the file system.
89       * @param observers The set of observers to add to the monitor.
90       */
91      public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) {
92          this(interval);
93          if (observers != null) {
94              Stream.of(observers).forEach(this::addObserver);
95          }
96      }
97  
98      /**
99       * Adds a file system observer to this monitor.
100      *
101      * @param observer The file system observer to add
102      */
103     public void addObserver(final FileAlterationObserver observer) {
104         if (observer != null) {
105             observers.add(observer);
106         }
107     }
108 
109     /**
110      * Returns the interval.
111      *
112      * @return the interval
113      */
114     public long getInterval() {
115         return intervalMillis;
116     }
117 
118     /**
119      * Returns the set of {@link FileAlterationObserver} registered with
120      * this monitor.
121      *
122      * @return The set of {@link FileAlterationObserver}
123      */
124     public Iterable<FileAlterationObserver> getObservers() {
125         return new ArrayList<>(observers);
126     }
127 
128     /**
129      * Removes a file system observer from this monitor.
130      *
131      * @param observer The file system observer to remove
132      */
133     public void removeObserver(final FileAlterationObserver observer) {
134         if (observer != null) {
135             observers.removeIf(observer::equals);
136         }
137     }
138 
139     /**
140      * Runs this monitor.
141      */
142     @Override
143     public void run() {
144         while (running) {
145             observers.forEach(FileAlterationObserver::checkAndNotify);
146             if (!running) {
147                 break;
148             }
149             try {
150                 ThreadUtils.sleep(Duration.ofMillis(intervalMillis));
151             } catch (final InterruptedException ignored) {
152                 // ignore
153             }
154         }
155     }
156 
157     /**
158      * Sets the thread factory.
159      *
160      * @param threadFactory the thread factory
161      */
162     public synchronized void setThreadFactory(final ThreadFactory threadFactory) {
163         this.threadFactory = threadFactory;
164     }
165 
166     /**
167      * Starts monitoring.
168      *
169      * @throws Exception if an error occurs initializing the observer
170      */
171     public synchronized void start() throws Exception {
172         if (running) {
173             throw new IllegalStateException("Monitor is already running");
174         }
175         for (final FileAlterationObserver observer : observers) {
176             observer.initialize();
177         }
178         running = true;
179         if (threadFactory != null) {
180             thread = threadFactory.newThread(this);
181         } else {
182             thread = new Thread(this);
183         }
184         thread.start();
185     }
186 
187     /**
188      * Stops monitoring.
189      *
190      * @throws Exception if an error occurs initializing the observer
191      */
192     public synchronized void stop() throws Exception {
193         stop(intervalMillis);
194     }
195 
196     /**
197      * Stops monitoring.
198      *
199      * @param stopInterval the amount of time in milliseconds to wait for the thread to finish.
200      * A value of zero will wait until the thread is finished (see {@link Thread#join(long)}).
201      * @throws Exception if an error occurs initializing the observer
202      * @since 2.1
203      */
204     public synchronized void stop(final long stopInterval) throws Exception {
205         if (!running) {
206             throw new IllegalStateException("Monitor is not running");
207         }
208         running = false;
209         try {
210             thread.interrupt();
211             thread.join(stopInterval);
212         } catch (final InterruptedException e) {
213             Thread.currentThread().interrupt();
214         }
215         for (final FileAlterationObserver observer : observers) {
216             observer.destroy();
217         }
218     }
219 }