diff -r 4724a416ccd5 openide.filesystems/apichanges.xml --- a/openide.filesystems/apichanges.xml Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/apichanges.xml Fri Sep 03 16:23:47 2010 +0200 @@ -49,6 +49,23 @@ Filesystems API + + + FileObject.createAndOpen + + + + + +

+ Create a file and write its content (almost) atomically using the new + FileObject.createAndOpen + method. +

+
+ + +
Interruptable addRecursiveListener diff -r 4724a416ccd5 openide.filesystems/manifest.mf --- a/openide.filesystems/manifest.mf Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/manifest.mf Fri Sep 03 16:23:47 2010 +0200 @@ -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.39 +OpenIDE-Module-Specification-Version: 7.40 diff -r 4724a416ccd5 openide.filesystems/src/org/openide/filesystems/FileEvent.java --- a/openide.filesystems/src/org/openide/filesystems/FileEvent.java Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileEvent.java Fri Sep 03 16:23:47 2010 +0200 @@ -226,6 +226,11 @@ if (atomicAction != null && atomicAction.getClass().getName().indexOf("AsyncRefreshAtomicAction") != -1) { return true; } + if (atomicAction instanceof FileSystem.AsyncAtomicAction) { + if (((FileSystem.AsyncAtomicAction)atomicAction).isAsynchronous()) { + return true; + } + } currentPropID = currentPropID.getPreviousLink(); } diff -r 4724a416ccd5 openide.filesystems/src/org/openide/filesystems/FileObject.java --- a/openide.filesystems/src/org/openide/filesystems/FileObject.java Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileObject.java Fri Sep 03 16:23:47 2010 +0200 @@ -883,6 +883,53 @@ public FileObject createData(String name) throws IOException { return createData(name, ""); // NOI18N } + + /** + * Creates new file in this folder and immediately opens it for writing. + * This method prevents possible race condition which can happen + * when using the {@link #createData(java.lang.String)} method. + * Using {@link #createData(java.lang.String) that method} makes it + * possible for someone to read the content of the newly created + * file before its content is written. This method does its best to eliminate + * such race condition. + * + *

+ * This method usually delivers both, {@link FileChangeListener#fileDataCreated(org.openide.filesystems.FileEvent) data created} + * and {@link FileChangeListener#fileChanged(org.openide.filesystems.FileEvent) changed} + * events. Preferably it delivers them asynchronously. The assumption + * is that the file will be (at least partially) written before + * the listeners start to process the first event. The safety is additionally + * ensured by mutual exclusion between output and input streams + * for the same file (any call to {@link #getInputStream()} will be blocked + * for at least two seconds if there is existing open output stream). + * If you finish writing the content of your file in those two seconds, + * you can be sure, nobody will have read its content yet. + *

+ * + * @param name name of file to create with its extension + * @return output stream to use to write content of the file + * @throws IOException if the file cannot be created (e.g. already exists), or if this is not a folder + * @since 7.40 + */ + public OutputStream createAndOpen(final String name) throws IOException { + class R implements FileSystem.AsyncAtomicAction { + OutputStream os; + + @Override + public void run() throws IOException { + FileObject fo = createData(name); + os = fo.getOutputStream(); + } + + @Override + public boolean isAsynchronous() { + return true; + } + } + R r = new R(); + getFileSystem().runAtomicAction(r); + return r.os; + } /** Test whether this file can be written to or not. *

diff -r 4724a416ccd5 openide.filesystems/src/org/openide/filesystems/FileSystem.java --- a/openide.filesystems/src/org/openide/filesystems/FileSystem.java Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/src/org/openide/filesystems/FileSystem.java Fri Sep 03 16:23:47 2010 +0200 @@ -841,6 +841,10 @@ */ public void run() throws IOException; } + + static interface AsyncAtomicAction extends AtomicAction { + boolean isAsynchronous(); + } /** Allows a filesystem to annotate a group of files (typically comprising a data object) with additional markers. *

This could be useful, for diff -r 4724a416ccd5 openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Fri Sep 03 15:30:16 2010 +0200 +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/FileObjectTestHid.java Fri Sep 03 16:23:47 2010 +0200 @@ -324,6 +324,76 @@ value.equals((String)fo3.getAttribute(attrName)) ); fileDataCreatedAssert("parent should fire fileDataCreated",1); } + + public void testCreateAndOpen() throws Exception { + checkSetUp(); + FileObject f; + try { + f = getTestFolder1(root).createFolder("createAndOpen"); + } catch (IOException iex) { + fsAssert("If read only FS, then OK to fail", + fs.isReadOnly()); + return; + } + final FileObject fold = f; + + class L extends FileChangeAdapter { + String dataCreated; + String changed; + + @Override + public synchronized void fileDataCreated(FileEvent fe) { + try { + FileObject ch = fold.getFileObject("child.txt"); + assertNotNull("Child found", ch); + + dataCreated = ch.asText(); + notifyAll(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public synchronized void fileChanged(FileEvent fe) { + try { + FileObject ch = fold.getFileObject("child.txt"); + assertNotNull("Child found", ch); + + changed = ch.asText(); + notifyAll(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + + public synchronized void waitEvents() throws InterruptedException { + for (int i = 0; i < 100; i++) { + if (changed != null && dataCreated != null) { + break; + } + wait(100); + } + } + } + + L l = new L(); + fold.addFileChangeListener(l); + assertEquals("No children", 0, fold.getChildren().length); + + OutputStream os = fold.createAndOpen("child.txt"); + os.write("Ahoj".getBytes()); + os.close(); + + FileObject ch = fold.getFileObject("child.txt"); + assertNotNull("Child found", ch); + assertEquals("Right content now", "Ahoj", ch.asText()); + + l.waitEvents(); + assertEquals("Right content when changed", "Ahoj", l.changed); + assertEquals("Right content when created", "Ahoj", l.dataCreated); + } /** Test of copy method, of class org.openide.filesystems.FileObject. */ public void testCopy1_FS() throws IOException {