1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.utils.directory; 4 5 import java.io.File; 6 import java.util.*; 7 8 /** 9 * A <code>DirectoryMonitor</code> is a class that monitors a collection of 10 * directories in the filesystem for changes. Upon a change, a collection of 11 * previously registered listeners is notified. 12 * 13 * @author Paul J. Lucas [paul@lightcrafts.com] 14 */ 15 public abstract class DirectoryMonitor { 16 17 static final boolean DEBUG = false; 18 19 ////////// public ///////////////////////////////////////////////////////// 20 21 /** 22 * Add a directory to be monitored. Adding the same directory more than 23 * once is guaranteed to be harmless. 24 * 25 * @param directory The directory to be monitored. 26 */ addDirectory( File directory )27 public abstract void addDirectory( File directory ); 28 29 /** 30 * Add a {@link DirectoryListener} to the collection of 31 * {@link DirectoryListener}s that receive notifications that the monitored 32 * collection of directories have changed. 33 * 34 * @param listener The {@link DirectoryListener} to add. 35 */ addListener( DirectoryListener listener )36 public final void addListener( DirectoryListener listener ) { 37 synchronized ( m_listeners ) { 38 m_listeners.add( listener ); 39 } 40 } 41 42 /** 43 * Dispose of this <code>DirectoryMonitor</code> by stopping its thread. 44 */ dispose()45 public void dispose() { 46 m_monitorThread.stopMonitoring(); 47 } 48 49 /** 50 * Resume monitoring of and notification about directories (but only if the 51 * call to <code>resume()</code> has balanced all previous calls to 52 * {@link #suspend()}. 53 * 54 * @param force If <code>true</code>, force resumption and monitoring and 55 * notification. 56 */ resume( boolean force )57 public final synchronized void resume( boolean force ) { 58 if ( force || --m_suspendCount < 0 ) 59 m_suspendCount = 0; 60 //noinspection ConstantConditions 61 if ( DEBUG ) 62 System.out.println( 63 "DirectoryMonitor: resuming (" + m_suspendCount + ')' 64 ); 65 } 66 67 /** 68 * Remove a directory from being monitored. 69 * 70 * @param directory The directory to remove. 71 * @return Returns <code>true</code> only if the directory was being 72 * monitored and thus removed. 73 */ removeDirectory( File directory )74 public abstract boolean removeDirectory( File directory ); 75 76 /** 77 * Remove a {@link DirectoryListener} from receiving notifications about 78 * changed directories. 79 * 80 * @param listener The {@link DirectoryListener} to remove. 81 * @return Returns <code>true</code> only if the {@link DirectoryListener} 82 * was removed. 83 */ removeListener( DirectoryListener listener )84 public final boolean removeListener( DirectoryListener listener ) { 85 synchronized ( m_listeners ) { 86 return m_listeners.remove( listener ); 87 } 88 } 89 90 /** 91 * Suspend monitoring of and notification about directories. This method 92 * may be called multiple times. Every call must be balanced by a call to 93 * {@link #resume(boolean)} in order for monitoring and notification to 94 * resume. 95 */ suspend()96 public final synchronized void suspend() { 97 ++m_suspendCount; 98 //noinspection ConstantConditions 99 if ( DEBUG ) 100 System.out.println( 101 "DirectoryMonitor: suspending (" + m_suspendCount + ')' 102 ); 103 } 104 105 ////////// package /////////////////////////////////////////////////////// 106 107 /** 108 * This method is just like {@link Thread#sleep(long)} except it handles 109 * the annoying {@link InterruptedException}. 110 * 111 * @param millis The length of time to sleep in milliseconds. 112 */ doze( long millis )113 static void doze( long millis ) { 114 try { 115 Thread.sleep( millis ); 116 } 117 catch ( InterruptedException e ) { 118 // ignore 119 } 120 } 121 122 ////////// protected ////////////////////////////////////////////////////// 123 124 /** 125 * Finalize this <code>DirectoryMonitor</code> by calling 126 * {@link #dispose()}. 127 */ finalize()128 protected void finalize() throws Throwable { 129 dispose(); 130 super.finalize(); 131 } 132 133 /** 134 * Gets the set of monitored directories. 135 * 136 * @return Returns an array of said directories. 137 */ getMonitoredDirectories()138 protected abstract File[] getMonitoredDirectories(); 139 140 /** 141 * Checks whether the given directory has changed. 142 * 143 * @param directory The directory to check. It must have been previously 144 * added via {@link #addDirectory(File)}. 145 * @return Returns <code>true</code> only if the directory changed. 146 */ hasChanged( File directory )147 protected abstract boolean hasChanged( File directory ); 148 149 /** 150 * Start monitoring. This must be called by derived class constructors. 151 * This can not be put into this class's constructor because then the 152 * thread will start before the derived class has finished construction. 153 */ start()154 protected void start() { 155 m_monitorThread.start(); 156 } 157 158 ////////// private //////////////////////////////////////////////////////// 159 160 /** 161 * A <code>MonitorThread</code> is-a {@link Thread} that monitors a 162 * collection of directories for changes. 163 */ 164 private final class MonitorThread extends Thread { 165 166 ////////// public ///////////////////////////////////////////////////// 167 168 /** 169 * Monitor all the requested directories for changes. 170 */ run()171 public void run() { 172 while ( !m_stop ) { 173 final int suspendCount; 174 synchronized ( DirectoryMonitor.this ) { 175 // 176 // Make a copy of the current value of m_suspendCount so 177 // the DirectoryMonitor object isn't locked the entire time 178 // the directories are being checked below. 179 // 180 suspendCount = m_suspendCount; 181 } 182 if ( suspendCount == 0 ) { 183 for ( File dir : getMonitoredDirectories() ) 184 if ( hasChanged( dir ) ) 185 notifyListenersAbout( dir ); 186 } 187 doze( 3000 ); 188 } 189 } 190 191 ////////// package //////////////////////////////////////////////////// 192 193 /** 194 * Stop this thread. 195 */ stopMonitoring()196 void stopMonitoring() { 197 m_stop = true; 198 } 199 200 ////////// private //////////////////////////////////////////////////// 201 202 /** 203 * Construct a <code>MonitorThread</code>. 204 */ MonitorThread()205 private MonitorThread() { 206 super( "DirectoryMonitor.MonitorThread" ); 207 setDaemon( true ); 208 setPriority( MIN_PRIORITY ); 209 } 210 211 /** 212 * A flag to indicate when this thread should stop. 213 */ 214 private boolean m_stop; 215 } 216 217 /** 218 * Notify all the listeners that a directory has changed. The listeners 219 * should check whether the directory still exists. 220 * 221 * @param dir The directory to notify about. 222 */ notifyListenersAbout( File dir )223 private void notifyListenersAbout( File dir ) { 224 synchronized ( m_listeners ) { 225 for ( DirectoryListener listener : m_listeners ) 226 listener.directoryChanged( dir ); 227 } 228 } 229 230 /** 231 * The collection of listeners to notify whenever any monitored directory 232 * changes. 233 */ 234 private final Collection<DirectoryListener> m_listeners = 235 new ArrayList<DirectoryListener>(); 236 237 /** 238 * The <code>MonitorThread</code> we're using. 239 */ 240 private final MonitorThread m_monitorThread = new MonitorThread(); 241 242 /** 243 * When greater than zero, checking and notification is suspended. 244 */ 245 private int m_suspendCount; 246 } 247 /* vim:set et sw=4 ts=4: */ 248