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     */
017    
018    package org.apache.commons.jci.monitor;
019    
020    import java.io.File;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    
029    /**
030     * Implementation of a FilesystemAlterationObserver
031     * 
032     * @author tcurdt
033     */
034    public class FilesystemAlterationObserverImpl implements FilesystemAlterationObserver {
035    
036        private final Log log = LogFactory.getLog(FilesystemAlterationObserverImpl.class);
037        
038        private interface MonitorFile {
039    
040            long lastModified();
041            MonitorFile[] listFiles();
042            boolean isDirectory();
043            boolean exists();
044            String getName();
045    
046        }
047        
048        private final static class MonitorFileImpl implements MonitorFile {
049    
050            private final File file;
051    
052            public MonitorFileImpl( final File pFile ) {
053                file = pFile;
054            }
055    
056            public boolean exists() {
057                return file.exists();
058            }
059    
060            public MonitorFile[] listFiles() {
061                final File[] children = file.listFiles();
062                if (children == null) { // not a directory or IOError (e.g. protection issue)
063                    return new MonitorFile[0];
064                }
065    
066                final MonitorFile[] providers = new MonitorFile[children.length];
067                for (int i = 0; i < providers.length; i++) {
068                    providers[i] = new MonitorFileImpl(children[i]);
069                }
070                return providers;
071            }
072    
073            public String getName() {
074                return file.getName();
075            }
076    
077            public boolean isDirectory() {
078                return file.isDirectory();
079            }
080    
081            public long lastModified() {
082                return file.lastModified();
083            }
084    
085            @Override
086            public String toString() {
087                return file.toString();
088            }
089    
090        }
091    
092        private final class Entry {
093    
094            private final static int TYPE_UNKNOWN = 0;
095            private final static int TYPE_FILE = 1;
096            private final static int TYPE_DIRECTORY = 2;
097    
098            private final MonitorFile file;
099            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    }