--- a/masterfs/manifest.mf Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/manifest.mf Wed Sep 16 16:32:49 2009 +0200
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.masterfs/2
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/resources/Bundle.properties
-OpenIDE-Module-Specification-Version: 2.17
+OpenIDE-Module-Specification-Version: 2.18
AutoUpdate-Show-In-Client: false
AutoUpdate-Essential-Module: true
--- a/masterfs/nbproject/project.xml Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/nbproject/project.xml Wed Sep 16 16:32:49 2009 +0200
@@ -60,7 +60,7 @@
- 6.2
+ 7.28
--- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/BaseFileObj.java Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/BaseFileObj.java Wed Sep 16 16:32:49 2009 +0200
@@ -203,6 +203,12 @@
public final boolean isRoot() {
return false;
}
+
+ public final java.util.Date lastModified() {
+ final File f = getFileName().getFile();
+ final long lastModified = f.lastModified();
+ return new Date(lastModified);
+ }
@Override
public final FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException {
@@ -376,6 +382,16 @@
getEventSupport().remove(FileChangeListener.class, fcl);
}
+ @Override
+ public void addRecursiveListener(FileChangeListener fcl) {
+ addFileChangeListener(fcl);
+ }
+
+ @Override
+ public void removeRecursiveListener(FileChangeListener fcl) {
+ removeFileChangeListener(fcl);
+ }
+
private Enumeration getListeners() {
if (eventSupport == null) {
return Enumerations.empty();
--- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FileObj.java Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FileObj.java Wed Sep 16 16:32:49 2009 +0200
@@ -210,11 +210,6 @@
return super.canWrite();
}
- public final Date lastModified() {
- final File f = getFileName().getFile();
- return new Date(f.lastModified());
- }
-
final void setLastModified(long lastModified) {
if (this.lastModified != 0) { // #130998 - don't set when already invalidated
if (this.lastModified != -1 && !realLastModifiedCached) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ 70eda1b69061 Wed Sep 16 16:32:49 2009 +0200
@@ -0,0 +1,249 @@
+/*
+ * 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.netbeans.modules.masterfs.filebasedfs.fileobjects;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.modules.masterfs.filebasedfs.fileobjects.FileObjectFactory.Caller;
+import org.openide.filesystems.FileAttributeEvent;
+import org.openide.filesystems.FileChangeListener;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileRenameEvent;
+
+/** Keeps list of fileobjects under given root. Adapted from Jan Lahoda's work
+ * in issue 168237
+ */
+final class FileObjectKeeper implements FileChangeListener {
+ private static final Logger LOG = Logger.getLogger(FileObjectKeeper.class.getName());
+
+ private Set kept;
+ private Collection listeners;
+ private final FolderObj root;
+ private long timeStamp;
+
+ public FileObjectKeeper(FolderObj root) {
+ this.root = root;
+ }
+
+ public synchronized void addRecursiveListener(FileChangeListener fcl) {
+ if (listeners == null) {
+ listeners = new CopyOnWriteArraySet();
+ }
+ if (listeners.isEmpty()) {
+ listenToAll();
+ }
+ listeners.add(fcl);
+ }
+
+ public synchronized void removeRecursiveListener(FileChangeListener fcl) {
+ if (listeners == null) {
+ return;
+ }
+ listeners.remove(fcl);
+ if (listeners.isEmpty()) {
+ listenNoMore();
+ }
+ }
+
+ public void init(long previous, FileObjectFactory factory, boolean expected) {
+ File file = root.getFileName().getFile();
+ File[] arr = file.listFiles();
+ long ts = 0;
+ if (arr != null) {
+ for (File f : arr) {
+ if (f.isDirectory()) {
+ continue;
+ }
+ long lm = f.lastModified();
+ LOG.log(Level.FINE, " check {0} for {1}", new Object[] { lm, f });
+ if (lm > ts) {
+ ts = lm;
+ }
+ if (lm > previous && factory != null) {
+ final BaseFileObj prevFO = factory.getCachedOnly(f);
+ if (prevFO == null) {
+ BaseFileObj who = factory.getValidFileObject(f, Caller.Others);
+ LOG.log(Level.FINE, "External change detected {0}", who);
+ who.fireFileChangedEvent(expected);
+ } else {
+ prevFO.refresh(expected, true);
+ }
+ }
+ }
+ }
+ timeStamp = ts;
+ LOG.log(Level.FINE, "Testing {0}, time {1}", new Object[] { file, timeStamp });
+ }
+
+ private final void listenToAll() {
+ assert Thread.holdsLock(this);
+ assert kept == null;
+ kept = new HashSet();
+ root.addFileChangeListener(this);
+ Enumeration extends FileObject> en = root.getChildren(true);
+ while (en.hasMoreElements()) {
+ FileObject fo = en.nextElement();
+ if (fo instanceof FolderObj) {
+ FolderObj obj = (FolderObj)fo;
+ obj.addFileChangeListener(this);
+ kept.add(obj);
+ obj.getKeeper();
+ }
+ }
+ }
+
+ private final void listenNoMore() {
+ assert Thread.holdsLock(this);
+
+ root.removeFileChangeListener(this);
+ Set k = kept;
+ if (k != null) {
+ for (FileObject fo : k) {
+ fo.removeFileChangeListener(this);
+ }
+ kept = null;
+ }
+ }
+
+ public void fileFolderCreated(FileEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ final FileObject f = fe.getFile();
+ if (f instanceof FolderObj) {
+ synchronized (this) {
+ kept.add(f);
+ f.addFileChangeListener(this);
+ Enumeration extends FileObject> en = f.getChildren(true);
+ while (en.hasMoreElements()) {
+ FileObject fo = en.nextElement();
+ if (fo instanceof FolderObj) {
+ fo.addFileChangeListener(this);
+ }
+ }
+ }
+ }
+ for (FileChangeListener l : arr) {
+ l.fileFolderCreated(fe);
+ }
+ }
+
+ public void fileDataCreated(FileEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ for (FileChangeListener l : arr) {
+ l.fileDataCreated(fe);
+ }
+ }
+
+ public void fileChanged(FileEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ for (FileChangeListener l : arr) {
+ l.fileChanged(fe);
+ }
+ }
+
+ public void fileDeleted(FileEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ final FileObject f = fe.getFile();
+ if (f.isFolder() && fe.getSource() == f && f != root) {
+ // there will be another event for parent folder
+ return;
+ }
+
+ for (FileChangeListener l : arr) {
+ l.fileDeleted(fe);
+ }
+ if (f instanceof FolderObj) {
+ synchronized (this) {
+ if (kept != null) {
+ kept.remove(f);
+ }
+ f.removeFileChangeListener(this);
+ }
+ }
+ }
+
+ public void fileRenamed(FileRenameEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ final FileObject f = fe.getFile();
+ if (f.isFolder() && fe.getSource() == f && f != root) {
+ // there will be another event for parent folder
+ return;
+ }
+ for (FileChangeListener l : arr) {
+ l.fileRenamed(fe);
+ }
+ }
+
+ public void fileAttributeChanged(FileAttributeEvent fe) {
+ Collection arr = listeners;
+ if (arr == null) {
+ return;
+ }
+ for (FileChangeListener l : arr) {
+ l.fileAttributeChanged(fe);
+ }
+ }
+
+ long childrenLastModified() {
+ return timeStamp;
+ }
+
+}
--- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FolderObj.java Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/FolderObj.java Wed Sep 16 16:32:49 2009 +0200
@@ -48,7 +48,6 @@
import java.io.OutputStream;
import java.io.SyncFailedException;
import java.util.ArrayList;
-import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -69,6 +68,7 @@
import org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager;
import org.netbeans.modules.masterfs.filebasedfs.utils.FileInfo;
import org.netbeans.modules.masterfs.providers.ProvidedExtensions;
+import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.util.Mutex;
@@ -82,7 +82,8 @@
private static final Mutex mutex = new Mutex(FolderObj.mp);
private FolderChildrenCache folderChildren;
- boolean valid = true;
+ boolean valid = true;
+ private FileObjectKeeper keeper;
/**
* Creates a new instance of FolderImpl
@@ -333,6 +334,7 @@
public void refreshImpl(final boolean expected, boolean fire) {
final ChildrenCache cache = getChildrenCache();
final Mutex.Privileged mutexPrivileged = cache.getMutexPrivileged();
+ final long previous = keeper == null ? -1 : keeper.childrenLastModified();
Set oldChildren = null;
Map refreshResult = null;
@@ -412,6 +414,11 @@
fireFileDeletedEvent(expected);
}
}
+
+ if (previous != -1) {
+ assert keeper != null;
+ keeper.init(previous, factory, expected);
+ }
}
@Override
@@ -487,10 +494,6 @@
throw new IOException(getPath());
}
- public final java.util.Date lastModified() {
- final File f = getFileName().getFile();
- return new Date(f.lastModified());
- }
public final FileLock lock() throws IOException {
return new FileLock();
@@ -508,6 +511,25 @@
return folderChildren;
}
+ synchronized FileObjectKeeper getKeeper() {
+ if (keeper == null) {
+ keeper = new FileObjectKeeper(this);
+ keeper.init(-1, null, false);
+ }
+ return keeper;
+ }
+
+ @Override
+ public final void addRecursiveListener(FileChangeListener fcl) {
+ getKeeper().addRecursiveListener(fcl);
+ }
+
+ @Override
+ public final void removeRecursiveListener(FileChangeListener fcl) {
+ getKeeper().removeRecursiveListener(fcl);
+ }
+
+
public final class FolderChildrenCache implements ChildrenCache {
public final ChildrenSupport ch = new ChildrenSupport();
--- a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/ReplaceForSerialization.java Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/src/org/netbeans/modules/masterfs/filebasedfs/fileobjects/ReplaceForSerialization.java Wed Sep 16 16:32:49 2009 +0200
@@ -91,10 +91,6 @@
return false;
}
- public Date lastModified() {
- return new Date(0L);
- }
-
/* Test whether the file is valid. The file can be invalid if it has been deserialized
* and the file no longer exists on disk; or if the file has been deleted.
*
--- a/masterfs/test/unit/src/org/netbeans/modules/masterfs/filebasedfs/FileUtilTest.java Wed Sep 16 14:48:03 2009 +0200
+++ a/masterfs/test/unit/src/org/netbeans/modules/masterfs/filebasedfs/FileUtilTest.java Wed Sep 16 16:32:49 2009 +0200
@@ -53,6 +53,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
+import junit.framework.Test;
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.RandomlyFails;
import org.openide.filesystems.FileAttributeEvent;
@@ -239,6 +240,141 @@
assertGC("FileChangeListener not collected.", ref);
}
+ public void testAddRecursiveListener() throws IOException, InterruptedException {
+ clearWorkDir();
+ File rootF = getWorkDir();
+ File dirF = new File(rootF, "dir");
+ File fileF = new File(dirF, "subdir");
+
+ // adding listeners
+ TestFileChangeListener fcl = new TestFileChangeListener();
+ FileUtil.addRecursiveListener(fcl, fileF);
+ try {
+ FileUtil.addRecursiveListener(fcl, fileF);
+ fail("Should not be possible to add listener for the same path.");
+ } catch (IllegalArgumentException iae) {
+ // ok
+ }
+ TestFileChangeListener fcl2 = new TestFileChangeListener();
+ try {
+ FileUtil.removeRecursiveListener(fcl2, fileF);
+ fail("Should not be possible to remove listener which is not registered.");
+ } catch (IllegalArgumentException iae) {
+ // ok
+ }
+ FileUtil.addRecursiveListener(fcl2, fileF);
+
+ // creation
+ final FileObject rootFO = FileUtil.toFileObject(rootF);
+ FileObject dirFO = rootFO.createFolder("dir");
+ assertEquals("Event fired when just parent dir created.", 0, fcl.checkAll());
+ FileObject fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
+ assertEquals("Event not fired when file was created.", 2, fcl.check(EventType.FOLDER_CREATED));
+ assertEquals("Event not fired when file was created.", 2, fcl2.check(EventType.FOLDER_CREATED));
+ FileObject fileFO2 = FileUtil.createData(dirFO, "subdir/subsubdir/file2");
+ assertEquals("Event not fired when file was created.", 2, fcl.check(EventType.DATA_CREATED));
+ assertEquals("Event not fired when file was created.", 2, fcl2.check(EventType.DATA_CREATED));
+ FileObject fileAFO = FileUtil.createData(dirFO, "fileA");
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // remove listener
+ FileUtil.removeRecursiveListener(fcl2, fileF);
+ fcl2.disabled = true;
+ assertEquals("No other events should be fired.", 0, fcl2.checkAll());
+
+ // modification
+ fileFO.getOutputStream().close();
+ fileFO.getOutputStream().close();
+ assertEquals("Event not fired when file was modified.", 2, fcl.check(EventType.CHANGED));
+ // no event fired when other file modified
+ fileAFO.getOutputStream().close();
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // deletion
+ fileFO.delete();
+ assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
+ dirFO.delete();
+ assertEquals("Event not fired when parent dir deleted.", 1, fcl.checkAll());
+ dirFO = rootFO.createFolder("dir");
+ fileFO = FileUtil.createData(dirFO, "subdir/subsubdir/file");
+ assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.DATA_CREATED));
+ assertEquals("Event not fired when dirs created.", 2, fcl.check(EventType.FOLDER_CREATED));
+ dirFO.delete();
+ assertEquals("Event not fired when parent dir deleted.", 1, fcl.check(EventType.DELETED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // atomic action
+ FileUtil.runAtomicAction(new Runnable() {
+
+ public void run() {
+ FileObject dirFO;
+ try {
+ dirFO = rootFO.createFolder("dir");
+ rootFO.createFolder("fakedir");
+ rootFO.setAttribute("fake", "fake");
+ rootFO.createData("fakefile");
+ FileUtil.createData(dirFO, "subdir/subsubdir/file");
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+ });
+ assertEquals("Notifying the folder creation only.", 1, fcl.check(EventType.FOLDER_CREATED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // rename
+ dirFO = FileUtil.toFileObject(dirF);
+ fileFO = FileUtil.toFileObject(fileF);
+ FileLock lock = dirFO.lock();
+ dirFO.rename(lock, "dirRenamed", null);
+ lock.releaseLock();
+ assertEquals("Event fired when parent dir renamed.", 0, fcl.checkAll());
+ lock = fileFO.lock();
+ fileFO.rename(lock, "fileRenamed", null);
+ lock.releaseLock();
+ assertEquals("Renamed event not fired.", 1, fcl.check(EventType.RENAMED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // disk changes
+ dirF.mkdir();
+ final File subdir = new File(fileF, "subdir");
+ subdir.mkdirs();
+ final File newfile = new File(subdir, "newfile");
+ assertTrue(newfile.createNewFile());
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file was created.", 1, fcl.check(EventType.FOLDER_CREATED));
+ Thread.sleep(1000); // make sure timestamp changes
+ new FileOutputStream(newfile).close();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file was modified.", 1, fcl.check(EventType.CHANGED));
+ assertEquals("Attribute change event not fired (see #129178).", 3, fcl.check(EventType.ATTRIBUTE_CHANGED));
+ newfile.delete();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file deleted.", 1, fcl.check(EventType.DELETED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // disk changes #66444
+ File fileX = new File(subdir, "oscilating.file");
+ for (int cntr = 0; cntr < 50; cntr++) {
+ fileX.getParentFile().mkdirs();
+ new FileOutputStream(fileX).close();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file was created; count=" + cntr, 1, fcl.check(EventType.DATA_CREATED));
+ fileX.delete();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file deleted; count=" + cntr, 1, fcl.check(EventType.DELETED));
+ }
+
+ // removed listener
+ assertEquals("No other events should be fired in removed listener.", 0, fcl2.checkAll());
+
+ // weakness
+ WeakReference ref = new WeakReference(fcl);
+ fcl = null;
+ assertGC("FileChangeListener not collected.", ref);
+ }
+
/** Tests FileChangeListener on folder. As declared in
* {@link FileUtil#addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File) }
* - fileFolderCreated event is fired when the folder is created or a child folder created
@@ -309,12 +445,327 @@
assertEquals("No other events should be fired.", 0, fcl.checkAll());
}
+ /** Tests FileObject.addRecursiveListener on folder as declared in
+ * {@link FileObject#addRecursiveListener(org.openide.filesystems.FileChangeListener) }.
+ * It is expected that all events from sub folders are delivered just once.
+ */
+ public void testAddRecursiveListenerToFileObjectFolder() throws Exception {
+ checkFolderRecursiveListener(false);
+ }
+
+ /** Tests FileUtil.addRecursiveListener on folder as declared in
+ * {@link FileUtil#addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File) }.
+ * It is expected that all events from sub folders are delivered just once.
+ */
+ public void testAddRecursiveListenerToFileFolder() throws Exception {
+ checkFolderRecursiveListener(true);
+ }
+
+ /** Tests addRecursiveListener on folder either added to FileObject or File.
+ * @param isOnFile true to add listener to java.io.File, false to FileObject
+ */
+ private void checkFolderRecursiveListener(boolean isOnFile) throws Exception {
+ clearWorkDir();
+ // test files: dir/file1, dir/subdir/subfile, dir/subdir/subsubdir/subsubfile
+ final File rootF = getWorkDir();
+ final File dirF = new File(rootF, "dir");
+ File fileF = new File(dirF, "file1");
+ File subdirF = new File(dirF, "subdir");
+ File subfileF = new File(subdirF, "subfile");
+ File subsubdirF = new File(subdirF, "subsubdir");
+ File subsubfileF = new File(subsubdirF, "subsubfile");
+
+ TestFileChangeListener fcl = new TestFileChangeListener();
+ FileObject dirFO;
+ if (isOnFile) {
+ FileUtil.addRecursiveListener(fcl, dirF);
+ dirFO = FileUtil.createFolder(dirF);
+ assertEquals("Wrong number of events fired when folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
+ } else {
+ dirFO = FileUtil.createFolder(dirF);
+ dirFO.addRecursiveListener(fcl);
+ }
+
+ // create dir
+ FileObject subdirFO = dirFO.createFolder("subdir");
+ assertEquals("Wrong number of events fired when sub folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
+ FileObject subsubdirFO = subdirFO.createFolder("subsubdir");
+ assertEquals("Wrong number of events when sub sub folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
+
+ // create file
+ FileObject file1FO = dirFO.createData("file1");
+ assertEquals("Wrong number of events when data created.", 1, fcl.check(EventType.DATA_CREATED));
+ FileObject subfileFO = subdirFO.createData("subfile");
+ assertEquals("Wrong number of events when data in sub folder created.", 1, fcl.check(EventType.DATA_CREATED));
+ FileObject subsubfileFO = subsubdirFO.createData("subsubfile");
+ assertEquals("Wrong number of events when data in sub sub folder created.", 1, fcl.check(EventType.DATA_CREATED));
+
+ // modify
+ file1FO.getOutputStream().close();
+ assertEquals("Wrong number of events when file folder modified.", 1, fcl.check(EventType.CHANGED));
+ subfileFO.getOutputStream().close();
+ assertEquals("Wrong number of events when file in sub folder modified.", 1, fcl.check(EventType.CHANGED));
+ subsubfileFO.getOutputStream().close();
+ assertEquals("Wrong number of events when file in sub sub folder modified.", 1, fcl.check(EventType.CHANGED));
+
+ // delete
+ file1FO.delete();
+ assertEquals("Wrong number of events when child file deleted.", 1, fcl.check(EventType.DELETED));
+ subsubfileFO.delete();
+ assertEquals("Wrong number of events when child file in sub sub folder deleted.", 1, fcl.check(EventType.DELETED));
+ subsubdirFO.delete();
+ assertEquals("Wrong number of events when sub sub folder deleted.", 1, fcl.check(EventType.DELETED));
+ subfileFO.delete();
+ assertEquals("Wrong number of events when child file in sub folder deleted.", 1, fcl.check(EventType.DELETED));
+ subdirFO.delete();
+ assertEquals("Wrong number of events when sub folder deleted.", 1, fcl.check(EventType.DELETED));
+
+ // atomic action
+ FileUtil.runAtomicAction(new Runnable() {
+
+ public void run() {
+ try {
+ FileObject rootFO = FileUtil.toFileObject(rootF);
+ rootFO.createFolder("fakedir"); // no events
+ rootFO.setAttribute("fake", "fake"); // no events
+ rootFO.createData("fakefile"); // no events
+ FileObject dirFO = FileUtil.toFileObject(dirF);
+ dirFO.createData("file1");
+ FileObject subdirFO = dirFO.createFolder("subdir");
+ subdirFO.createData("subfile");
+ FileObject subsubdirFO = subdirFO.createFolder("subsubdir");
+ subsubdirFO.createData("subsubfile");
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+ });
+ // TODO - should be 3
+ assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.DATA_CREATED));
+ // TODO - should be 2
+ assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.FOLDER_CREATED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // rename
+ file1FO = dirFO.getFileObject("file1");
+ subdirFO = dirFO.getFileObject("subdir");
+ subfileFO = subdirFO.getFileObject("subfile");
+ subsubdirFO = subdirFO.getFileObject("subsubdir");
+ subsubfileFO = subsubdirFO.getFileObject("subsubfile");
+ fcl.clearAll();
+ FileLock lock = file1FO.lock();
+ file1FO.rename(lock, "file1Renamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when child file renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = subfileFO.lock();
+ subfileFO.rename(lock, "subfileRenamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when child file in sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = subsubfileFO.lock();
+ subsubfileFO.rename(lock, "subsubfileRenamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when child file in sub sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = subsubdirFO.lock();
+ subsubdirFO.rename(lock, "subsubdirRenamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when sub sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = subdirFO.lock();
+ subdirFO.rename(lock, "subdirRenamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = dirFO.lock();
+ dirFO.rename(lock, "dirRenamed", null);
+ lock.releaseLock();
+ assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ lock = dirFO.lock();
+ dirFO.rename(lock, "dir", null);
+ lock.releaseLock();
+ /* According to jskrivanek in http://www.netbeans.org/nonav/issues/showattachment.cgi/86910/X.diff, the rename back does not need to
+ * fire an event. Instead the support delivers FOLDER_CREATED event:
+ assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.RENAMED));
+ assertEquals("Wrong number of events when sub folder renamed.", 1, fcl.check(EventType.FOLDER_CREATED));
+ fcl.printAll();
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+ */
+ // cleanup after rename
+ dirFO.getFileObject("file1Renamed").delete();
+ dirFO.getFileObject("subdirRenamed").delete();
+ fcl.clearAll();
+
+ // disk changes
+ Thread.sleep(1000); // give OS same time
+ assertTrue(subsubdirF.mkdirs());
+ assertTrue(fileF.createNewFile());
+ assertTrue(subfileF.createNewFile());
+ assertTrue(subsubfileF.createNewFile());
+ FileUtil.refreshAll();
+ // TODO - should be 3
+ assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
+ // TODO - should be 2
+ assertEquals("Wrong number of events when folder created.", 1, fcl.check(EventType.FOLDER_CREATED));
+ // TODO - should be 0
+ assertEquals("Wrong number of Attribute change events (see #129178).", 1, fcl.check(EventType.ATTRIBUTE_CHANGED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ Thread.sleep(1000); // make sure timestamp changes
+ new FileOutputStream(subsubfileF).close();
+ new FileOutputStream(subfileF).close();
+ new FileOutputStream(fileF).close();
+ FileUtil.refreshAll();
+ assertEquals("Wrong number of events when file was modified.", 3, fcl.check(EventType.CHANGED));
+ assertEquals("Wrong number of Attribute change events (see #129178).", 7, fcl.check(EventType.ATTRIBUTE_CHANGED));
+
+ assertTrue(subsubfileF.delete());
+ assertTrue(subsubdirF.delete());
+ assertTrue(subfileF.delete());
+ assertTrue(subdirF.delete());
+ assertTrue(fileF.delete());
+ FileUtil.refreshAll();
+ assertEquals("Wrong number of events when file deleted.", 5, fcl.check(EventType.DELETED));
+
+ // delete folder itself
+ dirFO.delete();
+ assertEquals("Wrong number of events when folder deleted.", 1, fcl.check(EventType.DELETED));
+ }
+
+ /** Tests recursive FileChangeListener on File.
+ * @see FileUtil#addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File)
+ */
+ public void testAddRecursiveListenerToFile() throws IOException, InterruptedException {
+ clearWorkDir();
+ File rootF = getWorkDir();
+ File dirF = new File(rootF, "dir");
+ File fileF = new File(dirF, "file");
+
+ // adding listeners
+ TestFileChangeListener fcl = new TestFileChangeListener();
+ FileUtil.addRecursiveListener(fcl, fileF);
+ try {
+ FileUtil.addRecursiveListener(fcl, fileF);
+ fail("Should not be possible to add listener for the same path.");
+ } catch (IllegalArgumentException iae) {
+ // ok
+ }
+ TestFileChangeListener fcl2 = new TestFileChangeListener();
+ try {
+ FileUtil.removeRecursiveListener(fcl2, fileF);
+ fail("Should not be possible to remove listener which is not registered.");
+ } catch (IllegalArgumentException iae) {
+ // ok
+ }
+ FileUtil.addRecursiveListener(fcl2, fileF);
+
+ // creation
+ final FileObject rootFO = FileUtil.toFileObject(rootF);
+ FileObject dirFO = rootFO.createFolder("dir");
+ assertEquals("Event fired when just parent dir created.", 0, fcl.checkAll());
+ FileObject fileFO = dirFO.createData("file");
+ assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
+ assertEquals("Wrong number of events when file was created.", 1, fcl2.check(EventType.DATA_CREATED));
+ FileObject fileAFO = dirFO.createData("fileA");
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // remove listener
+ FileUtil.removeRecursiveListener(fcl2, fileF);
+
+ // modification
+ fileFO.getOutputStream().close();
+ fileFO.getOutputStream().close();
+ assertEquals("Wrong number of events when file was modified.", 2, fcl.check(EventType.CHANGED));
+ // no event fired when other file modified
+ fileAFO.getOutputStream().close();
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // deletion
+ fileFO.delete();
+ assertEquals("Wrong number of events when file deleted.", 1, fcl.check(EventType.DELETED));
+ dirFO.delete();
+ assertEquals("Event fired when parent dir deleted and file already deleted.", 0, fcl.checkAll());
+ dirFO = rootFO.createFolder("dir");
+ fileFO = dirFO.createData("file");
+ assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
+ dirFO.delete();
+ assertEquals("Wrong number of events when parent dir deleted.", 1, fcl.check(EventType.DELETED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // atomic action
+ FileUtil.runAtomicAction(new Runnable() {
+
+ public void run() {
+ FileObject dirFO;
+ try {
+ dirFO = rootFO.createFolder("dir");
+ rootFO.createFolder("fakedir");
+ rootFO.setAttribute("fake", "fake");
+ rootFO.createData("fakefile");
+ dirFO.createData("file");
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+ });
+ assertEquals("Wrong number of events fired when file was created in atomic action.", 1, fcl.check(EventType.DATA_CREATED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // rename
+ dirFO = FileUtil.toFileObject(dirF);
+ fileFO = FileUtil.toFileObject(fileF);
+ FileLock lock = dirFO.lock();
+ dirFO.rename(lock, "dirRenamed", null);
+ lock.releaseLock();
+ assertEquals("Event fired when parent dir renamed.", 0, fcl.checkAll());
+ lock = fileFO.lock();
+ fileFO.rename(lock, "fileRenamed", null);
+ lock.releaseLock();
+ assertEquals("Renamed event not fired.", 1, fcl.check(EventType.RENAMED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // disk changes
+ dirF.mkdir();
+ assertTrue(fileF.createNewFile());
+ FileUtil.refreshAll();
+ assertEquals("Wrong number of events when file was created.", 1, fcl.check(EventType.DATA_CREATED));
+ Thread.sleep(1000); // make sure timestamp changes
+ new FileOutputStream(fileF).close();
+ FileUtil.refreshAll();
+ assertEquals("Wrong number of events when file was modified.", 1, fcl.check(EventType.CHANGED));
+ assertEquals("Attribute change event not fired (see #129178).", 2, fcl.check(EventType.ATTRIBUTE_CHANGED));
+ fileF.delete();
+ dirF.delete();
+ FileUtil.refreshAll();
+ assertEquals("Wrong number of events when file deleted.", 1, fcl.check(EventType.DELETED));
+ assertEquals("No other events should be fired.", 0, fcl.checkAll());
+
+ // disk changes #66444
+ for (int cntr = 0; cntr < 50; cntr++) {
+ dirF.mkdir();
+ new FileOutputStream(fileF).close();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file was created; count=" + cntr, 1, fcl.check(EventType.DATA_CREATED));
+ fileF.delete();
+ dirF.delete();
+ FileUtil.refreshAll();
+ assertEquals("Event not fired when file deleted; count=" + cntr, 1, fcl.check(EventType.DELETED));
+ }
+
+ // removed listener
+ assertEquals("No other events should be fired in removed listener.", 0, fcl2.checkAll());
+
+ // weakness
+ WeakReference ref = new WeakReference(fcl);
+ fcl = null;
+ assertGC("FileChangeListener not collected.", ref);
+ }
+
private static enum EventType {
DATA_CREATED, FOLDER_CREATED, DELETED, CHANGED, RENAMED, ATTRIBUTE_CHANGED
};
private static class TestFileChangeListener implements FileChangeListener {
+ boolean disabled;
private final Map> type2Event = new HashMap>();
@@ -356,26 +807,32 @@
}
public void fileFolderCreated(FileEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.FOLDER_CREATED).add(fe);
}
public void fileDataCreated(FileEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.DATA_CREATED).add(fe);
}
public void fileChanged(FileEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.CHANGED).add(fe);
}
public void fileDeleted(FileEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.DELETED).add(fe);
}
public void fileRenamed(FileRenameEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.RENAMED).add(fe);
}
public void fileAttributeChanged(FileAttributeEvent fe) {
+ assertFalse("No changes expected", disabled);
type2Event.get(EventType.ATTRIBUTE_CHANGED).add(fe);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ 70eda1b69061 Wed Sep 16 16:32:49 2009 +0200
@@ -0,0 +1,340 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2007 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]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+package org.netbeans.modules.masterfs.filebasedfs.fileobjects;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.junit.NbTestCase;
+import org.openide.filesystems.FileAttributeEvent;
+import org.openide.filesystems.FileChangeAdapter;
+import org.openide.filesystems.FileChangeListener;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileRenameEvent;
+import org.openide.filesystems.FileUtil;
+
+public class ExternalTouchTest extends NbTestCase {
+ private Logger LOG;
+ private FileObject testFolder;
+
+ public ExternalTouchTest(String testName) {
+ super(testName);
+ }
+
+ @Override
+ protected Level logLevel() {
+ return Level.FINE;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ clearWorkDir();
+
+ LOG = Logger.getLogger("test." + getName());
+ Logger.getLogger("org.openide.util.Mutex").setUseParentHandlers(false);
+
+ File dir = new File(getWorkDir(), "test");
+ dir.mkdirs();
+ testFolder = FileUtil.toFileObject(dir);
+ assertNotNull("Test folder created", testFolder);
+
+ }
+
+ public void testChangeInChildrenNoticed() throws Exception {
+ long lm = System.currentTimeMillis();
+ FileObject fileObject1 = testFolder.createData("fileObject1");
+ assertNotNull("Just to initialize the stamp", lm);
+ FileObject[] arr = testFolder.getChildren();
+ assertEquals("One child", 1, arr.length);
+ assertEquals("Right child", fileObject1, arr[0]);
+
+ File file = FileUtil.toFile(fileObject1);
+ assertNotNull("File found", file);
+ Reference ref = new WeakReference(fileObject1);
+ arr = null;
+ fileObject1 = null;
+ assertGC("File Object can disappear", ref);
+
+
+ class L extends FileChangeAdapter {
+ int cnt;
+ FileEvent event;
+
+ @Override
+ public void fileChanged(FileEvent fe) {
+ LOG.info("file change " + fe.getFile());
+ cnt++;
+ event = fe;
+ }
+ }
+ L listener = new L();
+ testFolder.addRecursiveListener(listener);
+
+ Thread.sleep(1000);
+
+ FileOutputStream os = new FileOutputStream(file);
+ os.write(10);
+ os.close();
+
+ if (lm > file.lastModified() - 50) {
+ fail("New modification time shall be at last 50ms after the original one: " + (file.lastModified() - lm));
+ }
+
+ testFolder.refresh();
+
+ assertEquals("Change notified", 1, listener.cnt);
+ assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
+ assertEquals("Right source", file.getParentFile(), FileUtil.toFile((FileObject)listener.event.getSource()));
+ }
+ public void testNewChildNoticed() throws Exception {
+ FileObject fileObject1 = testFolder.createData("fileObject1");
+ FileObject[] arr = testFolder.getChildren();
+ assertEquals("One child", 1, arr.length);
+ assertEquals("Right child", fileObject1, arr[0]);
+
+ File file = FileUtil.toFile(fileObject1);
+ assertNotNull("File found", file);
+ arr = null;
+ fileObject1 = null;
+ Reference ref = new WeakReference(fileObject1);
+ assertGC("File Object can disappear", ref);
+
+ Thread.sleep(100);
+
+ class L extends FileChangeAdapter {
+ int cnt;
+ FileEvent event;
+
+ @Override
+ public void fileDataCreated(FileEvent fe) {
+ cnt++;
+ event = fe;
+ }
+
+ }
+ L listener = new L();
+ testFolder.addRecursiveListener(listener);
+
+ File nfile = new File(file.getParentFile(), "new.txt");
+ nfile.createNewFile();
+
+ testFolder.refresh();
+
+ assertEquals("Change notified", 1, listener.cnt);
+ assertEquals("Right file", nfile, FileUtil.toFile(listener.event.getFile()));
+ }
+ public void testDeleteOfAChildNoticed() throws Exception {
+ FileObject fileObject1 = testFolder.createData("fileObject1");
+ FileObject[] arr = testFolder.getChildren();
+ assertEquals("One child", 1, arr.length);
+ assertEquals("Right child", fileObject1, arr[0]);
+
+ File file = FileUtil.toFile(fileObject1);
+ assertNotNull("File found", file);
+ arr = null;
+ fileObject1 = null;
+ Reference ref = new WeakReference(fileObject1);
+ assertGC("File Object can disappear", ref);
+
+ Thread.sleep(100);
+
+ class L extends FileChangeAdapter {
+ int cnt;
+ FileEvent event;
+
+ @Override
+ public void fileDeleted(FileEvent fe) {
+ cnt++;
+ event = fe;
+ }
+
+ }
+ L listener = new L();
+ testFolder.addRecursiveListener(listener);
+
+ file.delete();
+
+ testFolder.refresh();
+
+ assertEquals("Change notified", 1, listener.cnt);
+ assertEquals("Right file", file, FileUtil.toFile(listener.event.getFile()));
+ }
+
+ public void testRecursiveListener() throws Exception {
+ FileObject sub;
+ File fobj;
+ File fsub;
+ {
+ FileObject obj = FileUtil.createData(testFolder, "my/sub/children/children.java");
+ fobj = FileUtil.toFile(obj);
+ assertNotNull("File found", fobj);
+ sub = obj.getParent().getParent();
+ fsub = FileUtil.toFile(sub);
+
+ WeakReference