diff options
author | Thomas Watson | 2016-05-04 13:33:24 +0000 |
---|---|---|
committer | Thomas Watson | 2016-06-13 17:41:14 +0000 |
commit | 7d69bcd5fbcd10167517dec2e8071f763600629c (patch) | |
tree | d5b306e4dda01a0f1e4965c695e3e662132b7f99 /bundles/org.eclipse.osgi/felix/src/org/apache/felix | |
parent | e4c242ee27ad748eb7c1545070ba98a1d88f6afb (diff) | |
download | rt.equinox.framework-7d69bcd5fbcd10167517dec2e8071f763600629c.tar.gz rt.equinox.framework-7d69bcd5fbcd10167517dec2e8071f763600629c.tar.xz rt.equinox.framework-7d69bcd5fbcd10167517dec2e8071f763600629c.zip |
Bug 493385 - Update to latest resolver code
Clean up the felix resolver code
Maintain state of resolve process in ResolveSession instead of
passing loads of arguments all over the place.
Extract out common code for resolving between dynamic and regurlar
resolution.
Change-Id: I2418fa87b472e35643e5da78a617fc6135ca10ad
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
Diffstat (limited to 'bundles/org.eclipse.osgi/felix/src/org/apache/felix')
-rwxr-xr-x | bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java | 208 | ||||
-rwxr-xr-x | bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java | 849 |
2 files changed, 474 insertions, 583 deletions
diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java index bfa80c6ec..9dcf5a89e 100755 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java @@ -18,34 +18,14 @@ */ package org.apache.felix.resolver; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; - -import org.apache.felix.resolver.util.CopyOnWriteList; -import org.apache.felix.resolver.util.CopyOnWriteSet; -import org.apache.felix.resolver.util.OpenHashMap; -import org.apache.felix.resolver.util.OpenHashMapList; -import org.apache.felix.resolver.util.OpenHashMapSet; -import org.apache.felix.resolver.util.ShadowList; +import org.apache.felix.resolver.ResolverImpl.PermutationType; +import org.apache.felix.resolver.ResolverImpl.ResolveSession; +import org.apache.felix.resolver.util.*; import org.osgi.framework.Version; -import org.osgi.framework.namespace.HostNamespace; -import org.osgi.framework.namespace.IdentityNamespace; -import org.osgi.framework.namespace.PackageNamespace; -import org.osgi.resource.Capability; -import org.osgi.resource.Requirement; -import org.osgi.resource.Resource; -import org.osgi.resource.Wire; -import org.osgi.resource.Wiring; +import org.osgi.framework.namespace.*; +import org.osgi.resource.*; import org.osgi.service.resolver.HostedCapability; import org.osgi.service.resolver.ResolveContext; @@ -63,7 +43,7 @@ class Candidates } } - private final Set<Resource> m_mandatoryResources; + private final ResolveSession m_session; // Maps a capability to requirements that match it. private final OpenHashMapSet<Capability, Requirement> m_dependentMap; // Maps a requirement to the capability it matches. @@ -74,8 +54,6 @@ class Candidates // Map used when populating candidates to hold intermediate and final results. private final OpenHashMap<Resource, PopulateResult> m_populateResultCache; - private final Map<Resource, Boolean> m_validOnDemandResources; - private final Map<Capability, Requirement> m_subtitutableMap; private final OpenHashMapSet<Requirement, Capability> m_delta; @@ -84,21 +62,19 @@ class Candidates * Private copy constructor used by the copy() method. */ private Candidates( - Set<Resource> mandatoryResources, + ResolveSession session, OpenHashMapSet<Capability, Requirement> dependentMap, OpenHashMapList<Requirement, Capability> candidateMap, Map<Resource, WrappedResource> wrappedHosts, OpenHashMap<Resource, PopulateResult> populateResultCache, - Map<Resource, Boolean> onDemandResources, Map<Capability, Requirement> substitutableMap, OpenHashMapSet<Requirement, Capability> delta) { - m_mandatoryResources = mandatoryResources; + m_session = session; m_dependentMap = dependentMap; m_candidateMap = candidateMap; m_allWrappedHosts = wrappedHosts; m_populateResultCache = populateResultCache; - m_validOnDemandResources = onDemandResources; m_subtitutableMap = substitutableMap; m_delta = delta; } @@ -106,14 +82,13 @@ class Candidates /** * Constructs an empty Candidates object. */ - public Candidates(Map<Resource, Boolean> validOnDemandResources) + public Candidates(ResolveSession session) { - m_mandatoryResources = new HashSet<Resource>(); + m_session = session; m_dependentMap = new OpenHashMapSet<Capability, Requirement>(); m_candidateMap = new OpenHashMapList<Requirement, Capability>(); m_allWrappedHosts = new HashMap<Resource, WrappedResource>(); m_populateResultCache = new OpenHashMap<Resource, PopulateResult>(); - m_validOnDemandResources = validOnDemandResources; m_subtitutableMap = new OpenHashMap<Capability, Requirement>(); m_delta = new OpenHashMapSet<Requirement, Capability>(3); } @@ -123,35 +98,47 @@ class Candidates return m_populateResultCache.size(); } - public Map<Resource, Resource> getHosts() + public Map<Resource, Resource> getRootHosts() { - Map<Resource, Resource> hosts = new HashMap<Resource, Resource>(); - for (Resource res : m_mandatoryResources) + Map<Resource, Resource> hosts = new LinkedHashMap<Resource, Resource>(); + for (Resource res : m_session.getMandatoryResources()) { - if (res instanceof WrappedResource) - { - res = ((WrappedResource) res).getDeclaredResource(); - } - if (!Util.isFragment(res)) - { - hosts.put(res, getWrappedHost(res)); - } + addHost(res, hosts); } - for (Capability cap : m_dependentMap.keySet()) + + for (Resource res : m_session.getOptionalResources()) { - Resource res = cap.getResource(); - if (res instanceof WrappedResource) - { - res = ((WrappedResource) res).getDeclaredResource(); - } - if (!Util.isFragment(res)) - { - hosts.put(res, getWrappedHost(res)); + if (isPopulated(res)) { + addHost(res, hosts); } } + return hosts; } + private void addHost(Resource res, Map<Resource, Resource> hosts) { + if (res instanceof WrappedResource) + { + res = ((WrappedResource) res).getDeclaredResource(); + } + if (!Util.isFragment(res)) + { + hosts.put(res, getWrappedHost(res)); + } else { + Requirement hostReq = res.getRequirements(HostNamespace.HOST_NAMESPACE).get(0); + Capability hostCap = getFirstCandidate(hostReq); + // If the resource is an already resolved fragment and can not + // be attached to new hosts, there will be no matching host, + // so ignore this resource + if (hostCap != null) { + res = getWrappedHost(hostCap.getResource()); + if (res instanceof WrappedResource) { + hosts.put(((WrappedResource) res).getDeclaredResource(), res); + } + } + } + } + /** * Returns the delta which is the differences in the candidates from the * original Candidates permutation. @@ -162,14 +149,10 @@ class Candidates return m_delta; } - public void addMandatoryResources(Collection<Resource> resources) - { - m_mandatoryResources.addAll(resources); - } - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - public ResolutionError populate(ResolveContext rc, Collection<Resource> resources) + public void populate(Collection<Resource> resources) { + ResolveContext rc = m_session.getContext(); Set<Resource> toRemove = new HashSet<Resource>(); LinkedList<Resource> toPopulate = new LinkedList<Resource>(resources); while (!toPopulate.isEmpty()) @@ -201,14 +184,7 @@ class Candidates Collection<Resource> ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource); for (Resource fragment : ondemandFragments) { - Boolean valid = m_validOnDemandResources.get(fragment); - if (valid == null) - { - // Mark this resource as a valid on demand resource - m_validOnDemandResources.put(fragment, Boolean.TRUE); - valid = Boolean.TRUE; - } - if (valid) + if (m_session.isValidOnDemandResource(fragment)) { // This resource is a valid on demand resource; // populate it now, consider it optional @@ -220,13 +196,13 @@ class Candidates } // We have a requirement to process Requirement requirement = result.remaining.remove(0); - if (!isEffective(rc, requirement)) + if (!isEffective(requirement)) { continue; } List<Capability> candidates = rc.findProviders(requirement); LinkedList<Resource> newToPopulate = new LinkedList<Resource>(); - ResolutionError thrown = processCandidates(rc, newToPopulate, requirement, candidates); + ResolutionError thrown = processCandidates(newToPopulate, requirement, candidates); if (candidates.isEmpty() && !Util.isOptional(requirement)) { if (Util.isFragment(resource) && rc.getWirings().containsKey(resource)) @@ -261,11 +237,10 @@ class Candidates iterator.remove(); remove(resource, toRemove); } - return null; } - private boolean isEffective(ResolveContext rc, Requirement req) { - if (!rc.isEffective(req)) { + private boolean isEffective(Requirement req) { + if (!m_session.getContext().isEffective(req)) { return false; } String res = req.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE); @@ -352,7 +327,7 @@ class Candidates private static final int SUBSTITUTED = 2; private static final int EXPORTED = 3; - ResolutionError checkSubstitutes(List<Candidates> importPermutations) + ResolutionError checkSubstitutes() { OpenHashMap<Capability, Integer> substituteStatuses = new OpenHashMap<Capability, Integer>(m_subtitutableMap.size()); for (Capability substitutable : m_subtitutableMap.keySet()) @@ -373,7 +348,7 @@ class Candidates Requirement substitutedReq = m_subtitutableMap.get(substituteStatus.getKey()); if (substitutedReq != null) { - permutateIfNeeded(substitutedReq, importPermutations); + m_session.permutateIfNeeded(PermutationType.SUBSTITUTE, substitutedReq, this); } Set<Requirement> dependents = m_dependentMap.get(substituteStatus.getKey()); if (dependents != null) @@ -477,58 +452,53 @@ class Candidates return false; } - public ResolutionError populateDynamic( - ResolveContext rc, Resource resource, - Requirement req, List<Capability> candidates) + public ResolutionError populateDynamic() { - // Record the revision associated with the dynamic require - // as a mandatory revision. - m_mandatoryResources.add(resource); // Process the candidates, removing any candidates that // cannot resolve. // TODO: verify the two following statements LinkedList<Resource> toPopulate = new LinkedList<Resource>(); - ResolutionError rethrow = processCandidates(rc, toPopulate, req, candidates); + ResolutionError rethrow = processCandidates(toPopulate, m_session.getDynamicRequirement(), m_session.getDynamicCandidates()); // Add the dynamic imports candidates. // Make sure this is done after the call to processCandidates since we want to ensure // fragment candidates are properly hosted before adding the candidates list which makes a copy - addCandidates(req, candidates); + addCandidates(m_session.getDynamicRequirement(), m_session.getDynamicCandidates()); - populate(rc, toPopulate); + populate(toPopulate); - CopyOnWriteList<Capability> caps = m_candidateMap.get(req); + CopyOnWriteList<Capability> caps = m_candidateMap.get(m_session.getDynamicRequirement()); if (caps != null) { - candidates.retainAll(caps); + m_session.getDynamicCandidates().retainAll(caps); } else { - candidates.clear(); + m_session.getDynamicCandidates().clear(); } - if (candidates.isEmpty()) + if (m_session.getDynamicCandidates().isEmpty()) { if (rethrow == null) { - rethrow = new DynamicImportFailed(req); + rethrow = new DynamicImportFailed(m_session.getDynamicRequirement()); } return rethrow; } PopulateResult result = new PopulateResult(); result.success = true; - m_populateResultCache.put(resource, result); + m_populateResultCache.put(m_session.getDynamicHost(), result); return null; } private ResolutionError processCandidates( - ResolveContext rc, LinkedList<Resource> toPopulate, Requirement req, List<Capability> candidates) { + ResolveContext rc = m_session.getContext(); // Get satisfying candidates and populate their candidates if necessary. ResolutionError rethrow = null; Set<Capability> fragmentCands = null; @@ -551,6 +521,14 @@ class Candidates fragmentCands.add(candCap); } + // Do a sanity check incase the resolve context tries to attach + // a fragment to an already resolved host capability + if (HostNamespace.HOST_NAMESPACE.equals(req.getNamespace())) { + if (rc.getWirings().containsKey(candCap.getResource())) { + itCandCap.remove(); + continue; + } + } // If the candidate revision is a fragment, then always attempt // to populate candidates for its dependency, since it must be // attached to a host to be used. Otherwise, if the candidate @@ -780,7 +758,7 @@ class Candidates * @return ResolutionError if the removal of any unselected fragments * result in the root module being unable to resolve. */ - public ResolutionError prepare(ResolveContext rc) + public ResolutionError prepare() { // Maps a host capability to a map containing its potential fragments; // the fragment map maps a fragment symbolic name to a map that maps @@ -931,7 +909,7 @@ class Candidates original.remove(removeIdx); cands.remove(removeIdx); } - int insertIdx = rc.insertHostedCapability( + int insertIdx = m_session.getContext().insertHostedCapability( original, new SimpleHostedCapability( hostResource.getDeclaredResource(), @@ -971,7 +949,7 @@ class Candidates // Lastly, verify that all mandatory revisions are still // populated, since some might have become unresolved after // selecting fragments/singletons. - for (Resource resource : m_mandatoryResources) + for (Resource resource : m_session.getMandatoryResources()) { if (!isPopulated(resource)) { @@ -1154,12 +1132,11 @@ class Candidates public Candidates copy() { return new Candidates( - m_mandatoryResources, + m_session, m_dependentMap.deepClone(), m_candidateMap.deepClone(), m_allWrappedHosts, m_populateResultCache, - m_validOnDemandResources, m_subtitutableMap, m_delta.deepClone()); } @@ -1206,14 +1183,15 @@ class Candidates System.out.println("=== END CANDIDATE MAP ==="); } - public void permutate(Requirement req, List<Candidates> permutations) + public Candidates permutate(Requirement req) { if (!Util.isMultiple(req) && canRemoveCandidate(req)) { Candidates perm = copy(); perm.removeFirstCandidate(req); - permutations.add(perm); + return perm; } + return null; } public boolean canRemoveCandidate(Requirement req) @@ -1222,36 +1200,6 @@ class Candidates return ((candidates != null) && (candidates.size() > 1 || Util.isOptional(req))); } - public void permutateIfNeeded(Requirement req, List<Candidates> permutations) - { - List<Capability> candidates = m_candidateMap.get(req); - if ((candidates != null) && (candidates.size() > 1)) - { - // Check existing permutations to make sure we haven't - // already permutated this requirement. This check for - // duplicate permutations is simplistic. It assumes if - // there is any permutation that contains a different - // initial candidate for the requirement in question, - // then it has already been permutated. - boolean permutated = false; - for (Candidates existingPerm : permutations) - { - List<Capability> existingPermCands = existingPerm.m_candidateMap.get(req); - if (existingPermCands != null && !existingPermCands.get(0).equals(candidates.get(0))) - { - permutated = true; - break; - } - } - // If we haven't already permutated the existing - // import, do so now. - if (!permutated) - { - permutate(req, permutations); - } - } - } - static class DynamicImportFailed extends ResolutionError { private final Requirement requirement; diff --git a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java index ded683fd7..b435aabae 100755 --- a/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java +++ b/bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/ResolverImpl.java @@ -18,43 +18,16 @@ */ package org.apache.felix.resolver; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.security.*; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; - import org.apache.felix.resolver.util.ArrayMap; import org.apache.felix.resolver.util.OpenHashMap; -import org.osgi.framework.namespace.BundleNamespace; -import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; -import org.osgi.framework.namespace.HostNamespace; -import org.osgi.framework.namespace.IdentityNamespace; -import org.osgi.framework.namespace.PackageNamespace; -import org.osgi.resource.Capability; -import org.osgi.resource.Namespace; -import org.osgi.resource.Requirement; -import org.osgi.resource.Resource; -import org.osgi.resource.Wire; -import org.osgi.resource.Wiring; -import org.osgi.service.resolver.HostedCapability; -import org.osgi.service.resolver.ResolutionException; -import org.osgi.service.resolver.ResolveContext; -import org.osgi.service.resolver.Resolver; +import org.osgi.framework.namespace.*; +import org.osgi.resource.*; +import org.osgi.service.resolver.*; public class ResolverImpl implements Resolver { @@ -69,29 +42,65 @@ public class ResolverImpl implements Resolver private final Executor m_executor; + enum PermutationType { + USES, + IMPORT, + SUBSTITUTE + } + // Note this class is not thread safe. // Only use in the context of a single thread. - class ResolveSession + static class ResolveSession { // Holds the resolve context for this session private final ResolveContext m_resolveContext; + private final Collection<Resource> m_mandatoryResources; + private final Collection<Resource> m_optionalResources; + private final Resource m_dynamicHost; + private final Requirement m_dynamicReq; + private final List<Capability> m_dynamicCandidates; + // keeps track of valid on demand fragments that we have seen. + // a null value or TRUE indicate it is valid + Map<Resource, Boolean> m_validOnDemandResources = new HashMap<Resource, Boolean>(0); // Holds candidate permutations based on permutating "uses" chains. // These permutations are given higher priority. - private final List<Candidates> m_usesPermutations = new ArrayList<Candidates>(); + private final List<Candidates> m_usesPermutations = new LinkedList<Candidates>(); + private int m_usesIndex = 0; // Holds candidate permutations based on permutating requirement candidates. // These permutations represent backtracking on previous decisions. - private final List<Candidates> m_importPermutations = new ArrayList<Candidates>(); + private final List<Candidates> m_importPermutations = new LinkedList<Candidates>(); + private int m_importIndex = 0; + // Holds candidate permutations based on substituted packages + private final List<Candidates> m_substPermutations = new LinkedList<Candidates>(); + private int m_substituteIndex = 0; // Holds candidate permutations based on removing candidates that satisfy // multiple cardinality requirements. // This permutation represents a permutation that is consistent because we have // removed the offending capabilities private Candidates m_multipleCardCandidates = null; - + // The delta is used to detect that we have already processed this particular permutation + private final Set<Object> m_processedDeltas = new HashSet<Object>(); + private final Executor m_executor; + private final Set<Requirement> m_mutated = new HashSet<Requirement>(); + private final Set<Requirement> m_sub_mutated = new HashSet<Requirement>(); private final ConcurrentMap<String, List<String>> m_usesCache = new ConcurrentHashMap<String, List<String>>(); + private ResolutionError m_currentError; - ResolveSession(ResolveContext resolveContext) + ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates) { m_resolveContext = resolveContext; + m_executor = executor; + m_dynamicHost = dynamicHost; + m_dynamicReq = dynamicReq; + m_dynamicCandidates = dynamicCandidates; + if (m_dynamicHost != null) { + m_mandatoryResources = Collections.singletonList(dynamicHost); + m_optionalResources = Collections.emptyList(); + } else { + // Make copies of arguments in case we want to modify them. + m_mandatoryResources = new ArrayList<Resource>(resolveContext.getMandatoryResources()); + m_optionalResources = new ArrayList<Resource>(resolveContext.getOptionalResources()); + } } Candidates getMultipleCardCandidates() @@ -99,19 +108,198 @@ public class ResolverImpl implements Resolver return m_multipleCardCandidates; } - void setMultipleCardCandidates(Candidates multipleCardCandidates) - { - m_multipleCardCandidates = multipleCardCandidates; - } - ResolveContext getContext() { return m_resolveContext; } - public ConcurrentMap<String, List<String>> getUsesCache() { + ConcurrentMap<String, List<String>> getUsesCache() { return m_usesCache; } + + void permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation) { + List<Capability> candidates = permutation.getCandidates(req); + if ((candidates != null) && (candidates.size() > 1)) + { + if ((type == PermutationType.SUBSTITUTE)) { + if (!m_sub_mutated.add(req)) { + return; + } + } else if (!m_mutated.add(req)) { + return; + } + // If we haven't already permutated the existing + // import, do so now. + addPermutation(type, permutation.permutate(req)); + } + } + + private void clearMutateIndexes() { + m_usesIndex = 0; + m_importIndex = 0; + m_substituteIndex = 0; + m_mutated.clear(); + // NOTE: m_sub_mutated is never cleared. + // It is unclear if even more permutations based on a substitutions will ever help. + // Being safe and reducing extra permutations until we get a scenario that proves + // more permutations would really help. + } + + void addPermutation(PermutationType type, Candidates permutation) { + if (permutation != null) + { + List<Candidates> typeToAddTo = null; + try { + switch (type) { + case USES : + typeToAddTo = m_usesPermutations; + m_usesPermutations.add(m_usesIndex++, permutation); + break; + case IMPORT : + typeToAddTo = m_importPermutations; + m_importPermutations.add(m_importIndex++, permutation); + break; + case SUBSTITUTE : + typeToAddTo = m_substPermutations; + m_substPermutations.add(m_substituteIndex++, permutation); + break; + default : + throw new IllegalArgumentException("Unknown permitation type: " + type); + } + } catch (IndexOutOfBoundsException e) { + // just a safeguard, this really should never happen + typeToAddTo.add(permutation); + } + } + } + + Candidates getNextPermutation() { + Candidates next = null; + do { + if (!m_usesPermutations.isEmpty()) + { + next = m_usesPermutations.remove(0); + } + else if (!m_importPermutations.isEmpty()) + { + next = m_importPermutations.remove(0); + } + else if (!m_substPermutations.isEmpty()) + { + next = m_substPermutations.remove(0); + } + else { + return null; + } + } + while(!m_processedDeltas.add(next.getDelta())); + // Null out each time a new permutation is attempted. + // We only use this to store a valid permutation which is a + // delta of the current permutation. + m_multipleCardCandidates = null; + // clear mutateIndexes also so we insert new permutations + // based of this permutation as a higher priority + clearMutateIndexes(); + return next; + } + + void clearPermutations() { + m_usesPermutations.clear(); + m_importPermutations.clear(); + m_substPermutations.clear(); + m_multipleCardCandidates = null; + m_processedDeltas.clear(); + m_currentError = null; + } + + boolean checkMultiple( + UsedBlames usedBlames, + Blame usedBlame, + Candidates permutation) + { + // Check the root requirement to see if it is a multiple cardinality + // requirement. + List<Capability> candidates = null; + Requirement req = usedBlame.m_reqs.get(0); + if (Util.isMultiple(req)) + { + // Create a copy of the current permutation so we can remove the + // candidates causing the blame. + if (m_multipleCardCandidates == null) + { + m_multipleCardCandidates = permutation.copy(); + } + // Get the current candidate list and remove all the offending root + // cause candidates from a copy of the current permutation. + candidates = m_multipleCardCandidates.clearCandidates(req, usedBlames.getRootCauses(req)); + } + // We only are successful if there is at least one candidate left + // for the requirement + return (candidates != null) && !candidates.isEmpty(); + } + + long getPermutationCount() { + return m_usesPermutations.size() + m_importPermutations.size() + m_substPermutations.size(); + } + + Executor getExecutor() { + return m_executor; + } + + ResolutionError getCurrentError() { + return m_currentError; + } + + void setCurrentError(ResolutionError error) { + this.m_currentError = error; + } + + boolean isDynamic() { + return m_dynamicHost != null; + } + + Collection<Resource> getMandatoryResources() { + return m_mandatoryResources; + } + + Collection<Resource> getOptionalResources() { + return m_optionalResources; + } + + Resource getDynamicHost() { + return m_dynamicHost; + } + + Requirement getDynamicRequirement() { + return m_dynamicReq; + } + + List<Capability> getDynamicCandidates() { + return m_dynamicCandidates; + } + + public boolean isValidOnDemandResource(Resource fragment) { + Boolean valid = m_validOnDemandResources.get(fragment); + if (valid == null) + { + // Mark this resource as a valid on demand resource + m_validOnDemandResources.put(fragment, Boolean.TRUE); + valid = Boolean.TRUE; + } + return valid; + } + + public boolean invalidateOnDemandResource(Resource faultyResource) { + Boolean valid = m_validOnDemandResources.get(faultyResource); + if (valid != null && valid) + { + // This was an ondemand resource. + // Invalidate it and try again. + m_validOnDemandResources.put(faultyResource, Boolean.FALSE); + return true; + } + return false; + } } public ResolverImpl(Logger logger) @@ -182,16 +370,12 @@ public class ResolverImpl implements Resolver public Map<Resource, List<Wire>> resolve(ResolveContext rc, Executor executor) throws ResolutionException { - ResolveSession session = new ResolveSession(rc); - Map<Resource, List<Wire>> wireMap = - new HashMap<Resource, List<Wire>>(); + ResolveSession session = new ResolveSession(rc, executor, null, null, null); + return doResolve(session); + } - // Make copies of arguments in case we want to modify them. - Collection<Resource> mandatoryResources = new ArrayList<Resource>(rc.getMandatoryResources()); - Collection<Resource> optionalResources = new ArrayList<Resource>(rc.getOptionalResources()); - // keeps track of valid on demand fragments that we have seen. - // a null value or TRUE indicate it is valid - Map<Resource, Boolean> validOnDemandResources = new HashMap<Resource, Boolean>(0); + private Map doResolve(ResolveSession session) throws ResolutionException { + Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>(); boolean retry; do @@ -199,212 +383,40 @@ public class ResolverImpl implements Resolver retry = false; try { - // Create object to hold all candidates. - Candidates allCandidates = new Candidates(validOnDemandResources); - - List<Resource> mandatory = new ArrayList<Resource>(); - List<Resource> toPopulate = new ArrayList<Resource>(); - - // Populate mandatory resources; since these are mandatory - // resources, failure throws a resolve exception. - for (Resource resource : mandatoryResources) - { - if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null)) - { - mandatory.add(resource); - toPopulate.add(resource); - } - } - // Populate optional resources; since these are optional - // resources, failure does not throw a resolve exception. - for (Resource resource : optionalResources) - { - if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null)) - { - toPopulate.add(resource); - } - } - - allCandidates.addMandatoryResources(mandatory); - allCandidates.populate(rc, toPopulate); - - // Merge any fragments into hosts. - ResolutionError rethrow = allCandidates.prepare(rc); - if (rethrow != null) - { - throw rethrow.toException(); - } - - // Create a combined list of populated resources; for - // optional resources. We do not need to consider ondemand - // fragments, since they will only be pulled in if their - // host is already present. - Set<Resource> allResources = - new LinkedHashSet<Resource>(mandatoryResources); - for (Resource resource : optionalResources) - { - if (allCandidates.isPopulated(resource)) - { - allResources.add(resource); - } - } - - // Holds candidate permutations based on permutating "uses" chains. - // These permutations are given higher priority. - List<Candidates> usesPermutations = new ArrayList<Candidates>(); - // Holds candidate permutations based on permutating requirement candidates. - // These permutations represent backtracking on previous decisions. - List<Candidates> importPermutations = new ArrayList<Candidates>(); - // Holds candidate permutations based on substituted packages - List<Candidates> substPermutations = new ArrayList<Candidates>(); - - // Record the initial candidate permutation. - usesPermutations.add(allCandidates); - - // If a populated resource is a fragment, then its host - // must ultimately be verified, so store its host requirement - // to use for package space calculation. - Map<Resource, Requirement> hostReqs = new HashMap<Resource, Requirement>(); - for (Resource resource : allResources) - { - if (Util.isFragment(resource)) - { - hostReqs.put( - resource, - resource.getRequirements(HostNamespace.HOST_NAMESPACE).get(0)); - } + getInitialCandidates(session); + if (session.getCurrentError() != null) { + throw session.getCurrentError().toException(); } - Set<Object> processedDeltas = new HashSet<Object>(); - Map<Resource, ResolutionError> faultyResources = null; - do - { - if (!usesPermutations.isEmpty()) - { - allCandidates = usesPermutations.remove(0); - } - else if (!importPermutations.isEmpty()) - { - allCandidates = importPermutations.remove(0); - } - else if (!substPermutations.isEmpty()) - { - allCandidates = substPermutations.remove(0); - } - else - { - break; - } - - // The delta is used to detect that we have already processed this particular permutation - if (!processedDeltas.add(allCandidates.getDelta())) - { - // This permutation has already been tried - // Don't try it again - continue; - } - - // Null out each time a new permutation is attempted. - // We only use this to store a valid permutation which is a - // delta of the current permutation. - session.setMultipleCardCandidates(null); - -//allCandidates.dump(); - - rethrow = allCandidates.checkSubstitutes(substPermutations); - if (rethrow != null) - { - continue; - } - - // Compute the list of hosts - Map<Resource, Resource> hosts = new OpenHashMap<Resource, Resource>(); - for (Resource resource : allResources) - { - // If we are resolving a fragment, then get its - // host candidate and verify it instead. - Requirement hostReq = hostReqs.get(resource); - if (hostReq != null) - { - Capability hostCap = allCandidates.getFirstCandidate(hostReq); - // If the resource is an already resolved fragment and can not - // be attached to new hosts, there will be no matching host, - // so ignore this resource - if (hostCap == null) - { - continue; - } - resource = hostCap.getResource(); - } - hosts.put(resource, allCandidates.getWrappedHost(resource)); - } - - Map<Resource, ResolutionError> currentFaultyResources = new HashMap<Resource, ResolutionError>(); - - List<Candidates> newUses = new ArrayList<Candidates>(); - List<Candidates> newImports = new ArrayList<Candidates>(); - - rethrow = checkConsistency( - executor, - session, - newUses, - newImports, - allCandidates, - currentFaultyResources, - hosts, - false); - - usesPermutations.addAll(0, newUses); - importPermutations.addAll(0, newImports); - - if (!currentFaultyResources.isEmpty()) - { - if (faultyResources == null) - { - faultyResources = currentFaultyResources; - } - else if (faultyResources.size() > currentFaultyResources.size()) - { - // save the optimal faultyResources which has less - faultyResources = currentFaultyResources; - } - } - } - while (rethrow != null); + Map<Resource, ResolutionError> faultyResources = new HashMap<Resource, ResolutionError>(); + Candidates allCandidates = findValidCandidates(session, faultyResources); // If there is a resolve exception, then determine if an // optionally resolved resource is to blame (typically a fragment). // If so, then remove the optionally resolved resolved and try - // again; otherwise, rethrow the resolve exception. - if (rethrow != null) + // again; otherwise, m_currentError the resolve exception. + if (session.getCurrentError() != null) { - if (faultyResources != null) + Set<Resource> resourceKeys = faultyResources.keySet(); + retry = (session.getOptionalResources().removeAll(resourceKeys)); + for (Resource faultyResource : resourceKeys) { - Set<Resource> resourceKeys = faultyResources.keySet(); - retry = (optionalResources.removeAll(resourceKeys)); - for (Resource faultyResource : resourceKeys) - { - Boolean valid = validOnDemandResources.get(faultyResource); - if (valid != null && valid) - { - // This was an ondemand resource. - // Invalidate it and try again. - validOnDemandResources.put(faultyResource, Boolean.FALSE); - retry = true; - } - } - // log all the resolution exceptions for the uses constraint violations - for (Map.Entry<Resource, ResolutionError> usesError : faultyResources.entrySet()) + if (session.invalidateOnDemandResource(faultyResource)) { - m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue()); + retry = true; } } + // log all the resolution exceptions for the uses constraint violations + for (Map.Entry<Resource, ResolutionError> usesError : faultyResources.entrySet()) + { + m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue()); + } if (!retry) { - throw rethrow.toException(); + throw session.getCurrentError().toException(); } } - // If there is no exception to rethrow, then this was a clean + // If there is no exception to m_currentError, then this was a clean // resolve, so populate the wire map. else { @@ -415,32 +427,23 @@ public class ResolverImpl implements Resolver // Use the consistent permutation allCandidates = session.getMultipleCardCandidates(); } - for (Resource resource : allResources) + if (session.isDynamic() ) { - Resource target = resource; - - // If we are resolving a fragment, then we - // actually want to populate its host's wires. - Requirement hostReq = hostReqs.get(resource); - if (hostReq != null) + wireMap = populateDynamicWireMap(session.getContext(), + session.getDynamicHost(), session.getDynamicRequirement(), + wireMap, allCandidates); + } + else + { + for (Resource resource : allCandidates.getRootHosts().keySet()) { - Capability hostCap = allCandidates.getFirstCandidate(hostReq); - // If the resource is an already resolved fragment and can not - // be attached to new hosts, there will be no matching host, - // so ignore this resource - if (hostCap == null) + if (allCandidates.isPopulated(resource)) { - continue; + wireMap = + populateWireMap( + session.getContext(), allCandidates.getWrappedHost(resource), + wireMap, allCandidates); } - target = hostCap.getResource(); - } - - if (allCandidates.isPopulated(target)) - { - wireMap = - populateWireMap( - rc, allCandidates.getWrappedHost(target), - wireMap, allCandidates); } } } @@ -448,7 +451,7 @@ public class ResolverImpl implements Resolver finally { // Always clear the state. - session.setMultipleCardCandidates(null); + session.clearPermutations(); } } while (retry); @@ -456,28 +459,123 @@ public class ResolverImpl implements Resolver return wireMap; } + private void getInitialCandidates(ResolveSession session) { + // Create object to hold all candidates. + Candidates initialCandidates; + if (session.isDynamic()) { + // Create all candidates pre-populated with the single candidate set + // for the resolving dynamic import of the host. + initialCandidates = new Candidates(session); + ResolutionError prepareError = initialCandidates.populateDynamic(); + if (prepareError != null) { + session.setCurrentError(prepareError); + return; + } + } else { + List<Resource> toPopulate = new ArrayList<Resource>(); + + // Populate mandatory resources; since these are mandatory + // resources, failure throws a resolve exception. + for (Resource resource : session.getMandatoryResources()) + { + if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null)) + { + toPopulate.add(resource); + } + } + // Populate optional resources; since these are optional + // resources, failure does not throw a resolve exception. + for (Resource resource : session.getOptionalResources()) + { + if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null)) + { + toPopulate.add(resource); + } + } + + initialCandidates = new Candidates(session); + initialCandidates.populate(toPopulate); + } + + // Merge any fragments into hosts. + ResolutionError prepareError = initialCandidates.prepare(); + if (prepareError != null) + { + session.setCurrentError(prepareError); + } + else + { + // Record the initial candidate permutation. + session.addPermutation(PermutationType.USES, initialCandidates); + } + } + + private Candidates findValidCandidates(ResolveSession session, Map<Resource, ResolutionError> faultyResources) { + Candidates allCandidates = null; + boolean foundFaultyResources = false; + do + { + allCandidates = session.getNextPermutation(); + if (allCandidates == null) + { + break; + } + +//allCandidates.dump(); + + Map<Resource, ResolutionError> currentFaultyResources = new HashMap<Resource, ResolutionError>(); + + session.setCurrentError( + checkConsistency( + session, + allCandidates, + currentFaultyResources + ) + ); + + if (!currentFaultyResources.isEmpty()) + { + if (!foundFaultyResources) + { + foundFaultyResources = true; + faultyResources.putAll(currentFaultyResources); + } + else if (faultyResources.size() > currentFaultyResources.size()) + { + // save the optimal faultyResources which has less + faultyResources.clear(); + faultyResources.putAll(currentFaultyResources); + } + } + } + while (session.getCurrentError() != null); + + return allCandidates; + } + private ResolutionError checkConsistency( - Executor executor, ResolveSession session, - List<Candidates> usesPermutations, - List<Candidates> importPermutations, Candidates allCandidates, - Map<Resource, ResolutionError> currentFaultyResources, - Map<Resource, Resource> hosts, - boolean dynamic) + Map<Resource, ResolutionError> currentFaultyResources) { + ResolutionError rethrow = allCandidates.checkSubstitutes(); + if (rethrow != null) + { + return rethrow; + } + Map<Resource, Resource> allhosts = allCandidates.getRootHosts(); // Calculate package spaces Map<Resource, Packages> resourcePkgMap = - calculatePackageSpaces(executor, session, allCandidates, hosts.values()); + calculatePackageSpaces(session, allCandidates, allhosts.values()); ResolutionError error = null; // Check package consistency Map<Resource, Object> resultCache = new OpenHashMap<Resource, Object>(resourcePkgMap.size()); - for (Entry<Resource, Resource> entry : hosts.entrySet()) + for (Entry<Resource, Resource> entry : allhosts.entrySet()) { - ResolutionError rethrow = checkPackageSpaceConsistency( - session, usesPermutations, importPermutations, entry.getValue(), - allCandidates, dynamic, resourcePkgMap, resultCache); + rethrow = checkPackageSpaceConsistency( + session, entry.getValue(), + allCandidates, session.isDynamic(), resourcePkgMap, resultCache); if (rethrow != null) { Resource faultyResource = entry.getKey(); @@ -528,9 +626,6 @@ public class ResolverImpl implements Resolver List<Capability> matches) throws ResolutionException { - ResolveSession session = new ResolveSession(rc); - Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>(); - // We can only create a dynamic import if the following // conditions are met: // 1. The specified resource is resolved. @@ -549,122 +644,11 @@ public class ResolverImpl implements Resolver "Matching candidate does not provide a package name."); } } - - Map<Resource, Packages> resourcePkgMap = new HashMap<Resource, Packages>(); - Map<Resource, Boolean> onDemandResources = new HashMap<Resource, Boolean>(); - - boolean retry; - do - { - retry = false; - - try - { - // Create all candidates pre-populated with the single candidate set - // for the resolving dynamic import of the host. - Candidates allCandidates = new Candidates(onDemandResources); - ResolutionError rethrow = allCandidates.populateDynamic(rc, host, dynamicReq, matches); - if (rethrow == null) - { - // Merge any fragments into hosts. - rethrow = allCandidates.prepare(rc); - } - if (rethrow != null) - { - throw rethrow.toException(); - } - - List<Candidates> usesPermutations = new ArrayList<Candidates>(); - List<Candidates> importPermutations = new ArrayList<Candidates>(); - - // Record the initial candidate permutation. - usesPermutations.add(allCandidates); - - do - { - resourcePkgMap.clear(); - - allCandidates = (usesPermutations.size() > 0) - ? usesPermutations.remove(0) - : importPermutations.remove(0); -//allCandidates.dump(); - - rethrow = allCandidates.checkSubstitutes(importPermutations); - if (rethrow != null) - { - continue; - } - // For a dynamic import, the instigating resource - // will never be a fragment since fragments never - // execute code, so we don't need to check for - // this case like we do for a normal resolve. - rethrow = checkConsistency( - new DumbExecutor(), - session, usesPermutations, importPermutations, allCandidates, - new OpenHashMap<Resource, ResolutionError>(resourcePkgMap.size()), - allCandidates.getHosts(), - true); - } - while ((rethrow != null) - && ((usesPermutations.size() > 0) || (importPermutations.size() > 0))); - - // If there is a resolve exception, then determine if an - // optionally resolved resource is to blame (typically a fragment). - // If so, then remove the optionally resolved resource and try - // again; otherwise, rethrow the resolve exception. - if (rethrow != null) - { - Collection<Requirement> exReqs = rethrow.getUnresolvedRequirements(); - Requirement faultyReq = ((exReqs == null) || (exReqs.isEmpty())) - ? null : exReqs.iterator().next(); - Resource faultyResource = (faultyReq == null) - ? null : getDeclaredResource(faultyReq.getResource()); - // If the faulty requirement is wrapped, then it may - // be from a fragment, so consider the fragment faulty - // instead of the host. - if (faultyReq instanceof WrappedRequirement) - { - faultyResource = - ((WrappedRequirement) faultyReq) - .getDeclaredRequirement().getResource(); - } - Boolean valid = onDemandResources.get(faultyResource); - if (valid != null && valid) - { - onDemandResources.put(faultyResource, Boolean.FALSE); - retry = true; - } - else - { - throw rethrow.toException(); - } - } - // If there is no exception to rethrow, then this was a clean - // resolve, so populate the wire map. - else - { - if (session.getMultipleCardCandidates() != null) - { - // TODO this was not done before; but I think it should be; - // Candidates for multiple cardinality requirements were - // removed in order to provide a consistent class space. - // Use the consistent permutation - allCandidates = session.getMultipleCardCandidates(); - } - wireMap = populateDynamicWireMap(rc, - host, dynamicReq, wireMap, allCandidates); - } - } - finally - { - // Always clear the state. - session.setMultipleCardCandidates(null); - } - } - while (retry); + ResolveSession session = new ResolveSession(rc, new DumbExecutor(), host, dynamicReq, matches); + return doResolve(session); } - return wireMap; + return Collections.emptyMap(); } private static List<WireCandidate> getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource) @@ -707,23 +691,12 @@ public class ResolverImpl implements Resolver // the dynamic import is added here last to the // list is used later when checking to see if the package being // dynamically imported shadows an existing provider. - for (Requirement req : wiring.getResourceRequirements(null)) + Requirement dynamicReq = session.getDynamicRequirement(); + if (dynamicReq != null && resource.equals(session.getDynamicHost())) { - if (!Util.isDynamic(req)) - { - continue; - } // Grab first (i.e., highest priority) candidate. - Capability cap = allCandidates.getFirstCandidate(req); - // Optional requirements may not have any candidates. - if (cap == null) - { - continue; - } - wireCandidates.add(new WireCandidate(req, cap)); - // Can only dynamically import one at a time, so break - // out of the loop after the first. - break; + Capability cap = allCandidates.getFirstCandidate(dynamicReq); + wireCandidates.add(new WireCandidate(dynamicReq, cap)); } } else @@ -1117,12 +1090,11 @@ public class ResolverImpl implements Resolver } private static Map<Resource, Packages> calculatePackageSpaces( - final Executor innerExecutor, final ResolveSession session, final Candidates allCandidates, Collection<Resource> hosts) { - final EnhancedExecutor executor = new EnhancedExecutor(innerExecutor); + final EnhancedExecutor executor = new EnhancedExecutor(session.getExecutor()); // Parallel compute wire candidates final Map<Resource, List<WireCandidate>> allWireCandidates = new ConcurrentHashMap<Resource, List<WireCandidate>>(); @@ -1289,8 +1261,6 @@ public class ResolverImpl implements Resolver private ResolutionError checkPackageSpaceConsistency( ResolveSession session, - List<Candidates> usesPermutations, - List<Candidates> importPermutations, Resource resource, Candidates allCandidates, boolean dynamic, @@ -1333,9 +1303,9 @@ public class ResolverImpl implements Resolver else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource())) { // Try to permutate the conflicting requirement. - allCandidates.permutate(blame.m_reqs.get(0), importPermutations); + session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(blame.m_reqs.get(0))); // Try to permutate the source requirement. - allCandidates.permutate(sourceBlame.m_reqs.get(0), importPermutations); + session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(sourceBlame.m_reqs.get(0))); // Report conflict. rethrow = new UseConstraintError( session.getContext(), allCandidates, @@ -1370,7 +1340,7 @@ public class ResolverImpl implements Resolver { for (Blame usedBlame : usedBlames.m_blames) { - if (checkMultiple(session, usedBlames, usedBlame, allCandidates)) + if (session.checkMultiple(usedBlames, usedBlame, allCandidates)) { // Continue to the next usedBlame, if possible we // removed the conflicting candidates. @@ -1423,7 +1393,7 @@ public class ResolverImpl implements Resolver { if (!mutated.isEmpty()) { - usesPermutations.add(permutation); + session.addPermutation(PermutationType.USES, permutation); } if (m_logger.isDebugEnabled()) { @@ -1470,7 +1440,7 @@ public class ResolverImpl implements Resolver Blame requirementBlame = requirementBlames.get(0); for (Blame usedBlame : usedBlames.m_blames) { - if (checkMultiple(session, usedBlames, usedBlame, allCandidates)) + if (session.checkMultiple(usedBlames, usedBlame, allCandidates)) { // Continue to the next usedBlame, if possible we // removed the conflicting candidates. @@ -1529,10 +1499,10 @@ public class ResolverImpl implements Resolver // for the conflicting uses constraint. if (rethrow != null) { - // Add uses permutation if we mutated any candidates. + // Add uses permutation if we m_mutated any candidates. if (!mutated.isEmpty()) { - usesPermutations.add(permutation); + session.addPermutation(PermutationType.USES, permutation); } // Try to permutate the candidate for the original @@ -1547,7 +1517,7 @@ public class ResolverImpl implements Resolver // with existing import decisions, we may end up trying // to permutate the same import a lot of times, so we should // try to check if that the case and only permutate it once. - allCandidates.permutateIfNeeded(req, importPermutations); + session.permutateIfNeeded(PermutationType.IMPORT, req, allCandidates); } } @@ -1569,7 +1539,7 @@ public class ResolverImpl implements Resolver // current resource depends. Keep track of the current number // of permutations so we know if the lower level check was // able to create a permutation or not in the case of failure. - int permCount = usesPermutations.size() + importPermutations.size(); + long permCount = session.getPermutationCount(); for (Requirement req : resource.getRequirements(null)) { Capability cap = allCandidates.getFirstCandidate(req); @@ -1578,7 +1548,7 @@ public class ResolverImpl implements Resolver if (!resource.equals(cap.getResource())) { rethrow = checkPackageSpaceConsistency( - session, usesPermutations, importPermutations, cap.getResource(), + session, cap.getResource(), allCandidates, false, resourcePkgMap, resultCache); if (rethrow != null) { @@ -1586,9 +1556,9 @@ public class ResolverImpl implements Resolver // then we should create an import permutation for the // requirement with the dependency on the failing resource // to backtrack on our current candidate selection. - if (permCount == (usesPermutations.size() + importPermutations.size())) + if (permCount == session.getPermutationCount()) { - allCandidates.permutate(req, importPermutations); + session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(req)); } return rethrow; } @@ -1598,33 +1568,6 @@ public class ResolverImpl implements Resolver return null; } - private boolean checkMultiple( - ResolveSession session, - UsedBlames usedBlames, - Blame usedBlame, - Candidates permutation) - { - // Check the root requirement to see if it is a multiple cardinality - // requirement. - List<Capability> candidates = null; - Requirement req = usedBlame.m_reqs.get(0); - if (Util.isMultiple(req)) - { - // Create a copy of the current permutation so we can remove the - // candidates causing the blame. - if (session.getMultipleCardCandidates() == null) - { - session.setMultipleCardCandidates(permutation.copy()); - } - // Get the current candidate list and remove all the offending root - // cause candidates from a copy of the current permutation. - candidates = session.getMultipleCardCandidates().clearCandidates(req, usedBlames.getRootCauses(req)); - } - // We only are successful if there is at least one candidate left - // for the requirement - return (candidates != null) && !candidates.isEmpty(); - } - private static OpenHashMap<String, Blame> calculateExportedPackages( ResolveSession session, Candidates allCandidates, |