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.20 diff -u -r1.20 ModuleManager.java --- core/bootstrap/src/org/netbeans/ModuleManager.java 20 Nov 2006 22:33:28 -0000 1.20 +++ core/bootstrap/src/org/netbeans/ModuleManager.java 21 Nov 2006 00:27:28 -0000 @@ -71,7 +71,8 @@ // for any module, set of known failed dependencies or problems, // or null if this has not been computed yet - private final Map>> moduleProblems = new HashMap>>(100); + private final Map>> moduleProblemsWithoutNeeds = new HashMap>>(100); + private final Map>> moduleProblemsWithNeeds = new HashMap>>(100); // modules providing a given requires token; set may never be empty private final Map> providersOf = new HashMap>(25); @@ -696,7 +697,8 @@ // of "soft" problems (interdependencies between modules). // Also clear any "hard" problems associated with this module, as they // may now have been fixed. - moduleProblems.remove(m); + moduleProblemsWithoutNeeds.remove(m); + moduleProblemsWithNeeds.remove(m); firer.change(new ChangeFirer.Change(m, Module.PROP_PROBLEMS, null, null)); clearProblemCache(); firer.fire(); @@ -833,7 +835,7 @@ // Remember that there was a problem with this guy. Module bad = ie.getModule(); if (bad == null) throw new IllegalStateException("Problem with no associated module: " + ie); // NOI18N - Set> probs = moduleProblems.get(bad); + Set> probs = moduleProblemsWithNeeds.get(bad); if (probs == null) throw new IllegalStateException("Were trying to install a module that had never been checked: " + bad); // NOI18N if (! probs.isEmpty()) throw new IllegalStateException("Were trying to install a module that was known to be bad: " + bad); // NOI18N // Record for posterity. @@ -1104,9 +1106,8 @@ // and continue with the others, try to add them too... } } - // XXX sometimes fails, but not reproducible in a unit test. // Logic is that missingDependencies(m) should contain dep in this case. - assert foundOne : "Should have found a nonproblematic provider of " + dep + " among " + providers + " with willEnable=" + willEnable + " mightEnable=" + mightEnable; + assert foundOne || dep.getType() == Dependency.TYPE_RECOMMENDS : "Should have found a nonproblematic provider of " + dep + " among " + providers + " with willEnable=" + willEnable + " mightEnable=" + mightEnable; } // else some other kind of dependency that does not concern us } @@ -1322,77 +1323,23 @@ // Access from Module.getProblems, q.v. // The probed module must not be currently enabled or fixed. Set> missingDependencies(Module probed) { + return missingDependencies(probed, true); + } + Set> missingDependencies(Module probed, boolean withNeeds) { // We need to synchronize here because though this method may be called // only within a read mutex, it can write to moduleProblems. Other places // where moduleProblems are used are write-mutex only and so do not have // to worry about contention. - synchronized (moduleProblems) { - Set> result; - ArrayList check = new ArrayList(); - result = _missingDependencies(probed, check); - LOOP: while (result.isEmpty()) { - for (NeedsCheck needs : check) { - String token = needs.dep.getName(); - Set providers = providersOf.get(token); - if (providers == null) { - if (needs.dep.getType() == Dependency.TYPE_NEEDS) { - // Nobody provides it. This dep failed. - result.add(Union2.createFirst(needs.dep)); - } else { - // TYPE_RECOMMENDS is ok if not present - assert needs.dep.getType() == Dependency.TYPE_RECOMMENDS; - } - } else { - // We have some possible providers. Check that at least one is good. - boolean foundOne = false; - Set possibleModules = new HashSet(); - for (Module other : providers) { - if (other.isEnabled()) { - foundOne = true; - break; - } - } - if (!foundOne) { - for (Module m : providers) { - ArrayList arr = new ArrayList(); - if (!_missingDependencies(m, arr).isEmpty()) { - continue; - } - - if (!arr.isEmpty()) { - check.addAll(arr); - // restart the check - continue LOOP; - } - } - - } - } - } - break LOOP; - } - return result; - } - } - - private static class NeedsCheck { - public final Module module; - public final Dependency dep; - public NeedsCheck(Module m, Dependency d) { - this.module = m; - this.dep = d; - } - public String toString() { - return "(" + module + "," + dep + ")"; // NOI18N - } - } - - private Set> _missingDependencies(Module probed, Collection nonOrderingCheck) { - Set> probs = moduleProblems.get(probed); + synchronized (moduleProblemsWithNeeds) { + Map>> mP = (withNeeds ? moduleProblemsWithNeeds : moduleProblemsWithoutNeeds); + Set> probs = mP.get(probed); if (probs == null) { probs = new HashSet>(8); + if (withNeeds) { + probs.addAll(missingDependencies(probed, false)); + } probs.add(PROBING_IN_PROCESS); - moduleProblems.put(probed, probs); + mP.put(probed, probs); for (Dependency dep : probed.getDependenciesArray()) { if (dep.getType() == Dependency.TYPE_PACKAGE) { // Can't check it in advance. Assume it is OK; if not @@ -1457,7 +1404,8 @@ 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, nonOrderingCheck).isEmpty()) { + if ((!withNeeds && !missingDependencies(other, false).isEmpty()) || + (withNeeds && !isAlmostEmpty(missingDependencies(other, true)))) { // 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 @@ -1476,7 +1424,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 || (withNeeds && 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(); @@ -1488,10 +1436,14 @@ // We have some possible providers. Check that at least one is good. boolean foundOne = false; for (Module other : providers) { + if (foundOne) { + break; + } if (other.isEnabled()) { foundOne = true; } else { - if (_missingDependencies(other, nonOrderingCheck).isEmpty()) { + if ((!withNeeds && missingDependencies(other, false).isEmpty()) || + (withNeeds && isAlmostEmpty(missingDependencies(other, true)))) { // See comment above for regular module deps // re. use of PROBING_IN_PROCESS. foundOne = true; @@ -1503,10 +1455,7 @@ probs.add(Union2.createFirst(dep)); } } - } else if (dep.getType() == Dependency.TYPE_NEEDS || dep.getType() == Dependency.TYPE_RECOMMENDS) { - nonOrderingCheck.add(new NeedsCheck(probed, dep)); - } else { - assert dep.getType() == Dependency.TYPE_JAVA; + } else if (dep.getType() == Dependency.TYPE_JAVA) { // Java dependency. Fixed for whole VM session, safe to check once and keep. if (! Util.checkJavaDependency(dep)) { // Bad. @@ -1517,6 +1466,10 @@ probs.remove(PROBING_IN_PROCESS); } return probs; + } + } + private static boolean isAlmostEmpty(Set> probs) { + return probs.isEmpty() || probs.equals(Collections.singleton(PROBING_IN_PROCESS)); } /** Forget about any possible "soft" problems there might have been. @@ -1530,7 +1483,11 @@ * return a different result). */ private void clearProblemCache() { - Iterator>>> it = moduleProblems.entrySet().iterator(); + clearProblemCache(moduleProblemsWithoutNeeds); + clearProblemCache(moduleProblemsWithNeeds); + } + private void clearProblemCache(Map>> mP) { + Iterator>>> it = mP.entrySet().iterator(); while (it.hasNext()) { Map.Entry>> entry = it.next(); Module m = entry.getKey(); 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.11 diff -u -r1.11 ModuleManagerTest.java --- core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java 20 Nov 2006 22:33:29 -0000 1.11 +++ core/startup/test/unit/src/org/netbeans/core/startup/ModuleManagerTest.java 21 Nov 2006 00:27:29 -0000 @@ -1013,10 +1013,14 @@ mgr.mutexPrivileged().enterWriteAccess(); Module m1 = createModule(mgr, "OpenIDE-Module: m1\nOpenIDE-Module-Needs: tok\n"); Module m2 = createModule(mgr, "OpenIDE-Module: m2\nOpenIDE-Module-Module-Dependencies: m1\n"); - //assertEquals(1, m2.getProblems().size()); assertEquals(Collections.emptyList(), mgr.simulateEnable(Collections.singleton(m2))); Module m3 = createModule(mgr, "OpenIDE-Module: m3\nOpenIDE-Module-Provides: tok\n"); assertEquals(new HashSet(Arrays.asList(m1, m2, m3)), new HashSet(mgr.simulateEnable(Collections.singleton(m2)))); + mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + m1 = createModule(mgr, "OpenIDE-Module: m1\nOpenIDE-Module-Requires: tok\n"); + m2 = createModule(mgr, "OpenIDE-Module: m2\nOpenIDE-Module-Module-Dependencies: m1\nOpenIDE-Module-Provides: tok\n"); + assertEquals(Collections.emptyList(), mgr.simulateEnable(Collections.singleton(m2))); } public void testSimpleProvNeeds() throws Exception { @@ -1339,6 +1343,7 @@ } private void doRecommendsWithAProviderWithoutAProvider(boolean recommends) throws Exception { + // ========= XXX recommends parameter is unused! =========== FakeModuleInstaller installer = new FakeModuleInstaller(); FakeEvents ev = new FakeEvents(); ModuleManager mgr = new ModuleManager(installer, ev); @@ -1366,7 +1371,7 @@ assertEquals(null, deps.get(m2)); List toEnable = mgr.simulateEnable(new HashSet(m2List)); - assertEquals("cannot enable while provider of bla is missing", Collections.emptyList(), toEnable); + assertEquals("cannot enable while provider of bla is missing", Collections.singletonList(m2), toEnable); // try {