blob: 963bea62eac6daa3c33d1f5bd0ba068995c46d0f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.framework.resolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import org.apache.felix.framework.FelixResolverState;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.capabilityset.Attribute;
import org.apache.felix.framework.capabilityset.Capability;
import org.apache.felix.framework.capabilityset.CapabilitySet;
import org.apache.felix.framework.capabilityset.Directive;
import org.apache.felix.framework.capabilityset.Requirement;
import org.apache.felix.framework.util.manifestparser.RequirementImpl;
import org.osgi.framework.Constants;
// 1. Treat hard pkg constraints separately from implied package constraints
// 2. Map pkg constraints to a set of capabilities, not a single capability.
// 3. Uses constraints cannot conflict with other uses constraints, only with hard constraints.
public class ResolverImpl implements Resolver
{
private final Logger m_logger;
private static final Map<String, Long> m_invokeCounts = new HashMap<String, Long>();
private static boolean m_isInvokeCount = false;
// Reusable empty array.
private static final List<Wire> m_emptyWires = new ArrayList<Wire>(0);
public ResolverImpl(Logger logger)
{
//System.out.println("+++ PROTO3 RESOLVER");
m_logger = logger;
String v = System.getProperty("invoke.count");
m_isInvokeCount = (v == null) ? false : Boolean.valueOf(v);
}
private final List<Map<Requirement, Set<Capability>>> m_candidatePermutations =
new ArrayList<Map<Requirement, Set<Capability>>>();
public Map<Module, List<Wire>> resolve(ResolverState state, Module module)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
Map<Module, List<Wire>> wireMap = new HashMap<Module, List<Wire>>();
Map<Module, Packages> modulePkgMap = new HashMap<Module, Packages>();
if (!module.isResolved())
{
m_candidatePermutations.clear();
//System.out.println("+++ RESOLVING " + module);
Map<Requirement, Set<Capability>> candidateMap =
new HashMap<Requirement, Set<Capability>>();
populateCandidates(state, module, candidateMap, new HashMap<Module, Object>());
m_candidatePermutations.add(candidateMap);
ResolveException rethrow = null;
do
{
rethrow = null;
candidateMap = m_candidatePermutations.remove(0);
//dumpCandidateMap(state, candidateMap);
try
{
findConsistentCandidates(
module,
new ArrayList(),
candidateMap,
modulePkgMap,
new HashMap<Module, Object>());
}
catch (ResolveException ex)
{
rethrow = ex;
System.out.println("RE: " + ex);
}
}
while ((rethrow != null) && (m_candidatePermutations.size() > 0));
if (rethrow != null)
{
throw rethrow;
}
//dumpModulePkgMap(modulePkgMap);
wireMap =
populateWireMap(module, modulePkgMap, wireMap,
candidateMap);
}
if (m_isInvokeCount)
{
System.out.println("INVOKE COUNTS " + m_invokeCounts);
}
return wireMap;
}
public Map<Module, List<Wire>> resolve(ResolverState state, Module module, String pkgName)
{
Capability candidate = null;
// We can only create a dynamic import if the following
// conditions are met:
// 1. The specified module is resolved.
// 2. The package in question is not already imported.
// 3. The package in question is not accessible via require-bundle.
// 4. The package in question is not exported by the bundle.
// 5. The package in question matches a dynamic import of the bundle.
// The following call checks all of these conditions and returns
// a matching dynamic requirement if possible.
Map<Requirement, Set<Capability>> candidateMap =
getDynamicImportCandidates(state, module, pkgName);
if (candidateMap != null)
{
m_candidatePermutations.clear();
Map<Module, List<Wire>> wireMap = new HashMap();
Map<Module, Packages> modulePkgMap = new HashMap();
//System.out.println("+++ DYNAMICALLY RESOLVING " + module + " - " + pkgName);
populateDynamicCandidates(state, module, candidateMap);
m_candidatePermutations.add(candidateMap);
ResolveException rethrow = null;
do
{
rethrow = null;
candidateMap = m_candidatePermutations.remove(0);
//dumpCandidateMap(state, candidateMap);
try
{
findConsistentDynamicCandidate(
module,
new ArrayList(),
candidateMap,
modulePkgMap);
}
catch (ResolveException ex)
{
rethrow = ex;
System.out.println("RE: " + ex);
}
}
while ((rethrow != null) && (m_candidatePermutations.size() > 0));
if (rethrow != null)
{
throw rethrow;
}
//dumpModulePkgMap(modulePkgMap);
wireMap =
populateDynamicWireMap(
module, pkgName, modulePkgMap, wireMap, candidateMap);
//System.out.println("+++ DYNAMIC SUCCESS: " + wireMap.get(module));
return wireMap;
}
//System.out.println("+++ DYNAMIC FAILURE");
return null;
}
// TODO: FELIX3 - It would be nice to make this private.
public static Map<Requirement, Set<Capability>> getDynamicImportCandidates(
ResolverState state, Module module, String pkgName)
{
// Unresolved modules cannot dynamically import, nor can the default
// package be dynamically imported.
if (!module.isResolved() || pkgName.length() == 0)
{
return null;
}
// If any of the module exports this package, then we cannot
// attempt to dynamically import it.
List<Capability> caps = module.getCapabilities();
for (int i = 0; (caps != null) && (i < caps.size()); i++)
{
if (caps.get(i).getNamespace().equals(Capability.PACKAGE_NAMESPACE)
&& caps.get(i).getAttribute(Capability.PACKAGE_ATTR).getValue().equals(pkgName))
{
return null;
}
}
// If any of our wires have this package, then we cannot
// attempt to dynamically import it.
List<Wire> wires = module.getWires();
for (int i = 0; (wires != null) && (i < wires.size()); i++)
{
if (wires.get(i).hasPackage(pkgName))
{
return null;
}
}
// Loop through the importer's dynamic requirements to determine if
// there is a matching one for the package from which we want to
// load a class.
List<Directive> dirs = Collections.EMPTY_LIST;
List<Attribute> attrs = new ArrayList(1);
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(
Capability.PACKAGE_NAMESPACE, dirs, attrs);
Set<Capability> candidates = state.getCandidates(module, req, false);
List<Requirement> dynamics = module.getDynamicRequirements();
// First find a dynamic requirement that matches the capabilities.
Requirement dynReq = null;
for (int dynIdx = 0;
(candidates.size() > 0) && (dynReq == null) && (dynIdx < dynamics.size());
dynIdx++)
{
for (Iterator<Capability> itCand = candidates.iterator();
(dynReq == null) && itCand.hasNext(); )
{
Capability cap = itCand.next();
if (CapabilitySet.matches(cap, dynamics.get(dynIdx).getFilter()))
{
dynReq = dynamics.get(dynIdx);
}
}
}
// If we found a matching dynamic requirement, then filter out
// any candidates that do not match it.
if (dynReq != null)
{
for (Iterator<Capability> itCand = candidates.iterator(); itCand.hasNext(); )
{
Capability cap = itCand.next();
if (!CapabilitySet.matches(cap, dynReq.getFilter()))
{
itCand.remove();
}
}
}
else
{
candidates.clear();
}
if (candidates.size() > 0)
{
Map<Requirement, Set<Capability>> candidateMap = new HashMap();
candidateMap.put(dynReq, candidates);
return candidateMap;
}
return null;
}
private static void dumpCandidateMap(
ResolverState state, Map<Requirement, Set<Capability>> candidateMap)
{
System.out.println("=== BEGIN CANDIDATE MAP ===");
for (Module module : ((FelixResolverState) state).getModules())
{
System.out.println(" " + module
+ " (" + (module.isResolved() ? "RESOLVED)" : "UNRESOLVED)"));
for (Requirement req : module.getRequirements())
{
Set<Capability> candidates = candidateMap.get(req);
if ((candidates != null) && (candidates.size() > 0))
{
System.out.println(" " + req + ": " + candidates);
}
}
for (Requirement req : module.getDynamicRequirements())
{
Set<Capability> candidates = candidateMap.get(req);
if ((candidates != null) && (candidates.size() > 0))
{
System.out.println(" " + req + ": " + candidates);
}
}
}
System.out.println("=== END CANDIDATE MAP ===");
}
private static void dumpModulePkgMap(Map<Module, Packages> modulePkgMap)
{
System.out.println("+++MODULE PKG MAP+++");
for (Entry<Module, Packages> entry : modulePkgMap.entrySet())
{
dumpModulePkgs(entry.getKey(), entry.getValue());
}
}
private static void dumpModulePkgs(Module module, Packages packages)
{
System.out.println(module + " (" + (module.isResolved() ? "RESOLVED)" : "UNRESOLVED)"));
System.out.println(" EXPORTED");
for (Entry<String, Blame> entry : packages.m_exportedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" IMPORTED");
for (Entry<String, Blame> entry : packages.m_importedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" REQUIRED");
for (Entry<String, List<Blame>> entry : packages.m_requiredPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
System.out.println(" USED");
for (Entry<String, List<Blame>> entry : packages.m_usedPkgs.entrySet())
{
System.out.println(" " + entry.getKey() + " - " + entry.getValue());
}
}
// TODO: FELIX3 - Modify to not be recursive.
private static void populateCandidates(
ResolverState state, Module module,
Map<Requirement, Set<Capability>> candidateMap,
Map<Module, Object> resultCache)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
// Determine if we've already calculated this module's candidates.
// The result cache will have one of three values:
// 1. A resolve exception if we've already attempted to populate the
// module's candidates but were unsuccessful.
// 2. Boolean.TRUE indicating we've already attempted to populate the
// module's candidates and were successful.
// 3. An array containing the cycle count, current map of candidates
// for already processed requirements, and a list of remaining
// requirements whose candidates still need to be calculated.
// For case 1, rethrow the exception. For case 2, simply return immediately.
// For case 3, this means we have a cycle so we should continue to populate
// the candidates where we left off and not record any results globally
// until we've popped completely out of the cycle.
// Keeps track of the number of times we've reentered this method
// for the current module.
Integer cycleCount = null;
// Keeps track of the candidates we've already calculated for the
// current module's requirements.
Map<Requirement, Set<Capability>> localCandidateMap = null;
// Keeps track of the current module's requirements for which we
// haven't yet found candidates.
List<Requirement> remainingReqs = null;
// Get the cache value for the current module.
Object cacheValue = resultCache.get(module);
// This is case 1.
if (cacheValue instanceof ResolveException)
{
throw (ResolveException) cacheValue;
}
// This is case 2.
else if (cacheValue instanceof Boolean)
{
return;
}
// This is case 3.
else if (cacheValue != null)
{
// Increment and get the cycle count.
cycleCount = (Integer)
(((Object[]) cacheValue)[0]
= new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
// Get the already populated candidates.
localCandidateMap = (Map) ((Object[]) cacheValue)[1];
// Get the remaining requirements.
remainingReqs = (List) ((Object[]) cacheValue)[2];
}
// If there is no cache value for the current module, then this is
// the first time we are attempting to populate its candidates, so
// do some one-time checks and initialization.
if ((remainingReqs == null) && (localCandidateMap == null))
{
// Verify that any required execution environment is satisfied.
state.checkExecutionEnvironment(module);
// Verify that any native libraries match the current platform.
state.checkNativeLibraries(module);
// Record cycle count.
cycleCount = new Integer(0);
// Create a local map for populating candidates first, just in case
// the module is not resolvable.
localCandidateMap = new HashMap();
// Create a modifiable list of the module's requirements.
remainingReqs = new ArrayList(module.getRequirements());
// Add these value to the result cache so we know we are
// in the middle of populating candidates for the current
// module.
resultCache.put(module,
cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
}
// If we have requirements remaining, then find candidates for them.
while (remainingReqs.size() > 0)
{
Requirement req = remainingReqs.remove(0);
// Get satisfying candidates and populate their candidates if necessary.
Set<Capability> candidates = state.getCandidates(module, req, true);
for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
{
Capability candCap = itCandCap.next();
if (!candCap.getModule().isResolved())
{
try
{
populateCandidates(state, candCap.getModule(),
candidateMap, resultCache);
}
catch (ResolveException ex)
{
System.out.println("RE: Candidate not resolveable: " + ex);
// Remove the candidate since we weren't able to
// populate its candidates.
itCandCap.remove();
}
}
}
// If there are no candidates for the current requirement
// and it is not optional, then create, cache, and throw
// a resolve exception.
if ((candidates.size() == 0) && !req.isOptional())
{
ResolveException ex =
new ResolveException("Unable to resolve " + module
+ ": missing requirement " + req, module, req);
resultCache.put(module, ex);
throw ex;
}
// If we actually have candidates for the requirement, then
// add them to the local candidate map.
else if (candidates.size() > 0)
{
localCandidateMap.put(req, candidates);
}
}
// If we are exiting from a cycle then decrement
// cycle counter, otherwise record the result.
if (cycleCount.intValue() > 0)
{
((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
}
else if (cycleCount.intValue() == 0)
{
// Record that the module was successfully populated.
resultCache.put(module, Boolean.TRUE);
// Merge local candidate map into global candidate map.
if (localCandidateMap.size() > 0)
{
candidateMap.putAll(localCandidateMap);
}
}
}
private static void populateDynamicCandidates(
ResolverState state, Module module,
Map<Requirement, Set<Capability>> candidateMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
// There should be one entry in the candidate map, which are the
// the candidates for the matching dynamic requirement. Get the
// matching candidates and populate their candidates if necessary.
Entry<Requirement, Set<Capability>> entry = candidateMap.entrySet().iterator().next();
Requirement dynReq = entry.getKey();
Set<Capability> candidates = entry.getValue();
for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
{
Capability candCap = itCandCap.next();
if (!candCap.getModule().isResolved())
{
try
{
populateCandidates(state, candCap.getModule(),
candidateMap, new HashMap<Module, Object>());
}
catch (ResolveException ex)
{
System.out.println("RE: Candidate not resolveable: " + ex);
itCandCap.remove();
}
}
}
// TODO: FELIX3 - Since we reuse the same dynamic requirement, is it possible
// that some sort of cycle could cause us to try to match another set
// of candidates to the same requirement?
if (candidates.size() == 0)
{
candidateMap.remove(dynReq);
throw new ResolveException("Dynamic import failed.", module, dynReq);
}
// Add existing wires as candidates.
for (Wire wire : module.getWires())
{
Set<Capability> cs = new TreeSet();
cs.add(wire.getCapability());
candidateMap.put(wire.getRequirement(), cs);
}
}
// TODO: FELIX3 - Modify to not be recursive.
private void findConsistentCandidates(
Module module, List<Requirement> incomingReqs,
Map<Requirement, Set<Capability>> candidateMap,
Map<Module, Packages> modulePkgMap,
Map<Module, Object> resultCache)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
// Determine if we've already checked candidate consistency for this
// module. The result cache will have one of two values:
// 1. Boolean.TRUE if candidate consistency has been checked.
// 2. An array containing the cycle count and a list of wires for an
// already resolved (and consistent) module or a list of remaining
// requirments whose candidates still need to be checked for an
// unresolved module.
// For case 1, return immediately. For case 2, this means we are in a
// cycle so we should just continue checking consistency where we left
// off.
Integer cycleCount = null;
Object o = resultCache.get(module);
if (o instanceof Boolean)
{
return;
}
else if (o == null)
{
List list;
if (module.isResolved())
{
list = new ArrayList(module.getWires());
}
else
{
list = new ArrayList(module.getRequirements());
}
resultCache.put(module, o = new Object[] { cycleCount = new Integer(0), list });
calculateExportedPackages(module, incomingReqs, modulePkgMap);
}
else
{
// Increment and get the cycle count.
cycleCount = (Integer)
(((Object[]) o)[0]
= new Integer(((Integer) ((Object[]) o)[0]).intValue() + 1));
}
//System.out.println("+++ RESOLVING " + module);
if (module.isResolved())
{
List<Wire> wires = (List<Wire>) ((Object[]) o)[1];
while (wires.size() > 0)
{
Wire wire = wires.remove(0);
// Try to resolve the candidate.
findConsistentCandidates(
wire.getCapability().getModule(),
incomingReqs,
candidateMap,
modulePkgMap,
resultCache);
// If we are here, the candidate was consistent. Try to
// merge the candidate into the target module's packages.
mergeCandidatePackages(
module,
incomingReqs,
wire.getCapability(),
modulePkgMap,
candidateMap);
}
}
else
{
List<Requirement> reqs = (List<Requirement>) ((Object[]) o)[1];
while (reqs.size() > 0)
{
Requirement req = reqs.remove(0);
// Get the candidates for the current requirement.
Set<Capability> candCaps = candidateMap.get(req);
// Optional requirements may not have any candidates.
if (candCaps == null)
{
continue;
}
List<Requirement> outgoingReqs = new ArrayList<Requirement>(incomingReqs);
outgoingReqs.add(req);
for (Iterator<Capability> it = candCaps.iterator(); it.hasNext(); )
{
Capability candCap = it.next();
//System.out.println("+++ TRYING CAND " + candCap + " FOR " + req);
try
{
// Try to resolve the candidate.
findConsistentCandidates(
candCap.getModule(),
outgoingReqs,
candidateMap,
modulePkgMap,
resultCache);
// If we are here, the candidate was consistent. Try to
// merge the candidate into the target module's packages.
mergeCandidatePackages(
module,
outgoingReqs,
candCap,
modulePkgMap,
candidateMap);
// If we are here, we merged the candidate successfully,
// so we can continue with the next requirement
break;
}
catch (ResolveException ex)
{
//System.out.println("RE: " + ex);
//ex.printStackTrace();
// TODO: FELIX3 - Is it ok to remove the failed candidate? By removing
// it we keep the candidateMap up to date with the selected candidate, but
// theoretically this eliminates some potential combinations. Are those
// combinations guaranteed to be failures so eliminating them is ok?
it.remove();
if (!it.hasNext() && !req.isOptional())
{
throw new ResolveException("Unresolved constraint "
+ req + " in " + module, module, req);
}
}
}
}
}
// If we are exiting from a cycle then decrement
// cycle counter, otherwise record the result.
if (cycleCount.intValue() > 0)
{
((Object[]) o)[0] = new Integer(cycleCount.intValue() - 1);
}
else if (cycleCount.intValue() == 0)
{
// Record that the module was successfully populated.
resultCache.put(module, Boolean.TRUE);
}
}
private void findConsistentDynamicCandidate(
Module module, List<Requirement> incomingReqs,
Map<Requirement, Set<Capability>> candidateMap,
Map<Module, Packages> modulePkgMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
//System.out.println("+++ RESOLVING " + module);
calculateExportedPackages(module, incomingReqs, modulePkgMap);
List<Requirement> reqs = new ArrayList(module.getRequirements());
reqs.addAll(module.getDynamicRequirements());
for (Requirement req : reqs)
{
// Get the candidates for the current requirement.
Set<Capability> candCaps = candidateMap.get(req);
// Optional requirements may not have any candidates.
if (candCaps == null)
{
continue;
}
List<Requirement> outgoingReqs = new ArrayList<Requirement>(incomingReqs);
outgoingReqs.add(req);
for (Iterator<Capability> it = candCaps.iterator(); it.hasNext(); )
{
Capability candCap = it.next();
//System.out.println("+++ TRYING CAND " + candCap + " FOR " + req);
try
{
// Try to resolve the candidate.
findConsistentCandidates(
candCap.getModule(),
outgoingReqs,
candidateMap,
modulePkgMap,
new HashMap());
// If we are here, the candidate was consistent. Try to
// merge the candidate into the target module's packages.
mergeCandidatePackages(
module,
outgoingReqs,
candCap,
modulePkgMap,
candidateMap);
// If we are here, we merged the candidate successfully,
// so we can continue with the next requirement
break;
}
catch (ResolveException ex)
{
System.out.println("RE: " + ex);
ex.printStackTrace();
// TODO: FELIX3 - Is it ok to remove the failed candidate? By removing
// it we keep the candidateMap up to date with the selected candidate, but
// theoretically this eliminates some potential combinations. Are those
// combinations guaranteed to be failures so eliminating them is ok?
it.remove();
if (!it.hasNext() && !req.isOptional())
{
throw new ResolveException("Unresolved constraint "
+ req + " in " + module, module, req);
}
}
}
}
}
private static void calculateExportedPackages(
Module module, List<Requirement> incomingReqs, Map<Module, Packages> modulePkgMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
Packages packages = new Packages();
List<Capability> caps = module.getCapabilities();
if (caps.size() > 0)
{
for (int i = 0; i < caps.size(); i++)
{
// TODO: FELIX3 - Assume if a module imports the same package it
// exports that the import will overlap the export.
if (caps.get(i).getNamespace().equals(Capability.PACKAGE_NAMESPACE)
&& !hasOverlappingImport(module, caps.get(i)))
{
packages.m_exportedPkgs.put(
(String) caps.get(i).getAttribute(Capability.PACKAGE_ATTR).getValue(),
new Blame(incomingReqs, caps.get(i)));
}
}
}
modulePkgMap.put(module, packages);
}
private static boolean hasOverlappingImport(Module module, Capability cap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
List<Requirement> reqs = module.getRequirements();
for (int i = 0; i < reqs.size(); i++)
{
if (reqs.get(i).getNamespace().equals(Capability.PACKAGE_NAMESPACE)
&& CapabilitySet.matches(cap, reqs.get(i).getFilter()))
{
return true;
}
}
return false;
}
private void mergeCandidatePackages(
Module current, List<Requirement> outgoingReqs,
Capability candCap, Map<Module, Packages> modulePkgMap,
Map<Requirement, Set<Capability>> candidateMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
if (candCap.getNamespace().equals(Capability.PACKAGE_NAMESPACE))
{
mergeCandidatePackage(
current, false, new Blame(outgoingReqs, candCap), modulePkgMap, candidateMap);
}
else if (candCap.getNamespace().equals(Capability.MODULE_NAMESPACE))
{
// Get the candidate's package space to determine which packages
// will be visible to the current module.
Packages candPkgs = modulePkgMap.get(candCap.getModule());
for (Entry<String, Blame> entry : candPkgs.m_exportedPkgs.entrySet())
{
mergeCandidatePackage(
current,
true,
new Blame(outgoingReqs, entry.getValue().m_cap),
modulePkgMap,
candidateMap);
}
for (Entry<String, List<Blame>> entry : candPkgs.m_requiredPkgs.entrySet())
{
List<Blame> blames = entry.getValue();
for (Blame blame : blames)
{
// TODO: FELIX3 - Since a single module requirement can include many packages,
// it is likely we call merge too many times for the same module req. If we knew
// which candidates were being used to resolve this candidate's module dependencies,
// then we could just try to merge them directly. This info would also help in
// in creating wires, since we ultimately want to create wires for the selected
// candidates, which we are trying to deduce from the package space, but if we
// knew the selected candidates, we'd be done.
if (blame.m_cap.getModule().equals(current))
{
continue;
}
Directive dir = blame.m_reqs.get(blame.m_reqs.size() - 1)
.getDirective(Constants.VISIBILITY_DIRECTIVE);
if ((dir != null) && dir.getValue().equals(Constants.VISIBILITY_REEXPORT))
{
mergeCandidatePackage(
current,
true,
new Blame(outgoingReqs, blame.m_cap),
modulePkgMap,
candidateMap);
}
}
}
}
}
private void mergeCandidatePackage(
Module current, boolean requires,
Blame candBlame, Map<Module, Packages> modulePkgMap,
Map<Requirement, Set<Capability>> candidateMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
// TODO: FELIX3 - Check for merging where module imports from itself,
// then it should be listed as an export for requiring bundles.
if (candBlame.m_cap.getNamespace().equals(Capability.PACKAGE_NAMESPACE))
{
//System.out.println("+++ MERGING " + candBlame.m_cap + " INTO " + current);
String pkgName = (String)
candBlame.m_cap.getAttribute(Capability.PACKAGE_ATTR).getValue();
// Since this capability represents a package, it will become
// a hard constraint on the module's package space, so we need
// to make sure it doesn't conflict with any other hard constraints
// or any other uses constraints.
//
// First, check to see if the capability conflicts with
// any existing hard constraints.
//
Packages currentPkgs = modulePkgMap.get(current);
Blame currentExportedBlame = currentPkgs.m_exportedPkgs.get(pkgName);
Blame currentImportedBlame = currentPkgs.m_importedPkgs.get(pkgName);
List<Blame> currentRequiredBlames = currentPkgs.m_requiredPkgs.get(pkgName);
// We don't need to worry about an import conflicting with a required
// bundle's export, since imported package wires are terminal the
// bundle will never see the exported package from the required bundle.
// TODO: FELIX3 - See scenario 21, this seems odd.
if (!requires &&
(currentImportedBlame != null) && !currentImportedBlame.m_cap.equals(candBlame.m_cap))
// if (!requires &&
// (((currentExportedBlame != null) && !currentExportedBlame.m_cap.equals(candBlame.m_cap))
// || ((currentImportedBlame != null) && !currentImportedBlame.m_cap.equals(candBlame.m_cap))))
// || ((currentRequiredBlames != null) && !currentRequiredBlames.contains(candBlame))))
{
// Permutate the candidate map and throw a resolve exception.
// NOTE: This method ALWAYS throws an exception.
permutateCandidates(
current,
pkgName,
currentImportedBlame,
candBlame,
candidateMap);
}
//
// Second, check to see if the capability conflicts with
// any existing uses constraints
//
Packages currentPkgsCopy = currentPkgs;
if (!current.isResolved())
{
List<Blame> currentUsedBlames = currentPkgs.m_usedPkgs.get(pkgName);
checkExistingUsesConstraints(
current, pkgName, currentUsedBlames, candBlame, modulePkgMap, candidateMap);
//
// Last, check to see if any uses constraints implied by the
// candidate conflict with any of the existing hard constraints.
//
// For now, create a copy of the module's package space and
// add the current candidate to the imported packages.
currentPkgsCopy = new Packages(currentPkgs);
}
if (requires)
{
if (currentRequiredBlames == null)
{
currentRequiredBlames = new ArrayList<Blame>();
currentPkgsCopy.m_requiredPkgs.put(pkgName, currentRequiredBlames);
}
// TODO: FELIX3 - This is potentially modifying the original, we need to modify a copy.
currentRequiredBlames.add(candBlame);
}
else
{
currentPkgsCopy.m_importedPkgs.put(pkgName, candBlame);
}
// Verify and merge the candidate's transitive uses constraints.
verifyAndMergeUses(
current,
currentPkgsCopy,
candBlame,
modulePkgMap,
candidateMap,
new HashMap<String, List<Module>>());
// If we are here, then there were no conflict, so we should update
// the module's package space.
if (!current.isResolved())
{
currentPkgs.m_exportedPkgs.putAll(currentPkgsCopy.m_exportedPkgs);
currentPkgs.m_importedPkgs.putAll(currentPkgsCopy.m_importedPkgs);
currentPkgs.m_requiredPkgs.putAll(currentPkgsCopy.m_requiredPkgs);
currentPkgs.m_usedPkgs.putAll(currentPkgsCopy.m_usedPkgs);
}
//dumpModulePkgs(current, currentPkgs);
}
}
private void checkExistingUsesConstraints(
Module current, String pkgName, List<Blame> currentUsedBlames,
Blame candBlame, Map<Module, Packages> modulePkgMap,
Map<Requirement, Set<Capability>> candidateMap)
{
for (int i = 0; (currentUsedBlames != null) && (i < currentUsedBlames.size()); i++)
{
//System.out.println("+++ CHECK " + candBlame + " IN EXISTING " + currentUsedBlames.get(i));
if (!isCompatible(currentUsedBlames.get(i).m_cap, candBlame.m_cap, modulePkgMap))
{
// Permutate the candidate map and throw a resolve exception.
// NOTE: This method ALWAYS throws an exception.
permutateCandidates(
current,
pkgName,
currentUsedBlames.get(i),
candBlame,
candidateMap);
}
}
}
// TODO: FELIX3 - We end up with duplicates in uses constraints,
// see scenario 2 for an example.
private void verifyAndMergeUses(
Module current, Packages currentPkgs,
Blame candBlame, Map<Module, Packages> modulePkgMap,
Map<Requirement, Set<Capability>> candidateMap,
Map<String, List<Module>> cycleMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
// Check for cycles.
String pkgName = (String)
candBlame.m_cap.getAttribute(Capability.PACKAGE_ATTR).getValue();
List<Module> list = cycleMap.get(pkgName);
if ((list != null) && list.contains(current))
{
return;
}
list = (list == null) ? new ArrayList<Module>() : list;
list.add(current);
cycleMap.put(pkgName, list);
//System.out.println("+++ VERIFYING USES " + current + " FOR " + candBlame);
for (Capability candSourceCap : getPackageSources(
candBlame.m_cap, modulePkgMap, new ArrayList<Capability>(), new HashSet<Capability>()))
{
for (String usedPkgName : candSourceCap.getUses())
{
Blame currentExportedBlame = currentPkgs.m_exportedPkgs.get(usedPkgName);
Blame currentImportedBlame = currentPkgs.m_importedPkgs.get(usedPkgName);
// TODO: FELIX3 - What do we do with required packages?
List<Blame> currentRequiredBlames = currentPkgs.m_requiredPkgs.get(usedPkgName);
Packages candSourcePkgs = modulePkgMap.get(candSourceCap.getModule());
//System.out.println("+++ candSourceCap " + candSourceCap);
//System.out.println("+++ candSourceCap.getModule() " + candSourceCap.getModule() + " (" + candSourceCap.getModule().isResolved() + ")");
//System.out.println("+++ candSourcePkgs " + candSourcePkgs);
//System.out.println("+++ candSourcePkgs.m_exportedPkgs " + candSourcePkgs.m_exportedPkgs);
Blame candSourceBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName);
candSourceBlame = (candSourceBlame != null)
? candSourceBlame
: candSourcePkgs.m_importedPkgs.get(usedPkgName);
// sourceCap = (sourceCap != null)
// ? sourceCap
// : sourcePkgs.m_requiredPkgs.get(usedPkgName);
// If the candidate doesn't actually have a constraint for
// the used package, then just ignore it since this is likely
// an error in its metadata.
if (candSourceBlame == null)
{
return;
}
// If there is no current mapping for this package, then
// we can just return.
if ((currentExportedBlame == null)
&& (currentImportedBlame == null)
&& (currentRequiredBlames == null))
{
List<Blame> usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName);
if (usedCaps == null)
{
usedCaps = new ArrayList<Blame>();
currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps);
}
//System.out.println("+++ MERGING CB " + candBlame + " SB " + candSourceBlame);
// usedCaps.add(new Blame(candBlame.m_reqs, sourceBlame.m_cap));
usedCaps.add(candSourceBlame);
// return;
}
else if (!current.isResolved())
{
if ((currentExportedBlame != null)
&& !isCompatible(currentExportedBlame.m_cap, candSourceBlame.m_cap, modulePkgMap))
{
throw new ResolveException(
"Constraint violation for package '" + usedPkgName
+ "' when resolving module " + current
+ " between existing constraint "
+ currentExportedBlame
+ " and candidate constraint "
+ candSourceBlame, null, null);
}
else if ((currentImportedBlame != null)
&& !isCompatible(currentImportedBlame.m_cap, candSourceBlame.m_cap, modulePkgMap))
{
permutateCandidates(
current, usedPkgName, currentImportedBlame,
candSourceBlame, candidateMap);
}
}
verifyAndMergeUses(current, currentPkgs, candSourceBlame,
modulePkgMap, candidateMap, cycleMap);
}
}
}
private void permutateCandidates(
Module current, String pkgName, Blame currentBlame, Blame candBlame,
Map<Requirement, Set<Capability>> candidateMap)
throws ResolveException
{
// TODO: FELIX3 - I think permutation is not as efficient as it could be, since
// the check for subsets is costly.
// When we detect a conflict, we need to permutate the candidate map
// we when we try again, we'll select different candidates. To achieve
// this, we create a different permutation for each requirement in
// the set of blames requirements by eliminating the first candidate
// (i.e., the selected candidate) for each. We need to create an separate
// permutation for each blamed requirement to ensure we try all possible
// combination. This is a form of back tracking, since we eliminate each
// requirement's selected candidate, which will force them to choose
// another in the new permutation.
//
// The blamed requirement may be null if the bundle itself is exports
// the package imposing the uses constraint.
if ((currentBlame.m_reqs != null) && (currentBlame.m_reqs.size() != 0))
{
// Permutate the candidate map for each blamed requirement.
for (int reqIdx = 0; reqIdx < currentBlame.m_reqs.size(); reqIdx++)
{
// Verify whether we have more than one candidate to create
// a permutation.
Set<Capability> candidates = candidateMap.get(currentBlame.m_reqs.get(reqIdx));
if (candidates.size() > 1)
{
// Create a copy of the current candidate map and then remove
// the first (i.e., selected) candidate for the current
// blamed requirement.
Map<Requirement, Set<Capability>> copy = copyCandidateMap(candidateMap);
Set<Capability> candCopy = copy.get(currentBlame.m_reqs.get(reqIdx));
Iterator it = candCopy.iterator();
it.next();
it.remove();
// Check if the created permutation is a subset of a previously
// created permutation. If so, then we don't need to record it
// since it will be generated again when the superset permutation
// is processed.
boolean isSubset = false;
for (int permIdx = 0; !isSubset && (permIdx < m_candidatePermutations.size()); permIdx++)
{
if (isSubsetPermutation(m_candidatePermutations.get(permIdx), copy))
{
isSubset = true;
}
}
if (!isSubset)
{
//System.out.println("+++ SETTING "
// + currentBlame.m_reqs.get(reqIdx)
// + " CANDS FROM " + candidates + " TO " + candCopy);
m_candidatePermutations.add(0, copy);
}
}
}
}
throw new ResolveException(
"Constraint violation for package '"
+ pkgName + "' when resolving module "
+ current + " between existing constraint "
+ currentBlame + " and candidate constraint "
+ candBlame, null, null);
}
private static boolean isSubsetPermutation(
Map<Requirement, Set<Capability>> orig, Map<Requirement, Set<Capability>> copy)
{
for (Entry<Requirement, Set<Capability>> entry : orig.entrySet())
{
Set<Capability> copyCands = copy.get(entry.getKey());
if (copyCands == null)
{
return false;
}
if (!entry.getValue().containsAll(copyCands))
{
return false;
}
}
return true;
}
private static boolean isCompatible(
Capability currentCap, Capability candCap, Map<Module, Packages> modulePkgMap)
{
if ((currentCap != null) && (candCap != null))
{
List<Capability> currentSources =
getPackageSources(
currentCap,
modulePkgMap,
new ArrayList<Capability>(),
new HashSet<Capability>());
List<Capability> candSources =
getPackageSources(
candCap,
modulePkgMap,
new ArrayList<Capability>(),
new HashSet<Capability>());
//System.out.println("+++ currentSources " + currentSources + " - candSources " + candSources);
return currentSources.containsAll(candSources) || candSources.containsAll(currentSources);
}
return true;
}
private static List<Capability> getPackageSources(
Capability cap, Map<Module, Packages> modulePkgMap, List<Capability> sources,
Set<Capability> cycleMap)
{
if (cap.getNamespace().equals(Capability.PACKAGE_NAMESPACE))
{
if (cycleMap.contains(cap))
{
return sources;
}
cycleMap.add(cap);
// Get the package name associated with the capability.
String pkgName = cap.getAttribute(Capability.PACKAGE_ATTR).getValue().toString();
// Since a module can export the same package more than once, get
// all package capabilities for the specified package name.
List<Capability> caps = cap.getModule().getCapabilities();
for (int capIdx = 0; capIdx < caps.size(); capIdx++)
{
if (caps.get(capIdx).getNamespace().equals(Capability.PACKAGE_NAMESPACE)
&& caps.get(capIdx).getAttribute(Capability.PACKAGE_ATTR).getValue().equals(pkgName))
{
sources.add(caps.get(capIdx));
}
}
// Then get any addition sources for the package from required bundles.
Packages pkgs = modulePkgMap.get(cap.getModule());
List<Blame> required = pkgs.m_requiredPkgs.get(pkgName);
if (required != null)
{
for (Blame blame : required)
{
getPackageSources(blame.m_cap, modulePkgMap, sources, cycleMap);
}
}
}
return sources;
}
private static Map<Requirement, Set<Capability>> copyCandidateMap(
Map<Requirement, Set<Capability>> candidateMap)
{
Map<Requirement, Set<Capability>> copy =
new HashMap<Requirement, Set<Capability>>();
for (Entry<Requirement, Set<Capability>> entry : candidateMap.entrySet())
{
Set<Capability> candidates = new TreeSet(new CandidateComparator());
candidates.addAll(entry.getValue());
copy.put(entry.getKey(), candidates);
}
return copy;
}
private static Map<Module, List<Wire>> populateWireMap(
Module module, Map<Module, Packages> modulePkgMap,
Map<Module, List<Wire>> wireMap, Map<Requirement, Set<Capability>> candidateMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
if (!module.isResolved() && !wireMap.containsKey(module))
{
wireMap.put(module, m_emptyWires);
List<Wire> packageWires = new ArrayList<Wire>();
List<Wire> moduleWires = new ArrayList<Wire>();
for (Requirement req : module.getRequirements())
{
Set<Capability> cands = candidateMap.get(req);
if ((cands != null) && (cands.size() > 0))
{
Capability cand = cands.iterator().next();
if (!cand.getModule().isResolved())
{
populateWireMap(cand.getModule(),
modulePkgMap, wireMap, candidateMap);
}
// Ignore modules that import themselves.
if (req.getNamespace().equals(Capability.PACKAGE_NAMESPACE)
&& !module.equals(cand.getModule()))
{
packageWires.add(
new WireImpl(module,
req,
cand.getModule(),
cand));
}
else if (req.getNamespace().equals(Capability.MODULE_NAMESPACE))
{
Packages candPkgs = modulePkgMap.get(cand.getModule());
moduleWires.add(
new WireModuleImpl(module,
req,
cand.getModule(),
cand,
candPkgs.getExportedAndReexportedPackages()));
}
}
}
// Combine wires with module wires last.
packageWires.addAll(moduleWires);
wireMap.put(module, packageWires);
}
return wireMap;
}
private static Map<Module, List<Wire>> populateDynamicWireMap(
Module module, String pkgName, Map<Module, Packages> modulePkgMap,
Map<Module, List<Wire>> wireMap, Map<Requirement, Set<Capability>> candidateMap)
{
if (m_isInvokeCount)
{
String methodName = new Exception().fillInStackTrace().getStackTrace()[0].getMethodName();
Long count = m_invokeCounts.get(methodName);
count = (count == null) ? new Long(1) : new Long(count.longValue() + 1);
m_invokeCounts.put(methodName, count);
}
wireMap.put(module, m_emptyWires);
List<Wire> packageWires = new ArrayList<Wire>();
Packages pkgs = modulePkgMap.get(module);
for (Entry<String, Blame> entry : pkgs.m_importedPkgs.entrySet())
{
if (!entry.getValue().m_cap.getModule().isResolved())
{
populateWireMap(entry.getValue().m_cap.getModule(), modulePkgMap, wireMap,
candidateMap);
}
// Ignore modules that import themselves.
if (!module.equals(entry.getValue().m_cap.getModule())
&& entry.getValue().m_cap.getAttribute(
Capability.PACKAGE_ATTR).getValue().equals(pkgName))
{
List<Attribute> attrs = new ArrayList();
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
packageWires.add(
new WireImpl(
module,
// We need an unique requirement here or else subsequent
// dynamic imports for the same dynamic requirement will
// conflict with previous ones.
new RequirementImpl(Capability.PACKAGE_NAMESPACE, new ArrayList(0), attrs),
entry.getValue().m_cap.getModule(),
entry.getValue().m_cap));
}
}
wireMap.put(module, packageWires);
return wireMap;
}
private static class Packages
{
public final Map<String, Blame> m_exportedPkgs
= new HashMap<String, Blame>();
public final Map<String, Blame> m_importedPkgs
= new HashMap<String, Blame>();
public final Map<String, List<Blame>> m_requiredPkgs
= new HashMap<String, List<Blame>>();
public final Map<String, List<Blame>> m_usedPkgs
= new HashMap<String, List<Blame>>();
public Packages()
{
}
public Packages(Packages packages)
{
m_exportedPkgs.putAll(packages.m_exportedPkgs);
m_importedPkgs.putAll(packages.m_importedPkgs);
m_requiredPkgs.putAll(packages.m_requiredPkgs);
m_usedPkgs.putAll(packages.m_usedPkgs);
}
public List<String> getExportedAndReexportedPackages()
{
List<String> pkgs = new ArrayList();
for (Entry<String, Blame> entry : m_exportedPkgs.entrySet())
{
pkgs.add((String)
entry.getValue().m_cap.getAttribute(Capability.PACKAGE_ATTR).getValue());
}
for (Entry<String, List<Blame>> entry : m_requiredPkgs.entrySet())
{
for (Blame blame : entry.getValue())
{
Directive dir = blame.m_reqs.get(
blame.m_reqs.size() - 1).getDirective(Constants.VISIBILITY_DIRECTIVE);
if ((dir != null)
&& dir.getValue().equals(Constants.VISIBILITY_REEXPORT))
{
pkgs.add((String)
blame.m_cap.getAttribute(Capability.PACKAGE_ATTR).getValue());
break;
}
}
}
return pkgs;
}
}
private static class Blame
{
public final List<Requirement> m_reqs;
public final Capability m_cap;
public Blame(List<Requirement> reqs, Capability cap)
{
m_reqs = reqs;
m_cap = cap;
}
public String toString()
{
return m_cap.getModule()
+ "." + m_cap.getAttribute(Capability.PACKAGE_ATTR).getValue()
+ ((m_reqs.size() == 0)
? " NO BLAME"
: " BLAMED ON " + m_reqs.get(m_reqs.size() - 1));
}
public boolean equals(Object o)
{
return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs)
&& m_cap.equals(((Blame) o).m_cap);
}
}
}