diff -r 7fe7a1b1a4ef core.startup/src/org/netbeans/core/startup/ModuleList.java --- a/core.startup/src/org/netbeans/core/startup/ModuleList.java Sat Apr 26 22:19:38 2008 +0200 +++ b/core.startup/src/org/netbeans/core/startup/ModuleList.java Sat May 03 02:34:45 2008 +0200 @@ -1616,6 +1616,8 @@ final class ModuleList implements Stamps if (! cnb.equals(props.get("name"))) throw new IOException("Code name mismatch"); // NOI18N String jar = (String)props.get("jar"); // NOI18N File jarFile = findJarByName(jar, cnb); + String manifest = (String)props.get("manifest"); // NOI18N + File manifestFile = manifest == null ? null : findJarByName(manifest, cnb); Boolean reloadableB = (Boolean)props.get("reloadable"); // NOI18N boolean reloadable = (reloadableB != null ? reloadableB.booleanValue() : false); Boolean autoloadB = (Boolean)props.get("autoload"); // NOI18N @@ -1624,7 +1626,11 @@ final class ModuleList implements Stamps boolean eager = (eagerB != null ? eagerB.booleanValue() : false); Module m; try { - m = mgr.create(jarFile, new ModuleHistory(jar), reloadable, autoload, eager); + if (manifestFile != null) { + m = mgr.create(manifestFile, jarFile, new ModuleHistory(jar), reloadable, autoload, eager); + } else { + m = mgr.create(jarFile, new ModuleHistory(jar), reloadable, autoload, eager); + } } catch (DuplicateException dupe) { // XXX should this be tolerated somehow? In case the original is // in fact scheduled for deletion anyway? diff -r 7fe7a1b1a4ef core.startup/test/unit/src/org/netbeans/core/startup/ModuleListTest.java --- a/core.startup/test/unit/src/org/netbeans/core/startup/ModuleListTest.java Sat Apr 26 22:19:38 2008 +0200 +++ b/core.startup/test/unit/src/org/netbeans/core/startup/ModuleListTest.java Sat May 03 02:34:45 2008 +0200 @@ -281,5 +281,72 @@ public class ModuleListTest extends Setu // to install and remove the Modules/ entries in a MFS // and check that layer-driven events are enough to cause // complex installations & uninstallations + + public void testAddNewModuleExternalViaXML() throws Exception { + mgr.mutexPrivileged().enterWriteAccess(); + try { + assertEquals(Collections.emptySet(), list.readInitial()); + assertEquals(Collections.emptySet(), mgr.getModules()); + list.trigger(Collections.emptySet()); + assertEquals(Collections.emptySet(), mgr.getModules()); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + LoggedPCListener listener = new LoggedPCListener(); + mgr.addPropertyChangeListener(listener); + modulesfolder.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { + public void run() throws IOException { + // XXX this will require that there be an appropriate InstalledFileLocator in Lookup + FileObject fooxml = modulesfolder.createData("org-foo", "xml"); + copy(new File(JARS, "org-foo.xml"), fooxml); + + FileObject barxml = modulesfolder.createData("org-bar", "xml"); + File mf2 = new File(JARS, "depends-on-simple-module.mf"); + String xml = +"\n" + +"\n" + +"\n" + +" false\n" + +" false\n" + +" true\n" + +" " + new File(JARS, "depends-on-simple-module-external.jar") + "\n" + +" " + mf2 + "\n" + +" 1\n" + +" false\n" + +"\n"; + OutputStream os = barxml.getOutputStream(); + os.write(xml.getBytes("UTF-8")); + os.close(); + } + }); + assertTrue("PROP_MODULES fired", listener.waitForChange(mgr, ModuleManager.PROP_MODULES)); + mgr.mutexPrivileged().enterReadAccess(); + try { + Set modules = mgr.getEnabledModules(); + assertEquals(2, modules.size()); + TreeSet names = new TreeSet(); + { + Iterator it = modules.iterator(); + Module m = (Module)it.next(); + names.add(m.getCodeNameBase()); + assertTrue(m.isEnabled()); + m = (Module)it.next(); + assertTrue(m.isEnabled()); + names.add(m.getCodeNameBase()); + } + + Iterator it = names.iterator(); + assertEquals("org.bar", it.next()); + assertEquals("org.foo", it.next()); + + Class clazz = mgr.getClassLoader().loadClass("org.bar.SomethingElse"); + Class supr = clazz.getSuperclass(); + assertEquals("Parent is loaded from other module", "org.foo.Something", supr.getName()); + + } finally { + mgr.mutexPrivileged().exitReadAccess(); + } + } } diff -r 7fe7a1b1a4ef core.startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java --- a/core.startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java Sat Apr 26 22:19:38 2008 +0200 +++ b/core.startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java Sat May 03 02:34:46 2008 +0200 @@ -140,6 +140,100 @@ public class ModuleManagerTest extends S mgr.mutexPrivileged().enterWriteAccess(); try { Module m2 = mgr.create(new File(jars, "depends-on-simple-module.jar"), null, false, false, false); + Module m1 = mgr.create(new File(jars, "simple-module.jar"), null, false, false, false); + assertEquals("org.foo", m1.getCodeNameBase()); + assertEquals("org.bar", m2.getCodeNameBase()); + assertEquals(Collections.EMPTY_SET, m1.getDependencies()); + assertEquals(Dependency.create(Dependency.TYPE_MODULE, "org.foo/1"), m2.getDependencies()); + Map modulesByName = new HashMap(); + modulesByName.put(m1.getCodeNameBase(), m1); + modulesByName.put(m2.getCodeNameBase(), m2); + List m1m2 = Arrays.asList(m1, m2); + List m2m1 = Arrays.asList(m2, m1); + Map> deps = Util.moduleDependencies(m1m2, modulesByName, Collections.>emptyMap()); + assertNull(deps.get(m1)); + assertEquals(Collections.singletonList(m1), deps.get(m2)); + assertEquals(m2m1, Utilities.topologicalSort(m1m2, deps)); + assertEquals(m2m1, Utilities.topologicalSort(m2m1, deps)); + // Leave commented out since it has a (hopefully clean) mutation effect + // and could affect results: + /* + assertEquals(Collections.EMPTY_SET, m1.getProblems()); + assertEquals(Collections.EMPTY_SET, m2.getProblems()); + */ + Set m1PlusM2 = new HashSet(); + m1PlusM2.add(m1); + m1PlusM2.add(m2); + List toEnable = mgr.simulateEnable(m1PlusM2); + assertEquals("correct result of simulateEnable", Arrays.asList(m1, m2), toEnable); + mgr.enable(m1PlusM2); + assertEquals(Arrays.asList( + "prepare", + "prepare", + "load" + ), installer.actions); + assertEquals(Arrays.asList( + m1, + m2, + Arrays.asList(m1, m2) + ), installer.args); + Class somethingelse = Class.forName("org.bar.SomethingElse", true, m2.getClassLoader()); + Method somemethod = somethingelse.getMethod("message"); + assertEquals("hello", somemethod.invoke(somethingelse.newInstance())); + installer.clear(); + List toDisable = mgr.simulateDisable(Collections.singleton(m1)); + assertEquals("correct result of simulateDisable", Arrays.asList(m2, m1), toDisable); + toDisable = mgr.simulateDisable(m1PlusM2); + assertEquals("correct result of simulateDisable #2", Arrays.asList(m2, m1), toDisable); + mgr.disable(m1PlusM2); + assertFalse(m1.isEnabled()); + assertFalse(m2.isEnabled()); + assertEquals(Collections.EMPTY_SET, mgr.getEnabledModules()); + assertEquals(m1PlusM2, mgr.getModules()); + assertEquals(Arrays.asList( + "unload", + "dispose", + "dispose" + ), installer.actions); + assertEquals(Arrays.asList( + Arrays.asList(m2, m1), + m2, + m1 + ), installer.args); + installer.clear(); + mgr.enable(m1); + mgr.shutDown(); + assertEquals(Arrays.asList( + "prepare", + "load", + "closing", + "close" + ), installer.actions); + assertEquals(Arrays.asList( + m1, + Collections.singletonList(m1), + Collections.singletonList(m1), + Collections.singletonList(m1) + ), installer.args); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + } + + /** Load simple-module and depends-on-simple-module. + * Make sure they can be installed and in a sane order. + * Make sure a class from one can depend on a class from another. + * Try to disable them too. + */ + public void testSimpleInstallationOfExternalModule() throws Exception { + FakeModuleInstaller installer = new FakeModuleInstaller(); + FakeEvents ev = new FakeEvents(); + ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + try { + File mf2 = new File(jars, "depends-on-simple-module.mf"); + assertTrue("mf2", mf2.exists()); + Module m2 = mgr.create(mf2, new File(jars, "depends-on-simple-module-external.jar"), null, false, false, false); Module m1 = mgr.create(new File(jars, "simple-module.jar"), null, false, false, false); assertEquals("org.foo", m1.getCodeNameBase()); assertEquals("org.bar", m2.getCodeNameBase()); diff -r 7fe7a1b1a4ef core.startup/test/unit/src/org/netbeans/core/startup/build.xml --- a/core.startup/test/unit/src/org/netbeans/core/startup/build.xml Sat Apr 26 22:19:38 2008 +0200 +++ b/core.startup/test/unit/src/org/netbeans/core/startup/build.xml Sat May 03 02:34:46 2008 +0200 @@ -62,6 +62,12 @@ made subject to such option by the copyr + + + + + + diff -r 7fe7a1b1a4ef o.n.bootstrap/manifest.mf --- a/o.n.bootstrap/manifest.mf Sat Apr 26 22:19:38 2008 +0200 +++ b/o.n.bootstrap/manifest.mf Sat May 03 02:34:46 2008 +0200 @@ -1,4 +1,4 @@ Manifest-Version: 1.0 Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.bootstrap/1 -OpenIDE-Module-Specification-Version: 2.13 +OpenIDE-Module-Specification-Version: 2.14 OpenIDE-Module-Localizing-Bundle: org/netbeans/Bundle.properties diff -r 7fe7a1b1a4ef o.n.bootstrap/src/org/netbeans/ModuleFactory.java --- a/o.n.bootstrap/src/org/netbeans/ModuleFactory.java Sat Apr 26 22:19:38 2008 +0200 +++ b/o.n.bootstrap/src/org/netbeans/ModuleFactory.java Sat May 03 02:34:46 2008 +0200 @@ -64,6 +64,18 @@ public class ModuleFactory { throws IOException { return new StandardModule(mgr, ev, jar, history, reloadable, autoload, eager); } + + + /** + * This method creates a "standard" module with external manifest. + * @see StandardModule + * @since 2.14 + */ + public Module create(File manifest, File jar, Object history, boolean reloadable, + boolean autoload, boolean eager, ModuleManager mgr, Events ev) + throws IOException { + return new StandardModule(mgr, ev, manifest, jar, history, reloadable, autoload, eager); + } /** * This method creates a "fixed" module. Fixed modules cannot be diff -r 7fe7a1b1a4ef o.n.bootstrap/src/org/netbeans/ModuleManager.java --- a/o.n.bootstrap/src/org/netbeans/ModuleManager.java Sat Apr 26 22:19:38 2008 +0200 +++ b/o.n.bootstrap/src/org/netbeans/ModuleManager.java Sat May 03 02:34:46 2008 +0200 @@ -505,10 +505,39 @@ public final class ModuleManager { * You cannot request that a module be both autoload and eager. */ public Module create(File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException, DuplicateException { + return create(null, jar, history, reloadable, autoload, eager); + } + + /** Create a module from a JAR and external manifest add it to the managed set. + * Will initially be disabled, unless it is eager and can + * be enabled immediately. + * May throw an IOException if the JAR file cannot be opened + * for some reason, or is malformed. + * If there is already a module of the same name managed, + * throws a duplicate exception. In this case you may wish + * to delete the original and try again. + * You must give it some history object which can be used + * to provide context for where the module came from and + * whether it has been here before. + * You cannot request that a module be both autoload and eager. + * + * @since 2.14 + */ + public Module create(File manifest, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException, DuplicateException { assertWritable(); ev.log(Events.START_CREATE_REGULAR_MODULE, jar); - Module m = moduleFactory.create(jar.getAbsoluteFile(), + Module m; + + if (manifest == null) { + m = moduleFactory.create(jar.getAbsoluteFile(), history, reloadable, autoload, eager, this, ev); + } else { + m = moduleFactory.create( + manifest, jar.getAbsoluteFile(), + history, reloadable, autoload, eager, + this, ev + ); + } ev.log(Events.FINISH_CREATE_REGULAR_MODULE, jar); subCreate(m); if (m.isEager()) { diff -r 7fe7a1b1a4ef o.n.bootstrap/src/org/netbeans/StandardModule.java --- a/o.n.bootstrap/src/org/netbeans/StandardModule.java Sat Apr 26 22:19:38 2008 +0200 +++ b/o.n.bootstrap/src/org/netbeans/StandardModule.java Sat May 03 02:34:46 2008 +0200 @@ -47,6 +47,7 @@ package org.netbeans; // (NbBundle.getLocalizedValue is OK here.) import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.AllPermission; @@ -139,6 +140,26 @@ final class StandardModule extends Modul } moduleJARs.add(jar); } + + /** External module with manifest */ + StandardModule(ModuleManager mgr, Events ev, File manifest, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException { + super(mgr, ev, history, reloadable, autoload, eager); + this.jar = jar; + FileInputStream is = new FileInputStream(manifest); + this.manifest = new Manifest(is); + is.close(); + parseManifest(); + findExtensionsAndVariants(this.manifest); + // Check if some other module already listed this one in Class-Path. + // For the chronologically reverse case, see findExtensionsAndVariants(). + Set bogoOwners = extensionOwners.get(jar); + if (bogoOwners != null) { + Util.err.warning("module " + jar + " was incorrectly placed in the Class-Path of other JARs " + bogoOwners + "; please use OpenIDE-Module-Module-Dependencies instead"); + } + moduleJARs.add(jar); + } + + public @Override Manifest getManifest() { if (manifest == null) {