diff -r 33c1b2fcd188 openide.filesystems/apichanges.xml --- a/openide.filesystems/apichanges.xml Fri Aug 21 09:26:26 2009 +0200 +++ b/openide.filesystems/apichanges.xml Tue Aug 25 15:15:03 2009 +0200 @@ -46,6 +46,23 @@ Filesystems API + + + Support for recursive listeners + + + + + +

+ One can register a recursive listener on a file object by + calling + FileObject.addRecursiveListener(FileChangeListener). +

+
+ + +
Allow modules to dynamically add/remove layer content diff -r 33c1b2fcd188 openide.filesystems/nbproject/project.properties --- a/openide.filesystems/nbproject/project.properties Fri Aug 21 09:26:26 2009 +0200 +++ b/openide.filesystems/nbproject/project.properties Tue Aug 25 15:15:03 2009 +0200 @@ -44,4 +44,4 @@ javadoc.main.page=org/openide/filesystems/doc-files/api.html javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=7.23.0 +spec.version.base=7.24.0 diff -r 33c1b2fcd188 openide.filesystems/src/org/openide/filesystems/ExternalUtil.java --- a/openide.filesystems/src/org/openide/filesystems/ExternalUtil.java Fri Aug 21 09:26:26 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/ExternalUtil.java Tue Aug 25 15:15:03 2009 +0200 @@ -112,7 +112,7 @@ return orig; } - private static Logger LOG = Logger.getLogger("org.openide.filesystems"); // NOI18N + final static Logger LOG = Logger.getLogger("org.openide.filesystems"); // NOI18N /** Logs a text. */ public static void log(String msg) { diff -r 33c1b2fcd188 openide.filesystems/src/org/openide/filesystems/FileObject.java --- a/openide.filesystems/src/org/openide/filesystems/FileObject.java Fri Aug 21 09:26:26 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileObject.java Tue Aug 25 15:15:03 2009 +0200 @@ -58,7 +58,9 @@ import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; +import java.util.logging.Level; import org.openide.util.Enumerations; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.UserQuestionException; @@ -413,6 +415,53 @@ */ public abstract void removeFileChangeListener(FileChangeListener fcl); + + /** Adds a listener to this {@link FileObject} and all its children and + * children or its children. + * It is guaranteed that whenever a change + * is made via the FileSystem API itself under this {@link FileObject} + * that it is notified to the fcl listener. Whether external + * changes (if they make sense) are detected and + * notified depends on actual implementation. As some implementations may + * need to perform non-trivial amount of work during initialization of + * listeners, this methods can take long time. Usage of this method may + * consume a lot of system resources and as such it shall be used with care. + * Traditional {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener)} + * is definitely preferred variant. + *

