# HG changeset patch # Parent cf1bbd7899119a0651f0a5ac0c5c2b65a422e1d6 diff --git a/openide.filesystems/apichanges.xml b/openide.filesystems/apichanges.xml --- a/openide.filesystems/apichanges.xml +++ b/openide.filesystems/apichanges.xml @@ -49,6 +49,25 @@ Filesystems API + + + Allowed to reveal deleted files, or original files overriden by writable layer + + + + + +

+ Files which have been deleted can be obtained by folder.getAttribute("revealEntries"). + See MultiFileSystem + javadoc for details +

+

+ Warning: stability of this feature is development +

+
+ +
New method FileChooserBuilder.setAcceptAllFileFilterUsed. diff --git a/openide.filesystems/arch.xml b/openide.filesystems/arch.xml --- a/openide.filesystems/arch.xml +++ b/openide.filesystems/arch.xml @@ -361,7 +361,17 @@ --> - See documentation of FileSystem. +

+ See documentation of FileSystem. +

+
+ +

+ The MultiFileSystem defines some pseudo attributes on files and folders, + which provide rollback features. Please see MultiFileSystem class javadoc + for more information. +

diff --git a/openide.filesystems/manifest.mf b/openide.filesystems/manifest.mf --- a/openide.filesystems/manifest.mf +++ b/openide.filesystems/manifest.mf @@ -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: 8.4 +OpenIDE-Module-Specification-Version: 8.5 diff --git a/openide.filesystems/src/org/openide/filesystems/MultiFileObject.java b/openide.filesystems/src/org/openide/filesystems/MultiFileObject.java --- a/openide.filesystems/src/org/openide/filesystems/MultiFileObject.java +++ b/openide.filesystems/src/org/openide/filesystems/MultiFileObject.java @@ -45,6 +45,7 @@ package org.openide.filesystems; import java.io.Externalizable; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; @@ -52,8 +53,11 @@ import java.io.OutputStream; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -64,9 +68,12 @@ import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; +import org.openide.util.Enumerations; import org.openide.util.Exceptions; +import org.openide.util.Lookup; import org.openide.util.NbBundle; /** Implementation of the file object for multi file system. @@ -843,6 +850,13 @@ Number maxWeight = 0; Object attr = null; FileSystem writable = getMultiFileSystem().writableLayer(path); + + boolean revealEntries = isFolder() && "revealEntries".equals(attrName) && + writable != null && !writable.isReadOnly(); //NOI18N + + if (revealEntries) { + return collectRevealedFiles(); + } // boolean isLoaderAttr = /* DataObject.EA_ASSIGNED_LOADER */ "NetBeansAttrAssignedLoader".equals (attrName); // NOI18N for (int i = 0; i < systems.length; i++) { @@ -874,7 +888,7 @@ maxWeight = weight; } } - } + } if (prefixattr != null) { if ( @@ -1806,4 +1820,426 @@ return attribName; } } + + /** + * Revealed FileObject represents a FileObject which has been either overriden by + * the writable layer, or masked out by the layer. It delegates to the original object, + * and its Callable mixing interface allows to revert the change by removing the + * override file. + */ + private final class RevealedFileObject extends FileObject implements Callable { + /** + * The masking file. It may be either _hidden file, or a replacing + * file on the writable layer + */ + private final FileObject maskFile; + + /** + * Original file on lower layer + */ + private final FileObject delegate; + + public RevealedFileObject(FileObject parent, FileObject delegate) { + this.maskFile = parent; + this.delegate = delegate; + } + + @Override + public Object getAttribute(String attrName) { + return delegate.getAttribute(attrName); + } + + /** + * Return the revived file from the MFS + * @return the revived file, if the operation succeeds. + * @throws IOException from the deletion of the masking file. + */ + @Override + public FileObject call() throws IOException { + if (maskFile.isValid()) { + maskFile.delete(); + } + return MultiFileObject.this.getFileObject(maskFile.getNameExt()); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getExt() { + return delegate.getExt(); + } + + @Override + public void rename(FileLock lock, String name, String ext) throws IOException { + throw new IOException("Unsupported oepration"); // NOI18N + } + + @Override + public FileObject copy(FileObject target, String name, String ext) throws IOException { + return delegate.copy(target, name, ext); + } + + @Override + public FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException { + throw new IOException("Unsupported oepration"); // NOI18N + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public String getPath() { + return delegate.getPath(); + } + + @Override + public String getPackageNameExt(char separatorChar, char extSepChar) { + return delegate.getPackageNameExt(separatorChar, extSepChar); + } + + @Override + public String getPackageName(char separatorChar) { + return delegate.getPackageName(separatorChar); + } + + @Override + public String getNameExt() { + return delegate.getNameExt(); + } + + @Override + public FileSystem getFileSystem() throws FileStateInvalidException { + return MultiFileObject.this.getFileSystem(); + } + + @Override + public FileObject getParent() { + return MultiFileObject.this; + } + + @Override + public boolean isFolder() { + return delegate.isFolder(); + } + + @Override + public Date lastModified() { + return delegate.lastModified(); + } + + @Override + public boolean isRoot() { + return delegate.isRoot(); + } + + @Override + public boolean isData() { + return delegate.isData(); + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public boolean existsExt(String ext) { + return delegate.existsExt(ext); + } + + @Override + public void delete(FileLock lock) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public Lookup getLookup() { + return delegate.getLookup(); + } + + @Override + public void setAttribute(String attrName, Object value) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public Enumeration getAttributes() { + return delegate.getAttributes(); + } + + @Override + boolean isHasExtOverride() { + return delegate.isHasExtOverride(); + } + + @Override + boolean hasExtOverride(String ext) { + return delegate.hasExtOverride(ext); + } + + @Override + public void addFileChangeListener(FileChangeListener fcl) { + delegate.addFileChangeListener(fcl); + } + + @Override + public void removeFileChangeListener(FileChangeListener fcl) { + delegate.removeFileChangeListener(fcl); + } + + @Override + public void addRecursiveListener(FileChangeListener fcl) { + delegate.addRecursiveListener(fcl); + } + + @Override + public void removeRecursiveListener(FileChangeListener fcl) { + delegate.removeRecursiveListener(fcl); + } + + @Override + protected void fireFileDataCreatedEvent(Enumeration en, FileEvent fe) { + } + + @Override + protected void fireFileFolderCreatedEvent(Enumeration en, FileEvent fe) { + } + + @Override + protected void fireFileChangedEvent(Enumeration en, FileEvent fe) { + } + + @Override + protected void fireFileDeletedEvent(Enumeration en, FileEvent fe) { + } + + @Override + protected void fireFileAttributeChangedEvent(Enumeration en, FileAttributeEvent fe) { + } + + @Override + protected void fireFileRenamedEvent(Enumeration en, FileRenameEvent fe) { + } + + @Override + public String getMIMEType() { + return delegate.getMIMEType(); + } + + @Override + public String getMIMEType(String... withinMIMETypes) { + return delegate.getMIMEType(withinMIMETypes); + } + + @Override + public long getSize() { + return delegate.getSize(); + } + + @Override + public InputStream getInputStream() throws FileNotFoundException { + return delegate.getInputStream(); + } + + @Override + public byte[] asBytes() throws IOException { + return delegate.asBytes(); + } + + @Override + public String asText(String encoding) throws IOException { + return delegate.asText(encoding); + } + + @Override + public String asText() throws IOException { + return delegate.asText(); + } + + @Override + public List asLines() throws IOException { + return delegate.asLines(); + } + + @Override + public List asLines(String encoding) throws IOException { + return delegate.asLines(encoding); + } + + @Override + public OutputStream getOutputStream(FileLock lock) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public FileLock lock() throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public boolean isLocked() { + return delegate.isLocked(); + } + + @Override + public void setImportant(boolean b) { + } + + @Override + public FileObject[] getChildren() { + return new FileObject[0]; + } + + @Override + public Enumeration getChildren(boolean rec) { + return Enumerations.empty(); + } + + @Override + public Enumeration getFolders(boolean rec) { + return Enumerations.empty(); + } + + @Override + public Enumeration getData(boolean rec) { + return Enumerations.empty(); + } + + @Override + public FileObject getFileObject(String name, String ext) { + if (maskFile.isFolder()) { + return maskFile.getFileObject(name, ext); + } else { + return null; + } + } + + @Override + public FileObject getFileObject(String relativePath) { + if (maskFile.isFolder()) { + return maskFile.getFileObject(relativePath); + } else { + return null; + } + } + + @Override + public FileObject createFolder(String name) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public FileObject createData(String name, String ext) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public FileObject createData(String name) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public OutputStream createAndOpen(String name) throws IOException { + throw new IOException("Unsupported oepration"); //NOI18N + } + + @Override + public boolean isReadOnly() { + return delegate.isReadOnly(); + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public boolean canRead() { + return delegate.canRead(); + } + + @Override + public void refresh(boolean expected) { + delegate.refresh(expected); + } + + @Override + public void refresh() { + delegate.refresh(); + } + + @Override + public boolean isVirtual() { + return delegate.isVirtual(); + } + + public int hashCode() { + return 0xaa ^ delegate.hashCode() << 3; + } + + public boolean equals(Object o) { + if (!(o instanceof RevealedFileObject)) { + return false; + } + RevealedFileObject other = (RevealedFileObject)o; + return other.delegate.equals(delegate) && other.maskFile.equals(maskFile); + } + } + + /** + * The returned collection contains hidden or removed FileObjects, from + * the lower layers. The returned FileObjects are 'special' in that DataObjects + * cannot be created from them, and they cannot be changed. All mutation methods + * will throw IOException - unsupported operation. + */ + private Collection collectRevealedFiles() { + String path = getPath(); + FileSystem[] systems = getMultiFileSystem().getDelegates(); + FileSystem writable = getMultiFileSystem().writableLayer(path); + + if (writable == null) { + return Collections.emptyList(); + } + FileObject writableFolder = writable.findResource(path); + // no overrides as the containing folder does not exist on the writable fs. + if (writableFolder == null) { + return Collections.emptyList(); + } + FileObject[] ch = writableFolder.getChildren(); + int sl = systems.length; + + Collection result = null; + String parentPath = writableFolder.getPath(); + for (FileObject mask : ch) { + String fn = mask.getNameExt(); + if (fn.endsWith(MultiFileSystem.MASK)) { + fn = fn.substring(0, fn.length() - MultiFileSystem.MASK.length()); + } + String p = parentPath + "/" + fn; // NOI18N + for (int i = 0; i < sl; i++) { + if (writable == systems[i]) { + continue; + } + FileObject hiddenFo = systems[i].findResource(p); + if (hiddenFo != null) { + if (result == null) { + result = new ArrayList(ch.length); + } + + result.add(new RevealedFileObject(mask, hiddenFo)); + break; + } + } + } + if (result == null) { + return Collections.emptyList(); + } else { + return result; + } + } } diff --git a/openide.filesystems/src/org/openide/filesystems/MultiFileSystem.java b/openide.filesystems/src/org/openide/filesystems/MultiFileSystem.java --- a/openide.filesystems/src/org/openide/filesystems/MultiFileSystem.java +++ b/openide.filesystems/src/org/openide/filesystems/MultiFileSystem.java @@ -122,6 +122,17 @@ * be able to mask files not provided by their immediate siblings, but by cousins. For this reason, nested * subclasses may call {@link #setPropagateMasks} to make the mask files propagate up one or more nesting levels * and thus remain potent against cousin delegates. + * + *

