FileAlterationMonitor.java

  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. import java.time.Duration;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.List;
  23. import java.util.Optional;
  24. import java.util.concurrent.CopyOnWriteArrayList;
  25. import java.util.concurrent.ThreadFactory;
  26. import java.util.stream.Stream;

  27. import org.apache.commons.io.ThreadUtils;

  28. /**
  29.  * A runnable that spawns a monitoring thread triggering any
  30.  * registered {@link FileAlterationObserver} at a specified interval.
  31.  *
  32.  * @see FileAlterationObserver
  33.  * @since 2.0
  34.  */
  35. public final class FileAlterationMonitor implements Runnable {

  36.     private static final FileAlterationObserver[] EMPTY_ARRAY = {};

  37.     private final long intervalMillis;
  38.     private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<>();
  39.     private Thread thread;
  40.     private ThreadFactory threadFactory;
  41.     private volatile boolean running;

  42.     /**
  43.      * Constructs a monitor with a default interval of 10 seconds.
  44.      */
  45.     public FileAlterationMonitor() {
  46.         this(10_000);
  47.     }

  48.     /**
  49.      * Constructs a monitor with the specified interval.
  50.      *
  51.      * @param intervalMillis The amount of time in milliseconds to wait between
  52.      * checks of the file system.
  53.      */
  54.     public FileAlterationMonitor(final long intervalMillis) {
  55.         this.intervalMillis = intervalMillis;
  56.     }

  57.     /**
  58.      * Constructs a monitor with the specified interval and collection of observers.
  59.      *
  60.      * @param interval The amount of time in milliseconds to wait between
  61.      * checks of the file system.
  62.      * @param observers The collection of observers to add to the monitor.
  63.      * @since 2.9.0
  64.      */
  65.     public FileAlterationMonitor(final long interval, final Collection<FileAlterationObserver> observers) {
  66.         // @formatter:off
  67.         this(interval,
  68.             Optional
  69.                 .ofNullable(observers)
  70.                 .orElse(Collections.emptyList())
  71.                 .toArray(EMPTY_ARRAY)
  72.         );
  73.         // @formatter:on
  74.     }

  75.     /**
  76.      * Constructs a monitor with the specified interval and set of observers.
  77.      *
  78.      * @param interval The amount of time in milliseconds to wait between
  79.      * checks of the file system.
  80.      * @param observers The set of observers to add to the monitor.
  81.      */
  82.     public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) {
  83.         this(interval);
  84.         if (observers != null) {
  85.             Stream.of(observers).forEach(this::addObserver);
  86.         }
  87.     }

  88.     /**
  89.      * Adds a file system observer to this monitor.
  90.      *
  91.      * @param observer The file system observer to add
  92.      */
  93.     public void addObserver(final FileAlterationObserver observer) {
  94.         if (observer != null) {
  95.             observers.add(observer);
  96.         }
  97.     }

  98.     /**
  99.      * Returns the interval.
  100.      *
  101.      * @return the interval
  102.      */
  103.     public long getInterval() {
  104.         return intervalMillis;
  105.     }

  106.     /**
  107.      * Returns the set of {@link FileAlterationObserver} registered with
  108.      * this monitor.
  109.      *
  110.      * @return The set of {@link FileAlterationObserver}
  111.      */
  112.     public Iterable<FileAlterationObserver> getObservers() {
  113.         return new ArrayList<>(observers);
  114.     }

  115.     /**
  116.      * Removes a file system observer from this monitor.
  117.      *
  118.      * @param observer The file system observer to remove
  119.      */
  120.     public void removeObserver(final FileAlterationObserver observer) {
  121.         if (observer != null) {
  122.             observers.removeIf(observer::equals);
  123.         }
  124.     }

  125.     /**
  126.      * Runs this monitor.
  127.      */
  128.     @Override
  129.     public void run() {
  130.         while (running) {
  131.             observers.forEach(FileAlterationObserver::checkAndNotify);
  132.             if (!running) {
  133.                 break;
  134.             }
  135.             try {
  136.                 ThreadUtils.sleep(Duration.ofMillis(intervalMillis));
  137.             } catch (final InterruptedException ignored) {
  138.                 // ignore
  139.             }
  140.         }
  141.     }

  142.     /**
  143.      * Sets the thread factory.
  144.      *
  145.      * @param threadFactory the thread factory
  146.      */
  147.     public synchronized void setThreadFactory(final ThreadFactory threadFactory) {
  148.         this.threadFactory = threadFactory;
  149.     }

  150.     /**
  151.      * Starts monitoring.
  152.      *
  153.      * @throws Exception if an error occurs initializing the observer
  154.      */
  155.     public synchronized void start() throws Exception {
  156.         if (running) {
  157.             throw new IllegalStateException("Monitor is already running");
  158.         }
  159.         for (final FileAlterationObserver observer : observers) {
  160.             observer.initialize();
  161.         }
  162.         running = true;
  163.         if (threadFactory != null) {
  164.             thread = threadFactory.newThread(this);
  165.         } else {
  166.             thread = new Thread(this);
  167.         }
  168.         thread.start();
  169.     }

  170.     /**
  171.      * Stops monitoring.
  172.      *
  173.      * @throws Exception if an error occurs initializing the observer
  174.      */
  175.     public synchronized void stop() throws Exception {
  176.         stop(intervalMillis);
  177.     }

  178.     /**
  179.      * Stops monitoring.
  180.      *
  181.      * @param stopInterval the amount of time in milliseconds to wait for the thread to finish.
  182.      * A value of zero will wait until the thread is finished (see {@link Thread#join(long)}).
  183.      * @throws Exception if an error occurs initializing the observer
  184.      * @since 2.1
  185.      */
  186.     public synchronized void stop(final long stopInterval) throws Exception {
  187.         if (!running) {
  188.             throw new IllegalStateException("Monitor is not running");
  189.         }
  190.         running = false;
  191.         try {
  192.             thread.interrupt();
  193.             thread.join(stopInterval);
  194.         } catch (final InterruptedException e) {
  195.             Thread.currentThread().interrupt();
  196.         }
  197.         for (final FileAlterationObserver observer : observers) {
  198.             observer.destroy();
  199.         }
  200.     }
  201. }