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.Collection; 020import java.util.Collections; 021import java.util.List; 022import java.util.Optional; 023import java.util.concurrent.CopyOnWriteArrayList; 024import java.util.concurrent.ThreadFactory; 025 026/** 027 * A runnable that spawns a monitoring thread triggering any 028 * registered {@link FileAlterationObserver} at a specified interval. 029 * 030 * @see FileAlterationObserver 031 * @since 2.0 032 */ 033public final class FileAlterationMonitor implements Runnable { 034 035 private static final FileAlterationObserver[] EMPTY_ARRAY = {}; 036 037 private final long interval; 038 private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<>(); 039 private Thread thread; 040 private ThreadFactory threadFactory; 041 private volatile boolean running; 042 043 /** 044 * Constructs a monitor with a default interval of 10 seconds. 045 */ 046 public FileAlterationMonitor() { 047 this(10000); 048 } 049 050 /** 051 * Constructs a monitor with the specified interval. 052 * 053 * @param interval The amount of time in milliseconds to wait between 054 * checks of the file system. 055 */ 056 public FileAlterationMonitor(final long interval) { 057 this.interval = interval; 058 } 059 060 /** 061 * Constructs a monitor with the specified interval and collection of observers. 062 * 063 * @param interval The amount of time in milliseconds to wait between 064 * checks of the file system. 065 * @param observers The collection of observers to add to the monitor. 066 * @since 2.9.0 067 */ 068 public FileAlterationMonitor(final long interval, final Collection<FileAlterationObserver> observers) { 069 // @formatter:off 070 this(interval, 071 Optional 072 .ofNullable(observers) 073 .orElse(Collections.emptyList()) 074 .toArray(EMPTY_ARRAY) 075 ); 076 // @formatter:on 077 } 078 079 /** 080 * Constructs a monitor with the specified interval and set of observers. 081 * 082 * @param interval The amount of time in milliseconds to wait between 083 * checks of the file system. 084 * @param observers The set of observers to add to the monitor. 085 */ 086 public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) { 087 this(interval); 088 if (observers != null) { 089 for (final FileAlterationObserver observer : observers) { 090 addObserver(observer); 091 } 092 } 093 } 094 095 /** 096 * Returns the interval. 097 * 098 * @return the interval 099 */ 100 public long getInterval() { 101 return interval; 102 } 103 104 /** 105 * Sets the thread factory. 106 * 107 * @param threadFactory the thread factory 108 */ 109 public synchronized void setThreadFactory(final ThreadFactory threadFactory) { 110 this.threadFactory = threadFactory; 111 } 112 113 /** 114 * Adds a file system observer to this monitor. 115 * 116 * @param observer The file system observer to add 117 */ 118 public void addObserver(final FileAlterationObserver observer) { 119 if (observer != null) { 120 observers.add(observer); 121 } 122 } 123 124 /** 125 * Removes a file system observer from this monitor. 126 * 127 * @param observer The file system observer to remove 128 */ 129 public void removeObserver(final FileAlterationObserver observer) { 130 if (observer != null) { 131 while (observers.remove(observer)) { 132 // empty 133 } 134 } 135 } 136 137 /** 138 * Returns the set of {@link FileAlterationObserver} registered with 139 * this monitor. 140 * 141 * @return The set of {@link FileAlterationObserver} 142 */ 143 public Iterable<FileAlterationObserver> getObservers() { 144 return observers; 145 } 146 147 /** 148 * Starts monitoring. 149 * 150 * @throws Exception if an error occurs initializing the observer 151 */ 152 public synchronized void start() throws Exception { 153 if (running) { 154 throw new IllegalStateException("Monitor is already running"); 155 } 156 for (final FileAlterationObserver observer : observers) { 157 observer.initialize(); 158 } 159 running = true; 160 if (threadFactory != null) { 161 thread = threadFactory.newThread(this); 162 } else { 163 thread = new Thread(this); 164 } 165 thread.start(); 166 } 167 168 /** 169 * Stops monitoring. 170 * 171 * @throws Exception if an error occurs initializing the observer 172 */ 173 public synchronized void stop() throws Exception { 174 stop(interval); 175 } 176 177 /** 178 * Stops monitoring. 179 * 180 * @param stopInterval the amount of time in milliseconds to wait for the thread to finish. 181 * A value of zero will wait until the thread is finished (see {@link Thread#join(long)}). 182 * @throws Exception if an error occurs initializing the observer 183 * @since 2.1 184 */ 185 public synchronized void stop(final long stopInterval) throws Exception { 186 if (!running) { 187 throw new IllegalStateException("Monitor is not running"); 188 } 189 running = false; 190 try { 191 thread.interrupt(); 192 thread.join(stopInterval); 193 } catch (final InterruptedException e) { 194 Thread.currentThread().interrupt(); 195 } 196 for (final FileAlterationObserver observer : observers) { 197 observer.destroy(); 198 } 199 } 200 201 /** 202 * Runs this monitor. 203 */ 204 @Override 205 public void run() { 206 while (running) { 207 for (final FileAlterationObserver observer : observers) { 208 observer.checkAndNotify(); 209 } 210 if (!running) { 211 break; 212 } 213 try { 214 Thread.sleep(interval); 215 } catch (final InterruptedException ignored) { 216 // ignore 217 } 218 } 219 } 220}