[FELIX-4942] Avoid creating and throwing exceptions to report resolution problems internally. Also lazily create the error messages reported to the user.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1690702 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/resolver/src/main/java/org/apache/felix/resolver/Candidates.java b/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
index 1975eb7..89bc5c4 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
@@ -47,7 +47,6 @@
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;
class Candidates
@@ -140,21 +139,21 @@
* @param resource the resource whose candidates should be populated.
* @param resolution indicates the resolution type.
*/
- public final void populate(
- ResolveContext rc, Resource resource, int resolution) throws ResolutionException
+ public final ResolutionError populate(
+ ResolveContext rc, Resource resource, int resolution)
{
// Get the current result cache value, to make sure the revision
// hasn't already been populated.
Object cacheValue = m_populateResultCache.get(resource);
// Has been unsuccessfully populated.
- if (cacheValue instanceof ResolutionException)
+ if (cacheValue instanceof ResolutionError)
{
- return;
+ return null;
}
// Has been successfully populated.
else if (cacheValue instanceof Boolean)
{
- return;
+ return null;
}
// We will always attempt to populate fragments, since this is necessary
@@ -164,26 +163,21 @@
boolean isFragment = Util.isFragment(resource);
if (!isFragment && rc.getWirings().containsKey(resource))
{
- return;
+ return null;
}
if (resolution == MANDATORY)
{
m_mandatoryResources.add(resource);
}
- try
+ // Try to populate candidates for the optional revision.
+ ResolutionError error = populateResource(rc, resource);
+ // Only throw an exception if resolution is mandatory.
+ if (error != null && resolution == MANDATORY)
{
- // Try to populate candidates for the optional revision.
- populateResource(rc, resource);
+ return error;
}
- catch (ResolutionException ex)
- {
- // Only throw an exception if resolution is mandatory.
- if (resolution == MANDATORY)
- {
- throw ex;
- }
- }
+ return null;
}
/**
@@ -194,7 +188,7 @@
*/
// TODO: FELIX3 - Modify to not be recursive.
@SuppressWarnings("unchecked")
- private void populateResource(ResolveContext rc, Resource resource) throws ResolutionException
+ private ResolutionError populateResource(ResolveContext rc, Resource resource)
{
// Determine if we've already calculated this revision's candidates.
// The result cache will have one of three values:
@@ -226,14 +220,14 @@
Object cacheValue = m_populateResultCache.get(resource);
// This is case 1.
- if (cacheValue instanceof ResolutionException)
+ if (cacheValue instanceof ResolutionError)
{
- throw (ResolutionException) cacheValue;
+ return (ResolutionError) cacheValue;
}
// This is case 2.
else if (cacheValue instanceof Boolean)
{
- return;
+ return null;
}
// This is case 3.
else if (cacheValue != null)
@@ -286,14 +280,14 @@
// Process the candidates, removing any candidates that
// cannot resolve.
List<Capability> candidates = rc.findProviders(req);
- ResolutionException rethrow = processCandidates(rc, resource, candidates);
+ ResolutionError rethrow = processCandidates(rc, resource, candidates);
// First, due to cycles, makes sure we haven't already failed in
// a deeper recursion.
Object result = m_populateResultCache.get(resource);
- if (result instanceof ResolutionException)
+ if (result instanceof ResolutionError)
{
- throw (ResolutionException) result;
+ return (ResolutionError) result;
}
// Next, if are no candidates remaining and the requirement is not
// not optional, then record and throw a resolve exception.
@@ -303,17 +297,11 @@
{
// This is a fragment that is already resolved and there is no unresolved hosts to attach it to.
m_populateResultCache.put(resource, Boolean.TRUE);
- return;
+ return null;
}
- String msg = "Unable to resolve " + resource
- + ": missing requirement " + req;
- if (rethrow != null)
- {
- msg = msg + " [caused by: " + rethrow.getMessage() + "]";
- }
- rethrow = new ResolutionException(msg, null, Collections.singleton(req));
+ rethrow = new MissingRequirementError(req, rethrow);
m_populateResultCache.put(resource, rethrow);
- throw rethrow;
+ return rethrow;
}
// Otherwise, if we actually have candidates for the requirement, then
// add them to the local candidate map.
@@ -339,7 +327,7 @@
Map.Entry<Requirement, List<Capability>> entry = it.next();
for (Iterator<Capability> it2 = entry.getValue().iterator(); it2.hasNext();)
{
- if (m_populateResultCache.get(it2.next().getResource()) instanceof ResolutionException)
+ if (m_populateResultCache.get(it2.next().getResource()) instanceof ResolutionError)
{
it2.remove();
}
@@ -375,6 +363,7 @@
}
}
}
+ return null;
}
private void populateSubstitutables()
@@ -447,7 +436,7 @@
private static final int SUBSTITUTED = 2;
private static final int EXPORTED = 3;
- void checkSubstitutes(List<Candidates> importPermutations) throws ResolutionException
+ ResolutionError checkSubstitutes(List<Candidates> importPermutations)
{
Map<Capability, Integer> substituteStatuses = new LinkedHashMap<Capability, Integer>(m_subtitutableMap.size());
for (Capability substitutable : m_subtitutableMap.keySet())
@@ -516,15 +505,14 @@
}
else
{
- String msg = "Unable to resolve " + dependent.getResource()
- + ": missing requirement " + dependent;
- throw new ResolutionException(msg, null, Collections.singleton(dependent));
+ return new MissingRequirementError(dependent);
}
}
}
}
}
}
+ return null;
}
private boolean isSubstituted(Capability substitutableCap, Map<Capability, Integer> substituteStatuses)
@@ -581,9 +569,9 @@
return false;
}
- public void populateDynamic(
+ public ResolutionError populateDynamic(
ResolveContext rc, Resource resource,
- Requirement req, List<Capability> candidates) throws ResolutionException
+ Requirement req, List<Capability> candidates)
{
// Record the revision associated with the dynamic require
// as a mandatory revision.
@@ -591,7 +579,7 @@
// Process the candidates, removing any candidates that
// cannot resolve.
- ResolutionException rethrow = processCandidates(rc, resource, candidates);
+ ResolutionError rethrow = processCandidates(rc, resource, candidates);
// Add the dynamic imports candidates.
// Make sure this is done after the call to processCandidates since we want to ensure
@@ -602,13 +590,13 @@
{
if (rethrow == null)
{
- rethrow = new ResolutionException(
- "Dynamic import failed.", null, Collections.singleton(req));
+ rethrow = new DynamicImportFailed(req);
}
- throw rethrow;
+ return rethrow;
}
m_populateResultCache.put(resource, Boolean.TRUE);
+ return null;
}
/**
@@ -623,13 +611,13 @@
* @param candidates the candidates to process.
* @return a resolve exception to be re-thrown, if any, or null.
*/
- private ResolutionException processCandidates(
+ private ResolutionError processCandidates(
ResolveContext rc,
Resource resource,
List<Capability> candidates)
{
// Get satisfying candidates and populate their candidates if necessary.
- ResolutionException rethrow = null;
+ ResolutionError rethrow = null;
Set<Capability> fragmentCands = null;
for (Iterator<Capability> itCandCap = candidates.iterator();
itCandCap.hasNext();)
@@ -665,15 +653,12 @@
if ((isFragment || !rc.getWirings().containsKey(candCap.getResource()))
&& !candCap.getResource().equals(resource))
{
- try
- {
- populateResource(rc, candCap.getResource());
- }
- catch (ResolutionException ex)
+ ResolutionError error = populateResource(rc, candCap.getResource());
+ if (error != null)
{
if (rethrow == null)
{
- rethrow = ex;
+ rethrow = error;
}
// Remove the candidate since we weren't able to
// populate its candidates.
@@ -747,11 +732,11 @@
return ((value != null) && (value instanceof Boolean));
}
- public ResolutionException getResolveException(Resource resource)
+ public ResolutionError getResolutionError(Resource resource)
{
Object value = m_populateResultCache.get(resource);
- return ((value != null) && (value instanceof ResolutionException))
- ? (ResolutionException) value : null;
+ return ((value != null) && (value instanceof ResolutionError))
+ ? (ResolutionError) value : null;
}
/**
@@ -881,7 +866,7 @@
* @throws org.osgi.service.resolver.ResolutionException if the removal of any unselected fragments
* result in the root module being unable to resolve.
*/
- public void prepare(ResolveContext rc) throws ResolutionException
+ public ResolutionError prepare(ResolveContext rc)
{
// Maps a host capability to a map containing its potential fragments;
// the fragment map maps a fragment symbolic name to a map that maps
@@ -962,9 +947,7 @@
// Step 3
for (Resource fragment : unselectedFragments)
{
- removeResource(fragment,
- new ResolutionException(
- "Fragment was not selected for attachment: " + fragment));
+ removeResource(fragment, new FragmentNotSelectedError(fragment));
}
// Step 4
@@ -1081,7 +1064,7 @@
{
if (!isPopulated(resource))
{
- throw getResolveException(resource);
+ return getResolutionError(resource);
}
}
@@ -1089,6 +1072,8 @@
m_candidateMap.trim();
m_dependentMap.trim();
+
+ return null;
}
// Maps a host capability to a map containing its potential fragments;
@@ -1155,11 +1140,9 @@
* is no other candidate.
*
* @param resource the module to remove.
- * @throws ResolutionException if removing the module caused the resolve to
- * fail.
+ * @param ex the resolution error
*/
- private void removeResource(Resource resource, ResolutionException ex)
- throws ResolutionException
+ private void removeResource(Resource resource, ResolutionError ex)
{
// Add removal reason to result cache.
m_populateResultCache.put(resource, ex);
@@ -1185,11 +1168,8 @@
* @param unresolvedResources a list to containing any additional modules
* that that became unresolved as a result of removing this module and will
* also need to be removed.
- * @throws ResolutionException if removing the module caused the resolve to
- * fail.
*/
private void remove(Resource resource, Set<Resource> unresolvedResources)
- throws ResolutionException
{
for (Requirement r : resource.getRequirements(null))
{
@@ -1231,11 +1211,8 @@
* @param unresolvedResources a list to containing any additional modules
* that that became unresolved as a result of removing this module and will
* also need to be removed.
- * @throws ResolutionException if removing the module caused the resolve to
- * fail.
*/
private void remove(Capability c, Set<Resource> unresolvedResources)
- throws ResolutionException
{
Set<Requirement> dependents = m_dependentMap.remove(c);
if (dependents != null)
@@ -1249,11 +1226,9 @@
m_candidateMap.remove(r);
if (!Util.isOptional(r))
{
- String msg = "Unable to resolve " + r.getResource()
- + ": missing requirement " + r;
m_populateResultCache.put(
r.getResource(),
- new ResolutionException(msg, null, Collections.singleton(r)));
+ new MissingRequirementError(r));
unresolvedResources.add(r.getResource());
}
}
@@ -1369,4 +1344,66 @@
}
}
+ static class DynamicImportFailed extends ResolutionError {
+
+ private final Requirement requirement;
+
+ public DynamicImportFailed(Requirement requirement) {
+ this.requirement = requirement;
+ }
+
+ public String getMessage() {
+ return "Dynamic import failed.";
+ }
+
+ public Collection<Requirement> getUnresolvedRequirements() {
+ return Collections.singleton(requirement);
+ }
+
+ }
+
+ static class FragmentNotSelectedError extends ResolutionError {
+
+ private final Resource resource;
+
+ public FragmentNotSelectedError(Resource resource) {
+ this.resource = resource;
+ }
+
+ public String getMessage() {
+ return "Fragment was not selected for attachment: " + resource;
+ }
+
+ }
+
+ static class MissingRequirementError extends ResolutionError {
+
+ private final Requirement requirement;
+ private final ResolutionError cause;
+
+ public MissingRequirementError(Requirement requirement) {
+ this(requirement, null);
+ }
+
+ public MissingRequirementError(Requirement requirement, ResolutionError cause) {
+ this.requirement = requirement;
+ this.cause = cause;
+ }
+
+ public String getMessage() {
+ String msg = "Unable to resolve " + requirement.getResource()
+ + ": missing requirement " + requirement;
+ if (cause != null)
+ {
+ msg = msg + " [caused by: " + cause.getMessage() + "]";
+ }
+ return msg;
+ }
+
+ public Collection<Requirement> getUnresolvedRequirements() {
+ return Collections.singleton(requirement);
+ }
+
+ }
+
}
diff --git a/resolver/src/main/java/org/apache/felix/resolver/Logger.java b/resolver/src/main/java/org/apache/felix/resolver/Logger.java
index dbcaee9..d8c1b3a 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/Logger.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/Logger.java
@@ -129,7 +129,7 @@
}
}
- public void logUsesConstraintViolation(Resource resource, ResolutionException error)
+ public void logUsesConstraintViolation(Resource resource, ResolutionError error)
{
// do nothing by default
}
diff --git a/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java b/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java
new file mode 100644
index 0000000..0413839
--- /dev/null
+++ b/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java
@@ -0,0 +1,49 @@
+/*
+ * 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.resolver;
+
+import org.osgi.resource.Requirement;
+import org.osgi.service.resolver.ResolutionException;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Resolution error.
+ * This class contains all the needed information to build the ResolutionException
+ * without the need to actually compute a user friendly message, which can be
+ * quite time consuming.
+ */
+public abstract class ResolutionError {
+
+ public abstract String getMessage();
+
+ public Collection<Requirement> getUnresolvedRequirements() {
+ return Collections.emptyList();
+ }
+
+ public ResolutionException toException() {
+ return new ResolutionException(getMessage(), null, getUnresolvedRequirements());
+ }
+
+ @Override
+ public String toString() {
+ return getMessage();
+ }
+}
diff --git a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
index 00a8977..3bd6616 100644
--- a/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
+++ b/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
@@ -150,7 +150,11 @@
Resource resource = it.next();
if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
{
- allCandidates.populate(rc, resource, Candidates.MANDATORY);
+ ResolutionError error = allCandidates.populate(rc, resource, Candidates.MANDATORY);
+ if (error != null)
+ {
+ throw error.toException();
+ }
}
else
{
@@ -165,12 +169,20 @@
boolean isFragment = Util.isFragment(resource);
if (isFragment || (rc.getWirings().get(resource) == null))
{
- allCandidates.populate(rc, resource, Candidates.OPTIONAL);
+ ResolutionError error = allCandidates.populate(rc, resource, Candidates.OPTIONAL);
+ if (error != null)
+ {
+ throw error.toException();
+ }
}
}
// Merge any fragments into hosts.
- allCandidates.prepare(rc);
+ 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
@@ -192,8 +204,6 @@
// Record the initial candidate permutation.
usesPermutations.add(allCandidates);
- ResolutionException rethrow = null;
-
// 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.
@@ -209,7 +219,7 @@
}
Set<Object> processedDeltas = new HashSet<Object>();
- Map<Resource, ResolutionException> faultyResources = null;
+ Map<Resource, ResolutionError> faultyResources = null;
do
{
allCandidates = (usesPermutations.size() > 0)
@@ -229,8 +239,6 @@
continue;
}
- rethrow = null;
-
resourcePkgMap.clear();
session.getPackageSourcesCache().clear();
// Null out each time a new permutation is attempted.
@@ -240,14 +248,10 @@
//allCandidates.dump();
- Map<Resource, ResolutionException> currentFaultyResources = null;
- try
+ Map<Resource, ResolutionError> currentFaultyResources = null;
+ rethrow = allCandidates.checkSubstitutes(importPermutations);
+ if (rethrow != null)
{
- allCandidates.checkSubstitutes(importPermutations);
- }
- catch (ResolutionException e)
- {
- rethrow = e;
continue;
}
@@ -284,22 +288,18 @@
//dumpResourcePkgMap(resourcePkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");
- try
- {
- checkPackageSpaceConsistency(
+ rethrow = checkPackageSpaceConsistency(
session, allCandidates.getWrappedHost(target),
allCandidates, resourcePkgMap, resultCache);
- }
- catch (ResolutionException ex)
+ if (rethrow != null)
{
- rethrow = ex;
if (currentFaultyResources == null)
{
- currentFaultyResources = new HashMap<Resource, ResolutionException>();
+ currentFaultyResources = new HashMap<Resource, ResolutionError>();
}
Resource faultyResource = resource;
// check that the faulty requirement is not from a fragment
- for (Requirement faultyReq : ex.getUnresolvedRequirements())
+ for (Requirement faultyReq : rethrow.getUnresolvedRequirements())
{
if (faultyReq instanceof WrappedRequirement)
{
@@ -309,7 +309,7 @@
break;
}
}
- currentFaultyResources.put(faultyResource, ex);
+ currentFaultyResources.put(faultyResource, rethrow);
}
}
if (currentFaultyResources != null)
@@ -349,14 +349,14 @@
}
}
// log all the resolution exceptions for the uses constraint violations
- for (Map.Entry<Resource, ResolutionException> usesError : faultyResources.entrySet())
+ for (Map.Entry<Resource, ResolutionError> usesError : faultyResources.entrySet())
{
m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue());
}
}
if (!retry)
{
- throw rethrow;
+ throw rethrow.toException();
}
}
// If there is no exception to rethrow, then this was a clean
@@ -479,9 +479,16 @@
// Create all candidates pre-populated with the single candidate set
// for the resolving dynamic import of the host.
Candidates allCandidates = new Candidates(onDemandResources);
- allCandidates.populateDynamic(rc, host, dynamicReq, matches);
- // Merge any fragments into hosts.
- allCandidates.prepare(rc);
+ 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 = session.getUsesPermutations();
List<Candidates> importPermutations = session.getImportPermutations();
@@ -489,12 +496,8 @@
// Record the initial candidate permutation.
usesPermutations.add(allCandidates);
- ResolutionException rethrow;
-
do
{
- rethrow = null;
-
resourcePkgMap.clear();
session.getPackageSourcesCache().clear();
@@ -503,13 +506,9 @@
: importPermutations.remove(0);
//allCandidates.dump();
- try
+ rethrow = allCandidates.checkSubstitutes(importPermutations);
+ if (rethrow != null)
{
- allCandidates.checkSubstitutes(importPermutations);
- }
- catch (ResolutionException e)
- {
- rethrow = e;
continue;
}
// For a dynamic import, the instigating resource
@@ -525,16 +524,9 @@
//dumpResourcePkgMap(resourcePkgMap);
//System.out.println("+++ PACKAGE SPACES END +++");
- try
- {
- checkDynamicPackageSpaceConsistency(session,
+ rethrow = checkDynamicPackageSpaceConsistency(session,
allCandidates.getWrappedHost(host),
allCandidates, resourcePkgMap, new HashMap<Resource, Object>(64));
- }
- catch (ResolutionException ex)
- {
- rethrow = ex;
- }
}
while ((rethrow != null)
&& ((usesPermutations.size() > 0) || (importPermutations.size() > 0)));
@@ -567,7 +559,7 @@
}
else
{
- throw rethrow;
+ throw rethrow.toException();
}
}
// If there is no exception to rethrow, then this was a clean
@@ -1168,36 +1160,37 @@
addToBlame.addBlame(newBlame, matchingCap);
}
- private void checkPackageSpaceConsistency(
+ private ResolutionError checkPackageSpaceConsistency(
ResolveSession session,
Resource resource,
Candidates allCandidates,
Map<Resource, Packages> resourcePkgMap,
- Map<Resource, Object> resultCache) throws ResolutionException
+ Map<Resource, Object> resultCache)
{
if (session.getContext().getWirings().containsKey(resource))
{
- return;
+ return null;
}
- checkDynamicPackageSpaceConsistency(
+ return checkDynamicPackageSpaceConsistency(
session, resource, allCandidates, resourcePkgMap, resultCache);
}
- private void checkDynamicPackageSpaceConsistency(
+ private ResolutionError checkDynamicPackageSpaceConsistency(
ResolveSession session,
Resource resource,
Candidates allCandidates,
Map<Resource, Packages> resourcePkgMap,
- Map<Resource, Object> resultCache) throws ResolutionException
+ Map<Resource, Object> resultCache)
{
- if (resultCache.containsKey(resource))
+ Object cache = resultCache.get(resource);
+ if (cache != null)
{
- return;
+ return cache instanceof ResolutionError ? (ResolutionError) cache : null;
}
Packages pkgs = resourcePkgMap.get(resource);
- ResolutionException rethrow = null;
+ ResolutionError rethrow = null;
Candidates permutation = null;
Set<Requirement> mutated = null;
@@ -1226,30 +1219,18 @@
// Try to permutate the source requirement.
allCandidates.permutate(sourceBlame.m_reqs.get(0), importPermutations);
// Report conflict.
- ResolutionException ex = new ResolutionException(
- "Uses constraint violation. Unable to resolve resource "
- + Util.getSymbolicName(resource)
- + " [" + resource
- + "] because it is exposed to package '"
- + entry.getKey()
- + "' from resources "
- + Util.getSymbolicName(sourceBlame.m_cap.getResource())
- + " [" + sourceBlame.m_cap.getResource()
- + "] and "
- + Util.getSymbolicName(blame.m_cap.getResource())
- + " [" + blame.m_cap.getResource()
- + "] via two dependency chains.\n\nChain 1:\n"
- + toStringBlame(session.getContext(), allCandidates, sourceBlame)
- + "\n\nChain 2:\n"
- + toStringBlame(session.getContext(), allCandidates, blame),
- null,
- Collections.singleton(blame.m_reqs.get(0)));
- m_logger.log(
- Logger.LOG_DEBUG,
- "Candidate permutation failed due to a conflict with a "
- + "fragment import; will try another if possible.",
- ex);
- throw ex;
+ rethrow = new UseConstraintError(
+ session.getContext(), allCandidates,
+ resource, entry.getKey(),
+ sourceBlame, blame);
+ if (m_logger.isDebugEnabled())
+ {
+ m_logger.debug(
+ "Candidate permutation failed due to a conflict with a "
+ + "fragment import; will try another if possible."
+ + " (" + rethrow.getMessage() + ")");
+ }
+ return rethrow;
}
}
}
@@ -1281,22 +1262,11 @@
permutation = (permutation != null)
? permutation
: allCandidates.copy();
- rethrow = (rethrow != null)
- ? rethrow
- : new ResolutionException(
- "Uses constraint violation. Unable to resolve resource "
- + Util.getSymbolicName(resource)
- + " [" + resource
- + "] because it exports package '"
- + pkgName
- + "' and is also exposed to it from resource "
- + Util.getSymbolicName(usedBlame.m_cap.getResource())
- + " [" + usedBlame.m_cap.getResource()
- + "] via the following dependency chain:\n\n"
- + toStringBlame(session.getContext(), allCandidates, usedBlame),
- null,
- null);
-
+ if (rethrow == null)
+ {
+ rethrow = new UseConstraintError(
+ session.getContext(), allCandidates, resource, pkgName, usedBlame);
+ }
mutated = (mutated != null)
? mutated
: new HashSet<Requirement>();
@@ -1336,12 +1306,13 @@
{
usesPermutations.add(permutation);
}
- m_logger.log(
- Logger.LOG_DEBUG,
- "Candidate permutation failed due to a conflict between "
- + "an export and import; will try another if possible.",
- rethrow);
- throw rethrow;
+ if (m_logger.isDebugEnabled())
+ {
+ m_logger.debug("Candidate permutation failed due to a conflict between "
+ + "an export and import; will try another if possible."
+ + " (" + rethrow.getMessage() + ")");
+ }
+ return rethrow;
}
}
@@ -1382,26 +1353,13 @@
permutation = (permutation != null)
? permutation
: allCandidates.copy();
- rethrow = (rethrow != null)
- ? rethrow
- : new ResolutionException(
- "Uses constraint violation. Unable to resolve resource "
- + Util.getSymbolicName(resource)
- + " [" + resource
- + "] because it is exposed to package '"
- + pkgName
- + "' from resources "
- + Util.getSymbolicName(requirementBlame.m_cap.getResource())
- + " [" + requirementBlame.m_cap.getResource()
- + "] and "
- + Util.getSymbolicName(usedBlame.m_cap.getResource())
- + " [" + usedBlame.m_cap.getResource()
- + "] via two dependency chains.\n\nChain 1:\n"
- + toStringBlame(session.getContext(), allCandidates, requirementBlame)
- + "\n\nChain 2:\n"
- + toStringBlame(session.getContext(), allCandidates, usedBlame),
- null,
- null);
+ if (rethrow == null)
+ {
+ rethrow = new UseConstraintError(
+ session.getContext(), allCandidates,
+ resource, pkgName,
+ requirementBlame, usedBlame);
+ }
mutated = (mutated != null)
? mutated
@@ -1465,12 +1423,14 @@
}
}
- m_logger.log(
- Logger.LOG_DEBUG,
- "Candidate permutation failed due to a conflict between "
- + "imports; will try another if possible.",
- rethrow);
- throw rethrow;
+ if (m_logger.isDebugEnabled())
+ {
+ m_logger.debug("Candidate permutation failed due to a conflict between "
+ + "imports; will try another if possible."
+ + " (" + rethrow.getMessage() + ")"
+ );
+ }
+ return rethrow;
}
}
}
@@ -1489,13 +1449,10 @@
{
if (!resource.equals(cap.getResource()))
{
- try
- {
- checkPackageSpaceConsistency(
+ rethrow = checkPackageSpaceConsistency(
session, cap.getResource(),
allCandidates, resourcePkgMap, resultCache);
- }
- catch (ResolutionException ex)
+ if (rethrow != null)
{
// If the lower level check didn't create any permutations,
// then we should create an import permutation for the
@@ -1505,11 +1462,12 @@
{
allCandidates.permutate(req, importPermutations);
}
- throw ex;
+ return rethrow;
}
}
}
}
+ return null;
}
private boolean checkMultiple(
@@ -2247,4 +2205,201 @@
return m_blames.toString();
}
}
+
+ private static final class UseConstraintError extends ResolutionError {
+
+ private final ResolveContext m_context;
+ private final Candidates m_allCandidates;
+ private final Resource m_resource;
+ private final String m_pkgName;
+ private final Blame m_blame1;
+ private final Blame m_blame2;
+
+ public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame) {
+ this(context, allCandidates, resource, pkgName, blame, null);
+ }
+
+ public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame1, Blame blame2) {
+ this.m_context = context;
+ this.m_allCandidates = allCandidates;
+ this.m_resource = resource;
+ this.m_pkgName = pkgName;
+ this.m_blame1 = blame1;
+ this.m_blame2 = blame2;
+ }
+
+ public String getMessage() {
+ if (m_blame2 == null)
+ {
+ return "Uses constraint violation. Unable to resolve resource "
+ + Util.getSymbolicName(m_resource)
+ + " [" + m_resource
+ + "] because it exports package '"
+ + m_pkgName
+ + "' and is also exposed to it from resource "
+ + Util.getSymbolicName(m_blame1.m_cap.getResource())
+ + " [" + m_blame1.m_cap.getResource()
+ + "] via the following dependency chain:\n\n"
+ + toStringBlame(m_blame1);
+ }
+ else
+ {
+ return "Uses constraint violation. Unable to resolve resource "
+ + Util.getSymbolicName(m_resource)
+ + " [" + m_resource
+ + "] because it is exposed to package '"
+ + m_pkgName
+ + "' from resources "
+ + Util.getSymbolicName(m_blame1.m_cap.getResource())
+ + " [" + m_blame1.m_cap.getResource()
+ + "] and "
+ + Util.getSymbolicName(m_blame2.m_cap.getResource())
+ + " [" + m_blame2.m_cap.getResource()
+ + "] via two dependency chains.\n\nChain 1:\n"
+ + toStringBlame(m_blame1)
+ + "\n\nChain 2:\n"
+ + toStringBlame(m_blame2);
+ }
+ }
+
+ public Collection<Requirement> getUnresolvedRequirements() {
+ if (m_blame2 == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return Collections.singleton(m_blame2.m_reqs.get(0));
+ }
+ }
+
+ private String toStringBlame(Blame blame)
+ {
+ StringBuilder sb = new StringBuilder();
+ if ((blame.m_reqs != null) && !blame.m_reqs.isEmpty())
+ {
+ for (int i = 0; i < blame.m_reqs.size(); i++)
+ {
+ Requirement req = blame.m_reqs.get(i);
+ sb.append(" ");
+ sb.append(Util.getSymbolicName(req.getResource()));
+ sb.append(" [");
+ sb.append(req.getResource().toString());
+ sb.append("]\n");
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append(" import: ");
+ }
+ else
+ {
+ sb.append(" require: ");
+ }
+ sb.append(req.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE));
+ sb.append("\n |");
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append("\n export: ");
+ }
+ else
+ {
+ sb.append("\n provide: ");
+ }
+ if ((i + 1) < blame.m_reqs.size())
+ {
+ Capability cap = getSatisfyingCapability(blame.m_reqs.get(i));
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append(PackageNamespace.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(cap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE));
+ Capability usedCap =
+ getSatisfyingCapability(blame.m_reqs.get(i + 1));
+ sb.append("; uses:=");
+ sb.append(usedCap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE));
+ }
+ else
+ {
+ sb.append(cap);
+ }
+ sb.append("\n");
+ }
+ else
+ {
+ Capability export = getSatisfyingCapability(blame.m_reqs.get(i));
+ sb.append(export.getNamespace());
+ sb.append(": ");
+ Object namespaceVal = export.getAttributes().get(export.getNamespace());
+ if (namespaceVal != null)
+ {
+ sb.append(namespaceVal.toString());
+ }
+ else
+ {
+ for (Entry<String, Object> attrEntry : export.getAttributes().entrySet())
+ {
+ sb.append(attrEntry.getKey()).append('=')
+ .append(attrEntry.getValue()).append(';');
+ }
+ }
+ if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
+ && !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
+ .equals(blame.m_cap.getAttributes().get(
+ PackageNamespace.PACKAGE_NAMESPACE)))
+ {
+ sb.append("; uses:=");
+ sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
+ sb.append("\n export: ");
+ sb.append(PackageNamespace.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(blame.m_cap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE));
+ }
+ sb.append("\n ");
+ sb.append(Util.getSymbolicName(blame.m_cap.getResource()));
+ sb.append(" [");
+ sb.append(blame.m_cap.getResource().toString());
+ sb.append("]");
+ }
+ }
+ }
+ else
+ {
+ sb.append(blame.m_cap.getResource().toString());
+ }
+ return sb.toString();
+ }
+
+ private Capability getSatisfyingCapability(Requirement req)
+ {
+ // If the requiring revision is not resolved, then check in the
+ // candidate map for its matching candidate.
+ Capability cap = m_allCandidates.getFirstCandidate(req);
+ // Otherwise, if the requiring revision is resolved then check
+ // in its wires for the capability satisfying the requirement.
+ if (cap == null && m_context.getWirings().containsKey(req.getResource()))
+ {
+ List<Wire> wires =
+ m_context.getWirings().get(req.getResource()).getRequiredResourceWires(null);
+ req = getDeclaredRequirement(req);
+ for (Wire w : wires)
+ {
+ if (w.getRequirement().equals(req))
+ {
+ // TODO: RESOLVER - This is not 100% correct, since requirements for
+ // dynamic imports with wildcards will reside on many wires and
+ // this code only finds the first one, not necessarily the correct
+ // one. This is only used for the diagnostic message, but it still
+ // could confuse the user.
+ cap = w.getCapability();
+ break;
+ }
+ }
+ }
+
+ return cap;
+ }
+ }
+
}