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.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.List; 024import java.util.Optional; 025import java.util.concurrent.CopyOnWriteArrayList; 026import java.util.concurrent.ThreadFactory; 027import java.util.stream.Stream; 028 029import org.apache.commons.io.ThreadUtils; 030 031/** 032 * A runnable that spawns a monitoring thread triggering any 033 * registered {@link FileAlterationObserver} at a specified interval. 034 * 035 * @see FileAlterationObserver 036 * @since 2.0 037 */ 038public final class FileAlterationMonitor implements Runnable { 039 040 private static final FileAlterationObserver[] EMPTY_ARRAY = {}; 041 042 private final long intervalMillis; 043 private final List<FileAlterationObserver> observers = new CopyOnWriteArrayList<>(); 044 private Thread thread; 045 private ThreadFactory threadFactory; 046 private volatile boolean running; 047 048 /** 049 * Constructs a monitor with a default interval of 10 seconds. 050 */ 051 public FileAlterationMonitor() { 052 this(10_000); 053 } 054 055 /** 056 * Constructs a monitor with the specified interval. 057 * 058 * @param intervalMillis The amount of time in milliseconds to wait between 059 * checks of the file system. 060 */ 061 public FileAlterationMonitor(final long intervalMillis) { 062 this.intervalMillis = intervalMillis; 063 } 064 065 /** 066 * Constructs a monitor with the specified interval and collection of observers. 067 * 068 * @param interval The amount of time in milliseconds to wait between 069 * checks of the file system. 070 * @param observers The collection of observers to add to the monitor. 071 * @since 2.9.0 072 */ 073 public FileAlterationMonitor(final long interval, final Collection<FileAlterationObserver> observers) { 074 // @formatter:off 075 this(interval, 076 Optional 077 .ofNullable(observers) 078 .orElse(Collections.emptyList()) 079 .toArray(EMPTY_ARRAY) 080 ); 081 // @formatter:on 082 } 083 084 /** 085 * Constructs a monitor with the specified interval and set of observers. 086 * 087 * @param interval The amount of time in milliseconds to wait between 088 * checks of the file system. 089 * @param observers The set of observers to add to the monitor. 090 */ 091 public FileAlterationMonitor(final long interval, final FileAlterationObserver... observers) { 092 this(interval); 093 if (observers != null) { 094 Stream.of(observers).forEach(this::addObserver); 095 } 096 } 097 098 /** 099 * 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}