diff -r 98de2b9566b8 openide.filesystems/apichanges.xml --- a/openide.filesystems/apichanges.xml Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/apichanges.xml Wed Aug 19 16:51:28 2009 +0200 @@ -46,6 +46,23 @@ Filesystems API + + + Support for processing batch events + + + + + +

+ + FileEvent.runWhenDeliveryOver(Runnable) method added to support + easier processing of batch sets of events. +

+
+ + +
Allow modules to dynamically add/remove layer content diff -r 98de2b9566b8 openide.filesystems/nbproject/project.properties --- a/openide.filesystems/nbproject/project.properties Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/nbproject/project.properties Wed Aug 19 16:51:28 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 98de2b9566b8 openide.filesystems/src/org/openide/filesystems/EventControl.java --- a/openide.filesystems/src/org/openide/filesystems/EventControl.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/EventControl.java Wed Aug 19 16:51:28 2009 +0200 @@ -42,7 +42,9 @@ package org.openide.filesystems; import java.io.IOException; +import java.util.LinkedHashSet; import java.util.LinkedList; +import java.util.Set; /** * @author rmatous @@ -197,15 +199,18 @@ private LinkedList invokeDispatchers(boolean priority, LinkedList reqQueueCopy) { LinkedList newEnum = new LinkedList(); - + Set postNotify = new LinkedHashSet(); while ((reqQueueCopy != null) && !reqQueueCopy.isEmpty()) { FileSystem.EventDispatcher r = reqQueueCopy.removeFirst(); - r.dispatch(priority); + r.dispatch(priority, postNotify); if (priority) { newEnum.add(r); } } + for (Runnable r : postNotify) { + r.run(); + } return newEnum; } @@ -214,7 +219,7 @@ private synchronized boolean postponeFiring(FileSystem.EventDispatcher disp) { if (priorityRequests == 0) { disp.setAtomicActionLink(currentAtomAction); - disp.dispatch(true); + disp.dispatch(true, null); } if (requestsQueue != null) { diff -r 98de2b9566b8 openide.filesystems/src/org/openide/filesystems/FCLSupport.java --- a/openide.filesystems/src/org/openide/filesystems/FCLSupport.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FCLSupport.java Wed Aug 19 16:51:28 2009 +0200 @@ -40,8 +40,11 @@ */ package org.openide.filesystems; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Queue; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import org.openide.util.Exceptions; import org.openide.util.RequestProcessor; @@ -77,7 +80,7 @@ } } - final void dispatchEvent(FileEvent fe, Op operation) { + final void dispatchEvent(FileEvent fe, Op operation, Collection postNotify) { List fcls; synchronized (this) { @@ -89,14 +92,14 @@ } for (FileChangeListener l : fcls) { - dispatchEvent(l, fe, operation); + dispatchEvent(l, fe, operation, postNotify); } } - final static void dispatchEvent(final FileChangeListener fcl, final FileEvent fe, final Op operation) { + final static void dispatchEvent(final FileChangeListener fcl, final FileEvent fe, final Op operation, Collection postNotify) { boolean async = fe.isAsynchronous(); DispatchEventWrapper dw = new DispatchEventWrapper(fcl, fe, operation); - dw.dispatchEvent(async); + dw.dispatchEvent(async, postNotify); } /** @return true if there is a listener @@ -114,17 +117,18 @@ this.fe =fe; this.operation =operation; } - void dispatchEvent(boolean async) { + void dispatchEvent(boolean async, Collection postNotify) { if (async) { q.offer(this); task.schedule(300); } else { - dispatchEventImpl(fcl, fe, operation); + dispatchEventImpl(fcl, fe, operation, postNotify); } } - private void dispatchEventImpl(FileChangeListener fcl, FileEvent fe, Op operation) { + private void dispatchEventImpl(FileChangeListener fcl, FileEvent fe, Op operation, Collection postNotify) { try { + fe.setPostNotify(postNotify); switch (operation) { case DATA_CREATED: fcl.fileDataCreated(fe); @@ -149,6 +153,8 @@ } } catch (RuntimeException x) { Exceptions.printStackTrace(x); + } finally { + fe.setPostNotify(null); } } @@ -158,10 +164,14 @@ private static RequestProcessor.Task task = RP.create(new Runnable() { public void run() { DispatchEventWrapper dw = q.poll(); + Set post = new HashSet(); while (dw != null) { - dw.dispatchEvent(false); + dw.dispatchEvent(false, post); dw = q.poll(); } + for (Runnable r : post) { + r.run(); + } } }); } diff -r 98de2b9566b8 openide.filesystems/src/org/openide/filesystems/FileEvent.java --- a/openide.filesystems/src/org/openide/filesystems/FileEvent.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileEvent.java Wed Aug 19 16:51:28 2009 +0200 @@ -41,6 +41,7 @@ package org.openide.filesystems; +import java.util.Collection; import java.util.Date; import java.util.EventObject; @@ -66,6 +67,7 @@ /***/ private EventControl.AtomicActionLink atomActionID; + private transient Collection postNotify; /** Creates new FileEvent. The FileObject where the action occurred * is assumed to be the same as the source object. @@ -135,6 +137,33 @@ return expected; } + /** Support for batch processing of events. In some situations + * you may want to delay processing of received events until the last + * known one is delivered. For example if there is a lot of operations + * done inside {@link FileUtil#runAtomicAction(java.lang.Runnable)} + * action, there can be valid reason to do the processing only after + * all of them are delivered. In such situation attach your {@link Runnable} + * to provided event. Such {@link Runnable} is then guaranteed to be called once. + * Either immediately (if there is no batch delivery in progress) + * or some time later (if there is a batch delivery). You can attach + * single runnable multiple times, even to different events in the + * same batch section and its {@link Runnable#run()} method will still be + * called just once. {@link Object#equals(java.lang.Object)} is used + * to check equality of two {@link Runnable}s. + * + * @since 7.24 + * @param r the runnable to execute when batch event deliver is over + * (can be even executed immediately) + */ + public final void runWhenDeliveryOver(Runnable r) { + Collection to = postNotify; + if (to != null) { + to.add(r); + } else { + r.run(); + } + } + @Override public String toString() { StringBuilder b = new StringBuilder(); @@ -198,5 +227,11 @@ return false; } + + void setPostNotify(Collection runs) { + // cannot try to set the postNotify field twiced + assert postNotify == null || runs == null; + this.postNotify = runs; + } } diff -r 98de2b9566b8 openide.filesystems/src/org/openide/filesystems/FileObject.java --- a/openide.filesystems/src/org/openide/filesystems/FileObject.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileObject.java Wed Aug 19 16:51:28 2009 +0200 @@ -996,7 +996,7 @@ /** @param onlyPriority if true then invokes only priority listeners * else all listeners are invoked. */ - protected void dispatch(boolean onlyPriority) { + protected void dispatch(boolean onlyPriority, Collection postNotify) { if (this.op == null) { this.op = fe.getFile().isFolder() ? FCLSupport.Op.FOLDER_CREATED : FCLSupport.Op.DATA_CREATED; } @@ -1011,7 +1011,7 @@ continue; } - FCLSupport.dispatchEvent(fcl, fe, op); + FCLSupport.dispatchEvent(fcl, fe, op, postNotify); } if (onlyPriority) { @@ -1049,14 +1049,14 @@ } if (fs != null && fsList != null) { for (FileChangeListener fcl : fsList) { - FCLSupport.dispatchEvent(fcl, fe, op); + FCLSupport.dispatchEvent(fcl, fe, op, postNotify); } } if (rep != null && repList != null) { for (FileChangeListener fcl : repList) { - FCLSupport.dispatchEvent(fcl, fe, op); + FCLSupport.dispatchEvent(fcl, fe, op, postNotify); } } } diff -r 98de2b9566b8 openide.filesystems/src/org/openide/filesystems/FileSystem.java --- a/openide.filesystems/src/org/openide/filesystems/FileSystem.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileSystem.java Wed Aug 19 16:51:28 2009 +0200 @@ -49,6 +49,7 @@ import java.beans.VetoableChangeListener; import java.io.IOException; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Set; import org.openide.util.Exceptions; @@ -877,13 +878,13 @@ */ static abstract class EventDispatcher extends Object implements Runnable { public final void run() { - dispatch(false); + dispatch(false, null); } /** @param onlyPriority if true then invokes only priority listeners * else all listeners are invoked. */ - protected abstract void dispatch(boolean onlyPriority); + protected abstract void dispatch(boolean onlyPriority, Collection postNotify); /** @param propID */ protected abstract void setAtomicActionLink(EventControl.AtomicActionLink propID); @@ -898,7 +899,7 @@ this.fStatusEvent = fStatusEvent; } - protected void dispatch(boolean onlyPriority) { + protected void dispatch(boolean onlyPriority, Collection postNotify) { if (onlyPriority) { return; } diff -r 98de2b9566b8 openide.filesystems/test/unit/src/org/openide/filesystems/AtomicActionTest.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/AtomicActionTest.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/AtomicActionTest.java Wed Aug 19 16:51:28 2009 +0200 @@ -94,6 +94,7 @@ } }); assertTrue(tcl.deleteNotification); + assertTrue("Notified about end of delivery", tcl.over); tcl.reset(); assertNotNull(FileUtil.createData(root, "data")); @@ -116,16 +117,27 @@ } }); assertTrue(tcl.deleteNotification); + assertTrue("Notified about end of delivery", tcl.over); } - private static class TestChangeListener extends FileChangeAdapter { + private static class TestChangeListener extends FileChangeAdapter + implements Runnable { private boolean deleteNotification; + private boolean over; @Override public void fileDeleted(FileEvent fe) { + assertFalse("Delivery of events is not over yet", over); deleteNotification = true; + fe.runWhenDeliveryOver(this); } public void reset() { deleteNotification = false; + over = false; + } + + public void run() { + assertFalse("Over not set yet", over); + over = true; } } } diff -r 98de2b9566b8 openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Wed Aug 19 16:12:32 2009 +0200 +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Wed Aug 19 16:51:28 2009 +0200 @@ -183,22 +183,34 @@ fail(); } }; + class Once implements Runnable { + int once; + + public void run() { + assertEquals("No calls yet", 0, once); + once++; + } + } + final Once post = new Once(); final FileChangeListener listener1 = new FileChangeAdapter(){ @Override public void fileDataCreated(FileEvent fe) { fold.removeFileChangeListener(noFileDataCreatedListener); fold.addFileChangeListener(noFileDataCreatedListener); + fe.runWhenDeliveryOver(post); } }; - + assertEquals("Not called yet", 0, post.once); try { fold.getFileSystem().addFileChangeListener(listener1); fold.getFileSystem().runAtomicAction(new FileSystem.AtomicAction(){ public void run() throws java.io.IOException { fold.createData("file1"); fold.createData("file2"); + assertEquals("Called not", 0, post.once); } }); + assertEquals("Called once", 1, post.once); } finally { fold.getFileSystem().removeFileChangeListener(listener1); fold.removeFileChangeListener(noFileDataCreatedListener); @@ -989,21 +1001,51 @@ /** Test of fireFileRenamedEvent method, of class org.openide.filesystems.FileObject. */ public void testFireFileRenamedEvent_FS() { checkSetUp(); - FileObject fo = getTestFile1(root); + final FileObject fo = getTestFile1(root); registerDefaultListener(testedFS); - FileLock lock = null; + class Immediate extends FileChangeAdapter implements Runnable { + int cnt; + FileRenameEvent fe; + @Override + public void fileRenamed(FileRenameEvent fe) { + int prev = cnt; + fe.runWhenDeliveryOver(this); + assertEquals("run() not called immediately", prev, cnt); + this.fe = fe; + } + public void run() { + cnt++; + } + + void testCallOutsideOfDeliverySystem() { + assertNotNull("There shall be an event", fe); + int prev = cnt; + fe.runWhenDeliveryOver(this); + assertEquals("run() called immediately", prev + 1, cnt); + } + } + Immediate immediately = new Immediate(); + fo.addFileChangeListener(immediately); + + final FileLock[] lock = new FileLock[1]; try { - lock = fo.lock(); - fo.rename(lock,fo.getName()+"X",fo.getExt()+"X"); + lock[0] = fo.lock(); + FileUtil.runAtomicAction(new FileSystem.AtomicAction() { + public void run() throws IOException { + fo.rename(lock[0],fo.getName()+"X",fo.getExt()+"X"); + } + }); } catch (IOException iex) { fsAssert("FileObject could not be renamed. So there was expected fs or fo are read-only", fs.isReadOnly() || root.isReadOnly()); fileRenamedAssert("fs or fo is read-only. So no event should be fired",0); return; } finally { - if (lock != null) lock.releaseLock(); + if (lock[0] != null) lock[0].releaseLock(); } + assertEquals("One call", 1, immediately.cnt); + immediately.testCallOutsideOfDeliverySystem(); fileRenamedAssert("",1); fileDataCreatedAssert("fireFileDataCreatedEvent should not be fired ",0);