--- a/masterfs/nbproject/project.xml Tue Mar 09 10:29:50 2010 +0100 +++ a/masterfs/nbproject/project.xml Wed Mar 10 07:50:12 2010 +0100 @@ -60,7 +60,7 @@ - 7.28 + 7.37 --- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FileObjectKeeper.java Tue Mar 09 10:29:50 2010 +0100 +++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FileObjectKeeper.java Wed Mar 10 07:50:12 2010 +0100 @@ -44,6 +44,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; @@ -74,7 +75,11 @@ listeners = new CopyOnWriteArraySet(); } if (listeners.isEmpty()) { - listenToAll(); + Callable stop = null; + if (fcl instanceof Callable && fcl.getClass().getName().equals("org.openide.filesystems.DeepListener")) { // NOI18N + stop = (Callable)fcl; + } + listenToAll(stop); } listeners.add(fcl); } @@ -123,7 +128,7 @@ LOG.log(Level.FINE, "Testing {0}, time {1}", new Object[] { file, timeStamp }); } - private final void listenToAll() { + private final void listenToAll(Callable stop) { assert Thread.holdsLock(this); assert kept == null; kept = new HashSet(); @@ -133,6 +138,16 @@ FileObject fo = en.nextElement(); if (fo instanceof FolderObj) { FolderObj obj = (FolderObj)fo; + Object shallStop = null; + if (stop != null) try { + shallStop = stop.call(); + } catch (Exception ex) { + shallStop = Boolean.TRUE; + } + if (Boolean.TRUE.equals(shallStop)) { + LOG.log(Level.INFO, "addRecursiveListener to {0} interrupted", root); // NOI18N + return; + } obj.addFileChangeListener(this); kept.add(obj); obj.getKeeper(); --- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FolderObj.java Tue Mar 09 10:29:50 2010 +0100 +++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FolderObj.java Wed Mar 10 07:50:12 2010 +0100 @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -69,6 +70,7 @@ import org.openide.filesystems.FileChangeListener; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; import org.openide.util.Mutex; /** --- a/openide.filesystems/apichanges.xml Tue Mar 09 10:29:50 2010 +0100 +++ a/openide.filesystems/apichanges.xml Wed Mar 10 07:50:12 2010 +0100 @@ -46,6 +46,23 @@ Filesystems API + + + Interruptable addRecursiveListener + + + + + +

+ New variant of addRecursiveListener method + that allows the caller to control the process enumerating + files in subtree and stop it. +

+
+ + +
Ability to specify "weight" for MultiFileSystem overrides --- a/openide.filesystems/manifest.mf Tue Mar 09 10:29:50 2010 +0100 +++ a/openide.filesystems/manifest.mf Wed Mar 10 07:50:12 2010 +0100 @@ -2,5 +2,5 @@ OpenIDE-Module: org.openide.filesystems OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml -OpenIDE-Module-Specification-Version: 7.36 +OpenIDE-Module-Specification-Version: 7.37 --- a/openide.filesystems/src/org/openide/filesystems/DeepListener.java Tue Mar 09 10:29:50 2010 +0100 +++ a/openide.filesystems/src/org/openide/filesystems/DeepListener.java Wed Mar 10 07:50:12 2010 +0100 @@ -45,6 +45,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Callable; import org.openide.util.Utilities; import org.openide.util.WeakSet; @@ -53,15 +54,17 @@ * @author Jaroslav Tulach */ final class DeepListener extends WeakReference -implements FileChangeListener, Runnable { +implements FileChangeListener, Runnable, Callable { private final File path; private FileObject watching; private boolean removed; + private final Callable stop; private static List keep = new ArrayList(); - public DeepListener(FileChangeListener listener, File path) { + DeepListener(FileChangeListener listener, File path, Callable stop) { super(listener, Utilities.activeReferenceQueue()); this.path = path; + this.stop = stop; relisten(); keep.add(this); } @@ -199,4 +202,8 @@ return get(); } + @Override + public Boolean call() throws Exception { + return stop != null ? stop.call() : null; + } } --- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java Tue Mar 09 10:29:50 2010 +0100 +++ a/openide.filesystems/src/org/openide/filesystems/FileUtil.java Wed Mar 10 07:50:12 2010 +0100 @@ -67,6 +67,7 @@ import java.util.Stack; import java.util.StringTokenizer; import java.util.WeakHashMap; +import java.util.concurrent.Callable; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.logging.Level; @@ -333,7 +334,54 @@ * @since org.openide.filesystems 7.28 */ public static void addRecursiveListener(FileChangeListener listener, File path) { - addFileChangeListener(new DeepListener(listener, path), path); + addFileChangeListener(new DeepListener(listener, path, null), path); + } + + /** + * Adds a listener to changes under given path. It permits you to listen to a file + * which does not yet exist, or continue listening to it after it is deleted and recreated, etc. + *
+ * When given path represents a file ({@code path.isDirectory() == false}), this + * code behaves exectly like {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File)}. + * Usually the path shall represent a folder ({@code path.isDirectory() == true}) + *
    + *
  • fileFolderCreated event is fired when the folder is created or a child folder created
  • + *
  • fileDataCreated event is fired when a child file is created
  • + *
  • fileDeleted event is fired when the folder is deleted or a child file/folder removed
  • + *
  • fileChanged event is fired when a child file is modified
  • + *
  • fileRenamed event is fired when the folder is renamed or a child file/folder is renamed
  • + *
  • fileAttributeChanged is fired when FileObject's attribute is changed
  • + *
+ * The above events are delivered for changes in all subdirectories (recursively). + * It is guaranteed that with each change at least one event is generated. + * For example adding a folder does not notify about content of the folder, + * hence one event is delivered. + * + * Can only add a given [listener, path] pair once. However a listener can + * listen to any number of paths. Note that listeners are always held weakly + * - if the listener is collected, it is quietly removed. + * + *
+ * As registering of the listener can take a long time, especially on deep + * hierarchies, it is possible provide a callback stop. + * This stop object is guaranteed to be called once per every folder on the + * default (when masterfs module is included) implemention. If the call + * to stop.call() returns true, then the registration of + * next recursive items is interrupted. The listener may or may not get + * some events from already registered folders. + *
+ * + * @param listener FileChangeListener to listen to changes in path + * @param path File path to listen to (even not existing) + * @param stop an interface to interrupt the process of registering + * the listener. If the call returns true, the process + * of registering the listener is immediately interrupted + * + * @see FileObject#addRecursiveListener + * @since org.openide.filesystems 7.37 + */ + public static void addRecursiveListener(FileChangeListener listener, File path, Callable stop) { + addFileChangeListener(new DeepListener(listener, path, stop), path); } /** @@ -346,7 +394,7 @@ * @since org.openide.filesystems 7.28 */ public static void removeRecursiveListener(FileChangeListener listener, File path) { - DeepListener dl = (DeepListener)removeFileChangeListenerImpl(new DeepListener(listener, path), path); + DeepListener dl = (DeepListener)removeFileChangeListenerImpl(new DeepListener(listener, path, null), path); dl.run(); }