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