Index: core/startup/src/org/netbeans/core/startup/layers/SystemFileSystem.java =================================================================== RCS file: /shared/data/ccvs/repository/core/startup/src/org/netbeans/core/startup/layers/SystemFileSystem.java,v retrieving revision 1.8 diff -u -r1.8 SystemFileSystem.java --- core/startup/src/org/netbeans/core/startup/layers/SystemFileSystem.java 3 Aug 2006 09:57:27 -0000 1.8 +++ core/startup/src/org/netbeans/core/startup/layers/SystemFileSystem.java 13 Mar 2007 22:38:55 -0000 @@ -29,8 +29,11 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; @@ -42,13 +45,15 @@ import org.openide.filesystems.LocalFileSystem; import org.openide.filesystems.MultiFileSystem; import org.openide.filesystems.Repository; +import org.openide.util.Lookup; import org.openide.util.NbBundle; /** The system FileSystem - represents system files under $NETBEANS_HOME/system. * * @author Jan Jancura, Ian Formanek, Petr Hamernik */ -public final class SystemFileSystem extends MultiFileSystem implements FileSystem.Status { +public final class SystemFileSystem extends MultiFileSystem +implements FileSystem.Status, org.openide.util.LookupListener { // Must be public for BeanInfo to work: #11186. /** generated Serialized Version UID */ @@ -65,6 +70,11 @@ /** name of file attribute with URL to 32x32 color icon */ private static final String ATTR_ICON_32 = "SystemFileSystem.icon32"; // NOI18N + /** lookup result we listen on */ + private static Lookup.Result result = Lookup.getDefault().lookupResult(FileSystem.class); + /** the set of layers provided by the system */ + private static FileSystem[] layers; + /** user fs */ private ModuleLayeredFileSystem user; /** home fs */ @@ -73,13 +83,16 @@ /** @param fss list of file systems to delegate to */ @SuppressWarnings("deprecation") - private SystemFileSystem (FileSystem[] fss) throws PropertyVetoException { - super (fss); - user = (ModuleLayeredFileSystem) fss[0]; - home = fss.length > 2 ? (ModuleLayeredFileSystem) fss[1] : null; - - setSystemName (SYSTEM_NAME); - setHidden (true); + private SystemFileSystem() throws PropertyVetoException { + super(computeLayers()); + user = (ModuleLayeredFileSystem) layers[0]; + home = layers.length > 2 ? (ModuleLayeredFileSystem) layers[1] : null; + + setSystemName(SYSTEM_NAME); + setHidden(true); + + result.addLookupListener(this); + resultChanged(null); } @@ -113,8 +126,9 @@ else s.add (arr[i]); + layers = (FileSystem[])arr.clone(); // create own internal copy of passed filesystems - setDelegates(arr.clone()); + setDelegates(computeLayers()); firePropertyChange ("layers", null, null); // NOI18N } @@ -127,6 +141,20 @@ // don't return reference to internal buffer return getDelegates().clone(); } + + private synchronized static FileSystem[] computeLayers () { + FileSystem[] fromLookup = result.allInstances ().toArray (new FileSystem[0]); + + if (fromLookup.length > 0) { + ArrayList arr = new ArrayList(layers.length + fromLookup.length); + arr.addAll (Arrays.asList (layers)); + List lkpBased = Arrays.asList(fromLookup); + arr.addAll(lkpBased); + return arr.toArray (new FileSystem[0]); + } + + return layers; + } protected FileSystem createWritableOnForRename (String oldName, String newName) throws IOException { return createWritableOn (oldName); @@ -280,7 +308,8 @@ ("org.netbeans.core.projects.FixedFileSystem", "Automatic Manifest Installation"); // NOI18N arr[home == null ? 1 : 2] = FixedFileSystem.deflt; - return new SystemFileSystem (arr); + layers = arr; + return new SystemFileSystem (); } /** Notification that a file has migrated from one file system @@ -297,9 +326,14 @@ } // --- SAFETY --- - private Object writeReplace () throws ObjectStreamException { - new NotSerializableException ("WARNING - SystemFileSystem is not designed to be serialized").printStackTrace (); // NOI18N - return new SingletonSerializer (); + private Object writeReplace() throws ObjectStreamException { + new NotSerializableException("WARNING - SystemFileSystem is not designed to be serialized").printStackTrace(); // NOI18N + return new SingletonSerializer(); + } + + /** Refresh layers */ + public synchronized void resultChanged(org.openide.util.LookupEvent ev) { + setDelegates(computeLayers()); } private static final class SingletonSerializer extends Object implements Serializable { Index: core/startup/test/unit/src/org/netbeans/core/startup/layers/SystemFileSystemTest.java =================================================================== RCS file: core/startup/test/unit/src/org/netbeans/core/startup/layers/SystemFileSystemTest.java diff -N core/startup/test/unit/src/org/netbeans/core/startup/layers/SystemFileSystemTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/startup/test/unit/src/org/netbeans/core/startup/layers/SystemFileSystemTest.java 13 Mar 2007 22:38:55 -0000 @@ -0,0 +1,276 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.core.startup.layers; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.TreeSet; +import junit.framework.TestCase; +import org.netbeans.Module; +import org.netbeans.ModuleManager; +import org.netbeans.core.startup.Main; +import org.netbeans.core.startup.MainLookup; +import org.netbeans.core.startup.ModuleManagerTest; +import org.netbeans.junit.NbTestCase; +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; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.Repository; +import org.openide.modules.ModuleInfo; +import org.openide.util.Lookup; +import org.openide.util.lookup.InstanceContent; + +/** Test layering of filesystems installed via lookup. + * + * @author Jaroslav Tulach + */ +public class SystemFileSystemTest extends NbTestCase +implements InstanceContent.Convertor, FileChangeListener { + FileSystem fs; + FileSystem fs1 = FileUtil.createMemoryFileSystem(); + FileSystem fs2 = FileUtil.createMemoryFileSystem(); + private List events; + private File jars; + + public SystemFileSystemTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + fs = Repository.getDefault().getDefaultFileSystem(); + Lookup.getDefault().lookup(ModuleInfo.class); + + for (FileObject fo : fs.getRoot().getChildren()) { + fo.delete(); + } + events = new LinkedList(); + + fs.addFileChangeListener(this); + jars = new File(ModuleManagerTest.class.getResource("jars").getFile()); + clearWorkDir(); + } + + @Override + protected void tearDown() throws Exception { + MainLookup.unregister(fs1, this); + MainLookup.unregister(fs2, this); + } + + + + public void testUserHasPreferenceOverFSs() throws Exception { + FileObject global = FileUtil.createData(fs.getRoot(), "dir/file.txt"); + global.setAttribute("global", 3); + write(global, "global"); + + FileObject fo1 = FileUtil.createData(fs1.getRoot(), "dir/file.txt"); + fo1.setAttribute("one", 1); + write(fo1, "fileone"); + + FileObject fo2 = FileUtil.createData(fs2.getRoot(), "dir/file.txt"); + fo2.setAttribute("two", 2); + write(fo2, "two"); + + events.clear(); + + MainLookup.register(fs1, this); + MainLookup.register(fs2, this); + + Enumeration en = global.getAttributes(); + TreeSet t = new TreeSet(); + while (en.hasMoreElements()) { + t.add(en.nextElement()); + } + + assertEquals("three elements: " + t, 3, t.size()); + assertTrue(t.contains("two")); + assertTrue(t.contains("one")); + assertTrue(t.contains("global")); + + assertEquals(1, global.getAttribute("one")); + assertEquals(2, global.getAttribute("two")); + assertEquals(3, global.getAttribute("global")); + + assertEquals("contains global", 6, global.getSize()); + assertEquals("global", read(global)); + + + assertTrue("no events: " + events, events.isEmpty()); + } + + public void testUserHasPreferenceOverFSsButGeneratesAnEvent() throws Exception { + FileObject fo1 = FileUtil.createData(fs1.getRoot(), "dir/file.txt"); + fo1.setAttribute("one", 1); + write(fo1, "fileone"); + + FileObject fo2 = FileUtil.createData(fs2.getRoot(), "dir/file.txt"); + fo2.setAttribute("two", 2); + write(fo2, "two"); + + events.clear(); + + MainLookup.register(fs1, this); + MainLookup.register(fs2, this); + + assertFalse("not empty", events.isEmpty()); + events.clear(); + + FileObject global = FileUtil.createData(fs.getRoot(), "dir/file.txt"); + global.setAttribute("global", 3); + write(global, "global"); + + assertFalse("yet another set", events.isEmpty()); + + Enumeration en = global.getAttributes(); + TreeSet t = new TreeSet(); + while (en.hasMoreElements()) { + t.add(en.nextElement()); + } + + + + assertEquals("three elements: " + t, 3, t.size()); + assertTrue(t.contains("two")); + assertTrue(t.contains("one")); + assertTrue(t.contains("global")); + + assertEquals(1, global.getAttribute("one")); + assertEquals(2, global.getAttribute("two")); + assertEquals(3, global.getAttribute("global")); + + assertEquals("contains global", 6, global.getSize()); + assertEquals("global", read(global)); + } + + public void testPreferenceOfLayers() throws Exception { + ModuleManager mgr = Main.getModuleSystem ().getManager(); + mgr.mutexPrivileged().enterWriteAccess(); + try { + Module m1 = mgr.create(new File(jars, "base-layer-mod.jar"), null, false, false, false); + assertEquals(Collections.EMPTY_SET, m1.getProblems()); + mgr.enable(m1); + FileObject global = fs.findResource("foo/file2.txt"); + assertNotNull("File Object installed: " + global, global); + assertEquals("base contents", read(global)); + + + FileObject fo1 = FileUtil.createData(fs1.getRoot(), global.getPath()); + fo1.setAttribute("one", 1); + write(fo1, "fileone"); + + FileObject fo2 = FileUtil.createData(fs2.getRoot(), global.getPath()); + fo2.setAttribute("two", 2); + write(fo2, "two"); + + events.clear(); + + MainLookup.register(fs1, this); + MainLookup.register(fs2, this); + + Iterator it = Lookup.getDefault().lookupAll(FileSystem.class).iterator(); + assertTrue("At least One", it.hasNext()); + assertEquals("first is fs1", fs1, it.next()); + assertTrue("At least two ", it.hasNext()); + assertEquals("first is fs2", fs2, it.next()); + + + + assertEquals("base contents", read(global)); + + + mgr.disable(m1); + mgr.delete(m1); + + assertTrue("Still valid", global.isValid()); + assertEquals("fileone", read(global)); + + + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + + } + + private static void write(FileObject fo, String txt) throws IOException { + OutputStream os = fo.getOutputStream(); + os.write(txt.getBytes()); + os.close(); + } + + private static String read(FileObject fo) throws IOException { + byte[] arr = new byte[(int)fo.getSize()]; + InputStream is = fo.getInputStream(); + int len = is.read(arr); + assertEquals("Not enough read", arr.length, len); + return new String(arr); + } + + public FileSystem convert(FileSystem obj) { + return obj; + } + + public Class type(FileSystem obj) { + return obj.getClass(); + } + + public String id(FileSystem obj) { + return obj.getDisplayName(); + } + + public String displayName(FileSystem obj) { + return obj.getDisplayName(); + } + + public void fileFolderCreated(FileEvent fe) { + events.add(fe); + } + + public void fileDataCreated(FileEvent fe) { + events.add(fe); + } + + public void fileChanged(FileEvent fe) { + events.add(fe); + } + + public void fileDeleted(FileEvent fe) { + events.add(fe); + } + + public void fileRenamed(FileRenameEvent fe) { + events.add(fe); + } + + public void fileAttributeChanged(FileAttributeEvent fe) { + events.add(fe); + } +} Index: openide/arch/arch-openide-filesystems.xml =================================================================== RCS file: /shared/data/ccvs/repository/openide/arch/arch-openide-filesystems.xml,v retrieving revision 1.25 diff -u -r1.25 arch-openide-filesystems.xml --- openide/arch/arch-openide-filesystems.xml 15 Aug 2006 15:21:26 -0000 1.25 +++ openide/arch/arch-openide-filesystems.xml 13 Mar 2007 22:39:04 -0000 @@ -22,7 +22,7 @@ ]> @@ -718,5 +718,354 @@ No. + + + + + + +

+ NetBeans internally uses the concept of a virtual filesystem. This module + provide APIs for accessing such virtual files as well as some support classes + to make writing of custom vitual filesystems easier. +

+
+ + + + + +

+ XXX no answer for arch-quality +

+
+ + + + + +

+ XXX no answer for arch-time +

+
+ + + + + +

+ Many of the usecases are described at the + overall documentation, + in a way how to + register a mime type. + Some of the additional usecases are covered here. +

+ + +

+ Since version 7.1 there is a way to change the content of system file + system in a dynamic way. As system file systems contains various + definitions (in NetBeans Platform menus, toolbars, layout of windows, etc.) + it de-facto allows global change to these settings for example when + user logs into some system. +

+ +

+ First thing to do is to create an implementation of filesystem. + It can be created either from scratch, or by subclassing + AbstractFileSystem, + or + MultiFileSystem. + In this example we will subclass the + MultiFileSystem: +

+ +
+public class LoginFileSystem extends MultiFileSystem {
+  public LoginFileSystem() {
+    // let's create the filesystem empty, because the user
+    // is not yet logged in
+    super(new FileSystem[0]);
+  }
+  
+  public static void assignURL(URL u) {
+    LoginFileSystem lfs = Lookup.getDefault().lookup(LoginFileSystem.class);
+    XMLFileSystem xmlFS = new XMLFileSystem(u);
+    lfs.setDelegates(new FileSystem[] { xmlFS });
+  }
+}
+      
+ +

+ It is necessary to register this instance in lookup by creating two files: +

+
    +
  • META-INF/services/org.openide.filesystems.FileSystem
  • +
  • META-INF/services/your.module.LoginFileSystem
  • +
+

+ and filling these files with a single line containing the full + name of your filesystem - e.g. your.module.LoginFileSystem. + When done, the system will find out your registration of the filesystem + on startup and will merge the content of the filesystem into the + default system file system. You can show a dialog letting the user + to log in to some system anytime later, and when the user is successfully + in, just call LoginFileSystem.assignURL(url) where the + URL is an XML file in the same format + as used for regular layer files inside of many NetBeans modules. + The system will notice the change in the content and notify all the + config file listeners accordingly. +

+ +

+ Of course, instead of + XMLFileSystem + you can use for example + memory file system, + or any other you write yourself. +

+
+
+ + + + + + + + + + + + +

+ XXX no answer for compat-deprecation +

+
+ + + + + +

+ XXX no answer for deploy-dependencies +

+
+ + + + + +

+ XXX no answer for exec-ant-tasks +

+
+ + + + + +

+ XXX no answer for exec-threading +

+
+ + + + + +

+ XXX no answer for perf-spi +

+
+ + + + + +

+ XXX no answer for resources-preferences +

+
+ + + + + +

+ XXX no answer for security-grant +

+
+ + + + + +

+ XXX no answer for security-policy +

+
Index: openide/fs/apichanges.xml =================================================================== RCS file: /shared/data/ccvs/repository/openide/fs/apichanges.xml,v retrieving revision 1.11 diff -u -r1.11 apichanges.xml --- openide/fs/apichanges.xml 7 Sep 2006 08:43:36 -0000 1.11 +++ openide/fs/apichanges.xml 13 Mar 2007 22:39:13 -0000 @@ -23,6 +23,28 @@ Filesystems API + + + Allow modules to dynamically add/remove layer content + + + + + +

+ Repository.getDefaultFileSystem's content can now be + influenced by adding own + FileSystems + into global + Lookup.getDefault(). + This is supposed to work in a standalone mode as well + as inside NetBeans Platform. The tutorial is available + in the usecases section of achitecture description. +

+
+ + +
Added additional methods FileUtil.createData Index: openide/fs/manifest.mf =================================================================== RCS file: /shared/data/ccvs/repository/openide/fs/manifest.mf,v retrieving revision 1.8 diff -u -r1.8 manifest.mf --- openide/fs/manifest.mf 31 May 2006 15:42:10 -0000 1.8 +++ openide/fs/manifest.mf 13 Mar 2007 22:39:13 -0000 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.filesystems -OpenIDE-Module-Specification-Version: 7.0 +OpenIDE-Module-Specification-Version: 7.1 OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties Index: openide/fs/src/org/openide/filesystems/ExternalUtil.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/fs/src/org/openide/filesystems/ExternalUtil.java,v retrieving revision 1.7 diff -u -r1.7 ExternalUtil.java --- openide/fs/src/org/openide/filesystems/ExternalUtil.java 28 Oct 2006 21:57:38 -0000 1.7 +++ openide/fs/src/org/openide/filesystems/ExternalUtil.java 13 Mar 2007 22:39:13 -0000 @@ -18,10 +18,14 @@ */ package org.openide.filesystems; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.openide.util.Exceptions; import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; /** Contains utility methods to deal with repository and error manager, @@ -120,7 +124,30 @@ if (repository == null) { // if not provided use default one - repository = new Repository(FileUtil.createMemoryFileSystem()); + repository = new Repository(new MainFS()); } } + + private static final class MainFS extends MultiFileSystem implements LookupListener { + private static final Lookup.Result ALL = Lookup.getDefault().lookupResult(FileSystem.class); + private static final FileSystem MEMORY = FileUtil.createMemoryFileSystem(); + + public MainFS() { + super(computeDelegates()); + ALL.addLookupListener(this); + resultChanged(null); + } + + private static FileSystem[] computeDelegates() { + List arr = new ArrayList(); + arr.add(MEMORY); + arr.addAll(ALL.allInstances()); + return arr.toArray(new FileSystem[0]); + } + + + public void resultChanged(LookupEvent ev) { + setDelegates(computeDelegates()); + } + } // end of MainFS } Index: openide/fs/test/unit/src/org/openide/filesystems/RepositoryTest.java =================================================================== RCS file: openide/fs/test/unit/src/org/openide/filesystems/RepositoryTest.java diff -N openide/fs/test/unit/src/org/openide/filesystems/RepositoryTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/fs/test/unit/src/org/openide/filesystems/RepositoryTest.java 13 Mar 2007 22:39:13 -0000 @@ -0,0 +1,65 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.filesystems; + +import junit.framework.TestCase; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; + +/** + * + * @author Jaroslav Tulach + */ +public class RepositoryTest extends TestCase { + static { + System.setProperty("org.openide.util.Lookup", MainLookup.class.getName()); + } + + + public RepositoryTest(String testName) { + super(testName); + } + + + public void testContentOfFileSystemIsInfluencedByLookup () throws Exception { + FileSystem mem = FileUtil.createMemoryFileSystem(); + String dir = "/yarda/own/file"; + org.openide.filesystems.FileUtil.createFolder (mem.getRoot (), dir); + + assertNull ("File is not there yet", Repository.getDefault ().getDefaultFileSystem ().findResource (dir)); + MainLookup.ic.add(mem); + try { + assertNotNull ("The file is there now", Repository.getDefault ().getDefaultFileSystem ().findResource (dir)); + } finally { + MainLookup.ic.remove(mem); + } + assertNull ("File is no longer there", Repository.getDefault ().getDefaultFileSystem ().findResource (dir)); + } + + + public static final class MainLookup extends AbstractLookup { + static final InstanceContent ic = new InstanceContent(); + + + public MainLookup() { + super(ic); + } + } +}