To support rollback, two pseudo-attribute is defined since 8.5: {@code revealEntries} typed + * as Map<String, FileObject & Callable>. The attribute is available on a folder, and contains information + * on child FileObjects, which have been overriden or masked by the writable layer of the MFS. Map is keyed by + * child name, the value is a FileObject that allows access to the masked child attributes and/or content. + *

+ * The returned FileObjects do not leak its neighbours from the lower layers. The parent, children or siblings are + * returned from the MFS, if they exist. The FileObjects can be also casted to Callable<FileObject>. When + * called, the original version of the file is restored on the MultiFileSystem, and the restored instance is returned + * as result. + *

*/ public class MultiFileSystem extends FileSystem { static final long serialVersionUID = -767493828111559560L; diff --git a/openide.filesystems/test/unit/src/org/openide/filesystems/MultiFileSystemRevealTest.java b/openide.filesystems/test/unit/src/org/openide/filesystems/MultiFileSystemRevealTest.java new file mode 100644 --- /dev/null +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/MultiFileSystemRevealTest.java @@ -0,0 +1,254 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 2012 Sun Microsystems, Inc. + */ +package org.openide.filesystems; + +import java.io.OutputStreamWriter; +import java.util.Collection; +import java.util.concurrent.Callable; +import org.netbeans.junit.NbTestCase; + +/** + * Assures behaviour the 'revealEntries' pseudo-attribute + * @author sdedic + */ +public class MultiFileSystemRevealTest extends NbTestCase { + + public MultiFileSystemRevealTest(String name) { + super(name); + } + + /** + * In the case of no user modifications, empty Collection is provided + * @throws Exception + */ + public void testNoUserModifications() throws Exception { + MultiFileSystem fs = new MultiFileSystem( + TestUtilHid.createLocalFileSystem(getName() + "1", new String[0]), + TestUtilHid.createXMLFileSystem(getName() + "2", new String[] { + "folder/file1", + "folder/file2", + }), + TestUtilHid.createXMLFileSystem(getName() + "3", new String[] { + "folder/file1", + "folder/file3", + }) + ); + Collection fos = (Collection)fs.findResource("folder").getAttribute("revealEntries"); + assertTrue(fos.isEmpty()); + } + + /** + * Checks that for a deleted file, a FileObject is produced. + * file1 and file3 are deleted from different layers, then undeleted. + * + * @throws Exception + */ + public void testFileDeleted() throws Exception { + TestUtilHid.Resource root2 = TestUtilHid.createRoot(); + root2.add("folder/file2"); + root2.add("folder/file1").addAttribute("name", "stringvalue", "jouda"); + + TestUtilHid.Resource root3 = TestUtilHid.createRoot(); + root3.add("folder/file1"); + root3.add("folder/file3").addAttribute("name", "stringvalue", "bubak"); + + MultiFileSystem fs = new MultiFileSystem( + TestUtilHid.createLocalFileSystem(getName() + "1", new String[] { + "folder/file1_hidden", + "folder/file3_hidden", + }), + TestUtilHid.createXMLFileSystem(getName() + "2", root2), + TestUtilHid.createXMLFileSystem(getName() + "3", root3) + ); + Collection fos = (Collection)fs.findResource("folder").getAttribute("revealEntries"); + FileObject f = findFile(fos, "file1"); + assertNotNull("Delete file1 should be revealed", f); + assertTrue(f instanceof Callable); + assertEquals("folder/file1", f.getPath()); + assertSame(fs, f.getFileSystem()); + assertNotNull(f.getParent().getFileObject("file2")); + assertNull(f.getParent().getFileObject("file3")); + + assertNotNull(f.getAttribute("name")); + assertEquals("jouda", f.getAttribute("name")); + + FileObject f2 = findFile(fos, "file3"); + assertNotNull("Delete file1 should be revealed", f); + assertTrue(f2 instanceof Callable); + assertEquals("folder/file3", f2.getPath()); + assertSame(fs, f2.getFileSystem()); + assertNotNull(f2.getParent().getFileObject("file2")); + assertNull(f2.getParent().getFileObject("file1")); + + assertNotNull(f2.getAttribute("name")); + assertEquals("bubak", f2.getAttribute("name")); + + // try to un-delete the files: + ((Callable)f2).call(); + ((Callable)f).call(); + + assertEquals(3, fs.findResource("folder").getChildren().length); + } + + private FileObject findFile(Collection files, String name) { + for (FileObject f : files) { + if (f.getNameExt().equals(name)) { + return f; + } + } + return null; + } + + /** + * For a changed file, FileObject with the original content should + * be produced. + * @throws Exception + */ + public void testFileChangedAttributesAndContent() throws Exception { + TestUtilHid.Resource root2 = TestUtilHid.createRoot(); + root2.add("folder/file2"); + root2.add("folder/file1").withContent("Povidam povidam pohadku"); + + TestUtilHid.Resource root3 = TestUtilHid.createRoot(); + root3.add("folder/file1"); + root3.add("folder/file3").addAttribute("name", "stringvalue", "jouda"); + + FileSystem lfs = TestUtilHid.createLocalFileSystem(getName() + "1", new String[] { + "folder/file1", + "folder/file3", + }); + FileObject local1 = lfs.findResource("folder/file1"); + OutputStreamWriter wr = new OutputStreamWriter(local1.getOutputStream()); + wr.append("o cervenem kotatku"); + wr.close(); + + FileObject local3 = lfs.findResource("folder/file3"); + local3.setAttribute("test", "failed"); + local3.setAttribute("name", "truhlik"); + + MultiFileSystem fs = new MultiFileSystem( + lfs, + TestUtilHid.createXMLFileSystem(getName() + "2", root2), + TestUtilHid.createXMLFileSystem(getName() + "3", root3) + ); + + Collection fos = (Collection)fs.findResource("folder").getAttribute("revealEntries"); + FileObject f = findFile(fos, "file1"); + assertNotNull("Delete file1 should be revealed", f); + assertTrue(f instanceof Callable); + assertEquals("Povidam povidam pohadku", f.asText()); + assertEquals("folder/file1", f.getPath()); + assertSame(fs, f.getFileSystem()); + assertNotNull(f.getParent().getFileObject("file2")); + + FileObject f2 = findFile(fos, "file3"); + assertNotNull("Delete file1 should be revealed", f); + assertTrue(f2 instanceof Callable); + assertEquals("folder/file3", f2.getPath()); + assertSame(fs, f2.getFileSystem()); + assertNotNull(f2.getParent().getFileObject("file2")); + + assertNull(f2.getAttribute("test")); + assertEquals("jouda", f2.getAttribute("name")); + + FileObject multiFile1 = fs.findResource("folder/file1"); + FileObject multiFile3 = fs.findResource("folder/file3"); + + assertEquals("o cervenem kotatku", multiFile1.asText()); + assertEquals("truhlik", multiFile3.getAttribute("name")); + + // try to un-delete the files: + assertSame(multiFile3, ((Callable)f2).call()); + assertSame(multiFile1, ((Callable)f).call()); + + assertEquals("Povidam povidam pohadku", multiFile1.asText()); + assertEquals("jouda", multiFile3.getAttribute("name")); + + assertEquals(3, fs.findResource("folder").getChildren().length); + } + + /** + * No reveal entry should be present for user-only modification + * @throws Exception + */ + public void testUserOnlyContent() throws Exception { + MultiFileSystem fs = new MultiFileSystem( + TestUtilHid.createLocalFileSystem(getName() + "1", new String[] { + "folder/file4", + "folder/file5", + }), + TestUtilHid.createXMLFileSystem(getName() + "2", new String[] { + "folder/file1", + "folder/file2", + }), + TestUtilHid.createXMLFileSystem(getName() + "3", new String[] { + "folder/file1", + "folder/file3", + }) + ); + Collection fos = (Collection)fs.findResource("folder").getAttribute("revealEntries"); + assertTrue(fos.isEmpty()); + } + + /** + * No reveal entry should be present for user-only modification + * @throws Exception + */ + public void testNotWritableLayer() throws Exception { + MultiFileSystem fs = new MultiFileSystem( + TestUtilHid.createXMLFileSystem(getName() + "1", new String[] { + "folder/file1", + "folder/file3", + }), + TestUtilHid.createXMLFileSystem(getName() + "2", new String[] { + "folder/file1", + "folder/file2", + }), + TestUtilHid.createXMLFileSystem(getName() + "3", new String[] { + "folder/file1", + "folder/file3", + }) + ); + Collection fos = (Collection)fs.findResource("folder").getAttribute("revealEntries"); + assertNull("Readonly MFS does not support revealEntries", fos); + } +} diff --git a/openide.filesystems/test/unit/src/org/openide/filesystems/TestUtilHid.java b/openide.filesystems/test/unit/src/org/openide/filesystems/TestUtilHid.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/TestUtilHid.java +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/TestUtilHid.java @@ -46,6 +46,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; @@ -182,8 +183,19 @@ return xfs; } - - public static File createXMLLayer(String testName, String[] resources) throws IOException { + + public final static FileSystem createXMLFileSystem(String testName, Resource rootResource) throws IOException { + File xmlFile = createXMLLayer(testName, rootResource); + + XMLFileSystem xfs = new XMLFileSystem (); + try { + xfs.setXmlUrl(Utilities.toURI(xmlFile).toURL()); + } catch (Exception ex) {} + + return xfs; + } + + public static File createXMLLayer(String testName, Resource rootResource) throws IOException { File tempFile = TestUtilHid.locationOfTempFolder("xfstest"); tempFile.mkdir(); @@ -193,17 +205,25 @@ xmlFile.createNewFile(); } FileOutputStream xos = new FileOutputStream (xmlFile); - ResourceElement root = new ResourceElement (""); + PrintWriter pw = new PrintWriter (xos); + pw.println(""); + rootResource.serialize("", " ", pw, xmlFile.getParentFile()); + pw.println(""); + pw.close(); + return xmlFile; + } + + public static File createXMLLayer(String testName, String[] resources) throws IOException { + Resource root = createRoot(); for (int i = 0; i < resources.length; i++) root.add (resources[i]); - - PrintWriter pw = new PrintWriter (xos); - pw.println(""); - testStructure (pw,root.getChildren () ," "); - pw.println(""); - pw.close(); - return xmlFile; + + return createXMLLayer(testName, root); + } + + public static Resource createRoot() { + return new Resource(""); } private static void testStructure (PrintWriter pw,ResourceElement[] childern,String tab) { @@ -223,25 +243,28 @@ } } - private static class ResourceElement { + static class ResourceElement { String element; ResourceElement (String element) { //System.out.println(element); this.element = element; } Map children = new HashMap (); - void add (String resource) { - add (new StringTokenizer (resource,"/")); + Resource add (String resource) { + return add (new StringTokenizer (resource,"/")); } - private void add (Enumeration en) { + + Resource add (Enumeration en) { //StringTokenizer tokens = StringTokenizer (resource); if (en.hasMoreElements()) { String chldElem = (String)en.nextElement(); - ResourceElement child = (ResourceElement)children.get(chldElem); + Resource child = (Resource)children.get(chldElem); if (child == null) - child = new ResourceElement(chldElem); + child = new Resource(chldElem); children.put (chldElem,child); - child.add (en); + return child.add (en); + } else { + return (Resource)this; } } ResourceElement[] getChildren () { @@ -258,6 +281,14 @@ String getName () { return element; } + + boolean isLeaf() { + return children.isEmpty(); + } + + public void serialize(String path, String tab, PrintWriter pw, File baseFolder) throws IOException { + + } } static class StatusFileSystem extends LocalFileSystem { @@ -276,4 +307,89 @@ } } + + public static class Resource extends ResourceElement { + private Map attributeTypes = new HashMap(); + private Map attributeContents = new HashMap(); + private CharSequence fileContents = null; + private URL contentURL = null; + private boolean forceFolder; + + public Resource(String element) { + super(element); + } + + public Resource addAttribute(String name, String type, String valueContent) { + attributeTypes.put(name, type); + attributeContents.put(name, valueContent); + + return this; + } + + public Resource withContent(CharSequence seq) { + this.fileContents = seq; + + return this; + } + + public Resource withContentAt(URL contentURL) { + this.contentURL = contentURL; + return this; + } + + public Resource forceFolder() { + this.forceFolder = true; + return this; + } + + public boolean isLeaf() { + return !forceFolder && super.isLeaf(); + } + + public void serialize(String path, String tab, PrintWriter pw, File baseFolder) throws IOException { + if (!getName().isEmpty()) { + String n = getName(); + if (isLeaf()) { + pw.print(tab+""); + + for (String s : attributeContents.keySet()) { + pw.print("\n" + tab + " "); + } + pw.println(); + } + String newPath = path + getName(); + if (!newPath.isEmpty()) { + newPath = newPath + "/"; + } + for (ResourceElement res : children.values()) { + res.serialize(newPath, tab + " ", pw, baseFolder); + } + if (!getName().isEmpty()) { + if (isLeaf()) { + pw.println(tab+"" ); + } else { + pw.println(tab+"" ); + } + } + } + } }