Index: openide/modules/src/org/openide/modules/Dependency.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/modules/src/org/openide/modules/Dependency.java,v retrieving revision 1.5 diff -u -r1.5 Dependency.java --- openide/modules/src/org/openide/modules/Dependency.java 22 Mar 2006 06:37:52 -0000 1.5 +++ openide/modules/src/org/openide/modules/Dependency.java 28 Jun 2006 19:55:13 -0000 @@ -47,6 +47,12 @@ */ public final static int TYPE_REQUIRES = 5; + /** Dependency on a token, but without need to have token provider be initialised sooner. + * @see ModuleInfo#getProvides + * @since JST-PENDING + */ + public final static int TYPE_NEEDS = 6; + /** Comparison by specification version. */ public final static int COMPARE_SPEC = 1; @@ -310,6 +316,12 @@ } else if (type == Dependency.TYPE_REQUIRES) { if (comparison != Dependency.COMPARE_ANY) { throw new IllegalArgumentException("Cannot give a comparison for a token requires dep: " + body); // NOI18N + } + + checkCodeName(name, false); + } else if (type == Dependency.TYPE_NEEDS) { + if (comparison != Dependency.COMPARE_ANY) { + throw new IllegalArgumentException("Cannot give a comparison for a token needs dep: " + body); // NOI18N } checkCodeName(name, false); Index: core/bootstrap/src/org/netbeans/Module.java =================================================================== RCS file: /shared/data/ccvs/repository/core/bootstrap/src/org/netbeans/Module.java,v retrieving revision 1.15 diff -u -r1.15 Module.java --- core/bootstrap/src/org/netbeans/Module.java 22 Jun 2006 07:22:09 -0000 1.15 +++ core/bootstrap/src/org/netbeans/Module.java 28 Jun 2006 19:55:13 -0000 @@ -393,6 +393,7 @@ dependencies.addAll(Dependency.create(Dependency.TYPE_PACKAGE, pkgdeps)); // NOI18N } dependencies.addAll(Dependency.create(Dependency.TYPE_REQUIRES, attr.getValue("OpenIDE-Module-Requires"))); // NOI18N + dependencies.addAll(Dependency.create(Dependency.TYPE_NEEDS, attr.getValue("OpenIDE-Module-Needs"))); // NOI18N // Permit the concrete installer to make some changes: mgr.refineDependencies(this, dependencies); dependenciesA = dependencies.toArray(new Dependency[dependencies.size()]); Index: core/bootstrap/src/org/netbeans/ModuleManager.java =================================================================== RCS file: /shared/data/ccvs/repository/core/bootstrap/src/org/netbeans/ModuleManager.java,v retrieving revision 1.16 diff -u -r1.16 ModuleManager.java --- core/bootstrap/src/org/netbeans/ModuleManager.java 6 Jun 2006 14:41:18 -0000 1.16 +++ core/bootstrap/src/org/netbeans/ModuleManager.java 28 Jun 2006 19:55:14 -0000 @@ -1039,7 +1039,7 @@ Util.err.log(Level.WARNING, null, ex); } Util.err.warning("Cyclic module dependencies, will refuse to enable: " + deps); // NOI18N - return Collections.emptyList(); + return (List)ex.partialSort(); } } private void maybeAddToEnableList(Set willEnable, Set mightEnable, Module m, boolean okToFail) { @@ -1065,7 +1065,7 @@ if (! other.isEnabled()) { maybeAddToEnableList(willEnable, mightEnable, other, false); } - } else if (dep.getType() == Dependency.TYPE_REQUIRES) { + } else if (dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS) { String token = dep.getName(); Set providers = providersOf.get(token); if (providers == null) throw new IllegalStateException("Should have found a provider of: " + token); // NOI18N @@ -1309,10 +1309,10 @@ // where moduleProblems are used are write-mutex only and so do not have // to worry about contention. synchronized (moduleProblems) { - return _missingDependencies(probed); + return _missingDependencies(probed, true); } } - private Set> _missingDependencies(Module probed) { + private Set> _missingDependencies(Module probed, boolean testCycle) { Set> probs = moduleProblems.get(probed); if (probs == null) { probs = new HashSet>(8); @@ -1382,7 +1382,7 @@ if (! other.isEnabled()) { // Need to make sure the other one is not missing anything either. // Nor that it depends (directly on indirectly) on this one. - if (! _missingDependencies(other).isEmpty()) { + if (! _missingDependencies(other, testCycle).isEmpty()) { // This is a little subtle. Either the other module had real // problems, in which case our dependency on it is not legit. // Or, the other actually depends cyclically on this one. In @@ -1401,7 +1401,7 @@ // on it if we need it. } // Already-installed modules are of course fine. - } else if (dep.getType() == Dependency.TYPE_REQUIRES) { + } else if (dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS) { // Works much like a regular module dependency. However it only // fails if there are no satisfying modules with no problems. String token = dep.getName(); @@ -1416,7 +1416,7 @@ if (other.isEnabled()) { foundOne = true; } else { - if (_missingDependencies(other).isEmpty()) { + if (_missingDependencies(other, testCycle && dep.getType() == Dependency.TYPE_REQUIRES).isEmpty()) { // See comment above for regular module deps // re. use of PROBING_IN_PROCESS. foundOne = true; @@ -1438,6 +1438,10 @@ } } probs.remove(PROBING_IN_PROCESS); + } else { + if (!testCycle && probs.size() == 1 && probs.iterator().next() == PROBING_IN_PROCESS) { + return Collections.emptySet(); + } } return probs; } Index: core/bootstrap/src/org/netbeans/Util.java =================================================================== RCS file: /shared/data/ccvs/repository/core/bootstrap/src/org/netbeans/Util.java,v retrieving revision 1.10 diff -u -r1.10 Util.java --- core/bootstrap/src/org/netbeans/Util.java 22 Jun 2006 07:22:09 -0000 1.10 +++ core/bootstrap/src/org/netbeans/Util.java 28 Jun 2006 19:55:14 -0000 @@ -366,7 +366,7 @@ for (int i = 0; i < dependencies.length; i++) { Dependency dep = dependencies[i]; - if (dep.getType() == Dependency.TYPE_REQUIRES) { + if (dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS) { List providers = providersOf.get(dep.getName()); if (providers != null) { Index: core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java =================================================================== RCS file: /shared/data/ccvs/repository/core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java,v retrieving revision 1.6 diff -u -r1.6 ModuleManagerTest.java --- core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java 1 Jun 2006 15:03:58 -0000 1.6 +++ core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java 28 Jun 2006 19:55:16 -0000 @@ -49,6 +49,7 @@ import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; +import org.openide.util.TopologicalSortException; import org.openide.util.Utilities; /** Test the module manager as well as the Module class. @@ -1018,6 +1019,88 @@ assertEquals(4, toEnable.size()); assertTrue(toEnable.get(0) != m2); assertTrue(toEnable.get(0) != m3); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + } + + public void testSimpleProvNeeds() throws Exception { + FakeModuleInstaller installer = new FakeModuleInstaller(); + FakeEvents ev = new FakeEvents(); + ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + try { + Module m1 = mgr.create(new File(jars, "prov-foo-depends-needs_foo.jar"), null, false, false, false); + Module m2 = mgr.create(new File(jars, "needs-foo.jar"), null, false, false, false); + assertEquals(Collections.singletonList("foo"), Arrays.asList(m1.getProvides())); + assertEquals(Collections.EMPTY_LIST, Arrays.asList(m2.getProvides())); + assertEquals(1, m1.getDependencies().size()); + assertEquals(Dependency.create(Dependency.TYPE_NEEDS, "foo"), m2.getDependencies()); + Map modulesByName = new HashMap(); + modulesByName.put(m1.getCodeNameBase(), m1); + modulesByName.put(m2.getCodeNameBase(), m2); + Map> providersOf = new HashMap>(); + providersOf.put("foo", Collections.singleton(m1)); + List m1m2 = Arrays.asList(new Module[] {m1, m2}); + List m2m1 = Arrays.asList(new Module[] {m2, m1}); + Map> deps = Util.moduleDependencies(m1m2, modulesByName, providersOf); + assertEquals(Collections.singletonList(m2), deps.get(m1)); + assertEquals(Collections.singletonList(m1), deps.get(m2)); + + try { + Utilities.topologicalSort(m1m2, deps); + } catch (TopologicalSortException ex) { + Set[] arr = ex.unsortableSets(); + assertEquals("One unsortable set", 1, arr.length); + assertEquals("It contains two elements", 2, arr[0].size()); + assertTrue("m1 is there", arr[0].contains(m1)); + assertTrue("m2 is there", arr[0].contains(m2)); + } + Set m1PlusM2 = new HashSet(); + m1PlusM2.add(m1); + m1PlusM2.add(m2); + List toEnable = mgr.simulateEnable(m1PlusM2); + assertEquals("correct result of simulateEnable", Arrays.asList(new Module[] {m2, m1}), toEnable); + toEnable = mgr.simulateEnable(Collections.singleton(m1)); + assertEquals("correct result of simulateEnable #2", Arrays.asList(new Module[] {m2, m1}), toEnable); + toEnable = mgr.simulateEnable(Collections.singleton(m2)); + assertEquals("correct result of simulateEnable #3", Arrays.asList(new Module[] {m2, m1}), toEnable); + mgr.enable(m1PlusM2); + assertEquals(Arrays.asList(new String[] { + "prepare", + "prepare", + "load" + }), installer.actions); + assertEquals(Arrays.asList(new Object[] { + m1, + m2, + Arrays.asList(new Module[] {m1, m2}) + }), installer.args); + Class testclazz = Class.forName("org.prov_foo.Clazz", true, m1.getClassLoader()); + try { + Class.forName("org.prov_foo.Clazz", true, m2.getClassLoader()); + fail("Should not be able to access classes due to prov-req deps only"); + } catch (ClassNotFoundException cnfe) { + // OK, good. + } + installer.clear(); + List toDisable = mgr.simulateDisable(Collections.singleton(m1)); + assertEquals("correct result of simulateDisable", Arrays.asList(new Module[] {m2, m1}), toDisable); + toDisable = mgr.simulateDisable(m1PlusM2); + assertEquals("correct result of simulateDisable #2", Arrays.asList(new Module[] {m2, m1}), toDisable); + mgr.disable(m1PlusM2); + assertFalse(m1.isEnabled()); + assertFalse(m2.isEnabled()); + assertEquals(Arrays.asList(new String[] { + "unload", + "dispose", + "dispose" + }), installer.actions); + assertEquals(Arrays.asList(new Object[] { + Arrays.asList(new Module[] {m2, m1}), + m2, + m1 + }), installer.args); } finally { mgr.mutexPrivileged().exitWriteAccess(); } Index: core/startup/test/unit/src/org/netbeans/core/startup/build.xml =================================================================== RCS file: /shared/data/ccvs/repository/core/startup/test/unit/src/org/netbeans/core/startup/build.xml,v retrieving revision 1.3 diff -u -r1.3 build.xml --- core/startup/test/unit/src/org/netbeans/core/startup/build.xml 16 Sep 2005 13:08:42 -0000 1.3 +++ core/startup/test/unit/src/org/netbeans/core/startup/build.xml 28 Jun 2006 19:55:16 -0000 @@ -152,6 +152,8 @@ + + Index: core/startup/test/unit/src/org/netbeans/core/startup/jars/needs-foo.mf =================================================================== RCS file: core/startup/test/unit/src/org/netbeans/core/startup/jars/needs-foo.mf diff -N core/startup/test/unit/src/org/netbeans/core/startup/jars/needs-foo.mf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/startup/test/unit/src/org/netbeans/core/startup/jars/needs-foo.mf 28 Jun 2006 19:55:16 -0000 @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +OpenIDE-Module: needs_foo +OpenIDE-Module-Name: Needs foo +OpenIDE-Module-Needs: foo Index: core/startup/test/unit/src/org/netbeans/core/startup/jars/prov-foo-depends-needs_foo.mf =================================================================== RCS file: core/startup/test/unit/src/org/netbeans/core/startup/jars/prov-foo-depends-needs_foo.mf diff -N core/startup/test/unit/src/org/netbeans/core/startup/jars/prov-foo-depends-needs_foo.mf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/startup/test/unit/src/org/netbeans/core/startup/jars/prov-foo-depends-needs_foo.mf 28 Jun 2006 19:55:16 -0000 @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: prov_foo +OpenIDE-Module-Name: Provides foo a Depends on Needs_Foo +OpenIDE-Module-Provides: foo +OpenIDE-Module-Module-Dependencies: needs_foo