View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jci.monitor;
19  
20  import java.io.File;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  /**
30   * Implementation of a FilesystemAlterationObserver
31   * 
32   * @author tcurdt
33   */
34  public class FilesystemAlterationObserverImpl implements FilesystemAlterationObserver {
35  
36      private final Log log = LogFactory.getLog(FilesystemAlterationObserverImpl.class);
37      
38      private interface MonitorFile {
39  
40          long lastModified();
41          MonitorFile[] listFiles();
42          boolean isDirectory();
43          boolean exists();
44          String getName();
45  
46      }
47      
48      private final static class MonitorFileImpl implements MonitorFile {
49  
50          private final File file;
51  
52          public MonitorFileImpl( final File pFile ) {
53              file = pFile;
54          }
55  
56          public boolean exists() {
57              return file.exists();
58          }
59  
60          public MonitorFile[] listFiles() {
61              final File[] children = file.listFiles();
62              if (children == null) { // not a directory or IOError (e.g. protection issue)
63                  return new MonitorFile[0];
64              }
65  
66              final MonitorFile[] providers = new MonitorFile[children.length];
67              for (int i = 0; i < providers.length; i++) {
68                  providers[i] = new MonitorFileImpl(children[i]);
69              }
70              return providers;
71          }
72  
73          public String getName() {
74              return file.getName();
75          }
76  
77          public boolean isDirectory() {
78              return file.isDirectory();
79          }
80  
81          public long lastModified() {
82              return file.lastModified();
83          }
84  
85          @Override
86          public String toString() {
87              return file.toString();
88          }
89  
90      }
91  
92      private final class Entry {
93  
94          private final static int TYPE_UNKNOWN = 0;
95          private final static int TYPE_FILE = 1;
96          private final static int TYPE_DIRECTORY = 2;
97  
98          private final MonitorFile file;
99          private long lastModified = -1;
100         private int lastType = TYPE_UNKNOWN;
101         private final Map<String, Entry> children = new HashMap<String, Entry>();
102 
103         public Entry(final MonitorFile pFile) {
104             file = pFile;
105         }
106 
107         public String getName() {
108             return file.getName();
109         }
110         
111         
112         @Override
113         public String toString() {
114             return file.toString();
115         }
116 
117 
118         private void compareChildren() {
119             if (!file.isDirectory()) {
120                 return;
121             }
122 
123             final MonitorFile[] files = file.listFiles();
124             final Set<Entry> deleted = new HashSet<Entry>(children.values());
125             for (MonitorFile f : files) {
126                 final String name = f.getName();
127                 final Entry entry = children.get(name);
128                 if (entry != null) {
129                     // already recognized as child
130                     deleted.remove(entry);
131 
132                     if(entry.needsToBeDeleted()) {
133                         // we have to delete this one
134                         children.remove(name);
135                     }
136                 } else {
137                     // a new child
138                     final Entry newChild = new Entry(f);
139                     children.put(name, newChild);
140                     newChild.needsToBeDeleted();
141                 }
142             }
143 
144             // the ones not found on disk anymore
145 
146             for (Entry entry : deleted) {
147                 entry.deleteChildrenAndNotify();
148                 children.remove(entry.getName());
149             }
150         }
151 
152 
153         private void deleteChildrenAndNotify() {
154             for (Entry entry : children.values()) {
155                 entry.deleteChildrenAndNotify();
156             }
157             children.clear();
158 
159             if(lastType == TYPE_DIRECTORY) {
160                 notifyOnDirectoryDelete(this);
161             } else if (lastType == TYPE_FILE) {
162                 notifyOnFileDelete(this);
163             }
164         }
165 
166         public boolean needsToBeDeleted() {
167 
168             if (!file.exists()) {
169                 // deleted or has never existed yet
170 
171 //                log.debug(file + " does not exist or has been deleted");
172 
173                 deleteChildrenAndNotify();
174 
175                 // mark to be deleted by parent
176                 return true;
177             } else {
178                 // exists
179                 final long currentModified = file.lastModified(); 
180 
181                 if (currentModified != lastModified) {
182                     // last modified has changed
183                     lastModified = currentModified;
184 
185 //                    log.debug(file + " has new last modified");
186 
187                     // types only changes when also the last modified changes
188                     final int newType = (file.isDirectory()?TYPE_DIRECTORY:TYPE_FILE); 
189 
190                     if (lastType != newType) {
191                         // the type has changed
192 
193 //                        log.debug(file + " has a new type");
194 
195                         deleteChildrenAndNotify();
196 
197                         lastType = newType;
198 
199                         // and then an add as the new type
200 
201                         if (newType == TYPE_DIRECTORY) {
202                             notifyOnDirectoryCreate(this);
203                             compareChildren();
204                         } else {
205                             notifyOnFileCreate(this);
206                         }
207 
208                         return false;
209                     }
210 
211                     if (newType == TYPE_DIRECTORY) {
212                         notifyOnDirectoryChange(this);
213                         compareChildren();
214                     } else {
215                         notifyOnFileChange(this);
216                     }
217 
218                     return false;
219 
220                 } else {
221 
222                     // so exists and has not changed
223 
224 //                    log.debug(file + " does exist and has not changed");
225 
226                     compareChildren();
227 
228                     return false;
229                 }
230             }
231         }
232         
233         public MonitorFile getFile() {
234             return file;
235         }
236 
237         public void markNotChanged() {
238             lastModified = file.lastModified();
239         }
240 
241     }
242 
243     private final File rootDirectory;
244     private final Entry rootEntry;
245 
246     private FilesystemAlterationListener[] listeners = new FilesystemAlterationListener[0];
247     private final Set<FilesystemAlterationListener> listenersSet = new HashSet<FilesystemAlterationListener>();
248 
249     public FilesystemAlterationObserverImpl( final File pRootDirectory ) {
250         rootDirectory = pRootDirectory;
251         rootEntry = new Entry(new MonitorFileImpl(pRootDirectory));
252     }
253 
254 
255 
256     private void notifyOnStart() {
257         log.debug("onStart " + rootEntry);
258         for (FilesystemAlterationListener listener : listeners) {
259             listener.onStart(this);
260         }
261     }
262     private void notifyOnStop() {
263         log.debug("onStop " + rootEntry);
264         for (FilesystemAlterationListener listener : listeners) {
265             listener.onStop(this);
266         }
267     }
268 
269     private void notifyOnFileCreate( final Entry pEntry ) {
270         log.debug("onFileCreate " + pEntry);
271         for (FilesystemAlterationListener listener : listeners) {
272             listener.onFileCreate(((MonitorFileImpl)pEntry.getFile()).file );
273         }
274     }
275     private void notifyOnFileChange( final Entry pEntry ) {
276         log.debug("onFileChange " + pEntry);
277         for (FilesystemAlterationListener listener : listeners) {
278             listener.onFileChange(((MonitorFileImpl)pEntry.getFile()).file );
279         }
280     }
281     private void notifyOnFileDelete( final Entry pEntry ) {
282         log.debug("onFileDelete " + pEntry);
283         for (FilesystemAlterationListener listener : listeners) {
284             listener.onFileDelete(((MonitorFileImpl)pEntry.getFile()).file );
285         }
286     }
287 
288     private void notifyOnDirectoryCreate( final Entry pEntry ) {
289         log.debug("onDirectoryCreate " + pEntry);
290         for (FilesystemAlterationListener listener : listeners) {
291             listener.onDirectoryCreate(((MonitorFileImpl)pEntry.getFile()).file );
292         }
293     }
294     private void notifyOnDirectoryChange( final Entry pEntry ) {
295         log.debug("onDirectoryChange " + pEntry);
296         for (FilesystemAlterationListener listener : listeners) {
297             listener.onDirectoryChange(((MonitorFileImpl)pEntry.getFile()).file );
298         }
299     }
300     private void notifyOnDirectoryDelete( final Entry pEntry ) {
301         log.debug("onDirectoryDelete " + pEntry);
302         for (FilesystemAlterationListener listener : listeners) {
303             listener.onDirectoryDelete(((MonitorFileImpl)pEntry.getFile()).file );
304         }
305     }
306 
307 
308     private void checkEntries() {
309         if(rootEntry.needsToBeDeleted()) {
310             // root not existing
311             rootEntry.lastType = Entry.TYPE_UNKNOWN;
312         }
313     }
314 
315     
316     public void checkAndNotify() {
317     	synchronized(listenersSet) {
318 	        if (listeners.length == 0) {
319 	            return;
320 	        }
321 	
322 	        notifyOnStart();
323 	        
324 	        checkEntries();
325 	        
326 	        notifyOnStop();
327     	}
328     }
329 
330     
331     public File getRootDirectory() {
332         return rootDirectory;
333     }
334 
335     public void addListener( final FilesystemAlterationListener pListener ) {
336     	synchronized(listenersSet) {
337 	        if (listenersSet.add(pListener)) {
338 	            listeners = createArrayFromSet();
339 	        }
340     	}
341     }
342 
343     public void removeListener( final FilesystemAlterationListener pListener ) {
344     	synchronized(listenersSet) {
345 	        if (listenersSet.remove(pListener)) {
346 	            listeners = createArrayFromSet();
347 	        }
348     	}
349     }
350 
351     private FilesystemAlterationListener[] createArrayFromSet() {
352         final FilesystemAlterationListener[] newListeners = new FilesystemAlterationListener[listenersSet.size()];
353         listenersSet.toArray(newListeners);
354         return newListeners;
355     }
356 
357     public FilesystemAlterationListener[] getListeners() {
358     	synchronized(listenersSet) {
359         	final FilesystemAlterationListener[] res = new FilesystemAlterationListener[listeners.length];
360         	System.arraycopy(listeners, 0, res, 0, res.length);
361             return res;
362     	}    	
363     }
364 }