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