+ * If you are running with the MasterFS module enabled, it guarantees + * that for files backed with real {@link File}, the system initializes + * itself to detect external changes on the whole subtree. + * This requires non-trivial amount of work and especially on slow + * disks (aka networks ones) may take a long time to add the listener + * and also refresh the system when {@link FileObject#refresh()} + * and especially {@link FileUtil#refreshAll()} is requested. + *

+ * + * @param fcl the listener to register + * @since 7.24 + */ + public void addRecursiveListener(FileChangeListener fcl) { + try { + getFileSystem().addFileChangeListener(new RecursiveListener(this, fcl)); + } catch (FileStateInvalidException ex) { + ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex); + } + } + + /** Removes listener previously added by {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener)} + * + * @param fcl the listener to remove + * @since 7.24 + */ + public void removeRecursiveListener(FileChangeListener fcl) { + try { + getFileSystem().removeFileChangeListener(new RecursiveListener(this, fcl)); + } catch (FileStateInvalidException ex) { + ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex); + } + } + /** Fire data creation event. * @param en listeners that should receive the event * @param fe the event to fire in this object diff -r 33c1b2fcd188 openide.filesystems/src/org/openide/filesystems/RecursiveListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.filesystems/src/org/openide/filesystems/RecursiveListener.java Tue Aug 25 15:15:03 2009 +0200 @@ -0,0 +1,127 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.openide.filesystems; + +import java.lang.ref.WeakReference; + +/** + * + * @author Jaroslav Tulach + */ +final class RecursiveListener extends WeakReference +implements FileChangeListener { + private final FileChangeListener fcl; + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RecursiveListener other = (RecursiveListener) obj; + if (this.fcl != other.fcl && (this.fcl == null || !this.fcl.equals(other.fcl))) { + return false; + } + final FileObject otherFo = other.get(); + final FileObject thisFo = this.get(); + if (thisFo != otherFo && (thisFo == null || !thisFo.equals(otherFo))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final FileObject thisFo = this.get(); + int hash = 3; + hash = 37 * hash + (this.fcl != null ? this.fcl.hashCode() : 0); + hash = 13 * hash + (thisFo != null ? thisFo.hashCode() : 0); + return hash; + } + + public RecursiveListener(FileObject source, FileChangeListener fcl) { + super(source); + this.fcl = fcl; + } + + public void fileRenamed(FileRenameEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileRenamed(fe); + } + } + + public void fileFolderCreated(FileEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileFolderCreated(fe); + } + } + + public void fileDeleted(FileEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileDeleted(fe); + } + } + + public void fileDataCreated(FileEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileDataCreated(fe); + } + } + + public void fileChanged(FileEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileChanged(fe); + } + } + + public void fileAttributeChanged(FileAttributeEvent fe) { + FileObject thisFo = this.get(); + if (thisFo != null && FileUtil.isParentOf(thisFo, fe.getFile())) { + fcl.fileAttributeChanged(fe); + } + } +} diff -r 33c1b2fcd188 openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Fri Aug 21 09:26:26 2009 +0200 +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Tue Aug 25 15:15:03 2009 +0200 @@ -1569,6 +1569,104 @@ assertNotNull(child); } } + + public void testRecursiveListener() throws IOException { + checkSetUp(); + + FileObject folder1 = getTestFolder1 (root); + /** delete first time*/ + try { + FileObject obj = FileUtil.createData(folder1, "my/sub/children/children.java"); + FileObject sub = obj.getParent().getParent(); + + class L implements FileChangeListener { + StringBuilder sb = new StringBuilder(); + + public void fileFolderCreated(FileEvent fe) { + sb.append("FolderCreated"); + } + + public void fileDataCreated(FileEvent fe) { + sb.append("DataCreated"); + } + + public void fileChanged(FileEvent fe) { + sb.append("Changed"); + } + + public void fileDeleted(FileEvent fe) { + sb.append("Deleted"); + } + + public void fileRenamed(FileRenameEvent fe) { + sb.append("Renamed"); + } + + public void fileAttributeChanged(FileAttributeEvent fe) { + sb.append("AttributeChanged"); + } + + public void assertMessages(String txt, String msg) { + assertEquals(txt, msg, sb.toString()); + sb.setLength(0); + } + } + L recursive = new L(); + L flat = new L(); + + sub.addFileChangeListener(flat); + sub.addRecursiveListener(recursive); + + FileObject fo = obj.getParent().createData("sibling.java"); + + flat.assertMessages("No messages in flat mode", ""); + recursive.assertMessages("Creation", "DataCreated"); + + fo.setAttribute("jarda", "hello"); + + flat.assertMessages("No messages in flat mode", ""); + recursive.assertMessages("attr", "AttributeChanged"); + + final OutputStream os = fo.getOutputStream(); + os.write(10); + os.close(); + + flat.assertMessages("No messages in flat mode", ""); + recursive.assertMessages("written", "Changed"); + + fo.delete(); + + flat.assertMessages("No messages in flat mode", ""); + recursive.assertMessages("gone", "Deleted"); + + sub.createFolder("testFolder"); + + flat.assertMessages("Direct Folder notified", "FolderCreated"); + recursive.assertMessages("Direct Folder notified", "FolderCreated"); + + sub.getParent().createData("unimportant.txt"); + + flat.assertMessages("No messages in flat mode", ""); + recursive.assertMessages("No messages in recursive mode", ""); + + sub.removeRecursiveListener(recursive); + + sub.createData("test.data"); + + flat.assertMessages("Direct file notified", "DataCreated"); + recursive.assertMessages("No longer active", ""); + + WeakReference ref = new WeakReference(recursive); + recursive = null; + assertGC("Listener can be GCed", ref); + + } catch (IOException iex) { + if (fs.isReadOnly() || root.isReadOnly()) return; + throw iex; + } finally { + } + } + /** Test of delete method, of class org.openide.filesystems.FileObject. */ public void testCreateDeleteFolderCreate () throws IOException {