You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2015/07/13 17:16:50 UTC

svn commit: r1690702 - in /felix/trunk/resolver/src/main/java/org/apache/felix/resolver: Candidates.java Logger.java ResolutionError.java ResolverImpl.java

Author: gnodet
Date: Mon Jul 13 15:16:49 2015
New Revision: 1690702

URL: http://svn.apache.org/r1690702
Log:
[FELIX-4942] Avoid creating and throwing exceptions to report resolution problems internally. Also lazily create the error messages reported to the user.

Added:
    felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java
Modified:
    felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
    felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Logger.java
    felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java

Modified: felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Candidates.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Candidates.java?rev=1690702&r1=1690701&r2=1690702&view=diff
==============================================================================
--- felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Candidates.java (original)
+++ felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Candidates.java Mon Jul 13 15:16:49 2015
@@ -47,7 +47,6 @@ 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;
 
 class Candidates
@@ -140,21 +139,21 @@ class Candidates
      * @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 @@ class Candidates
         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);
-        }
-        catch (ResolutionException ex)
-        {
-            // Only throw an exception if resolution is mandatory.
-            if (resolution == MANDATORY)
-            {
-                throw ex;
-            }
+            return error;
         }
+        return null;
     }
 
     /**
@@ -194,7 +188,7 @@ class Candidates
      */
 // 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 @@ class Candidates
         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 @@ class Candidates
             // 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 @@ class Candidates
                 {
                     // 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;
-                }
-                String msg = "Unable to resolve " + resource
-                    + ": missing requirement " + req;
-                if (rethrow != null)
-                {
-                    msg = msg + " [caused by: " + rethrow.getMessage() + "]";
+                    return null;
                 }
-                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 @@ class Candidates
                 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 @@ class Candidates
                 }
             }
         }
+        return null;
     }
 
     private void populateSubstitutables()
@@ -447,7 +436,7 @@ class Candidates
     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 @@ class Candidates
                             }
                             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 @@ class Candidates
         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 @@ class Candidates
 
         // 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 @@ class Candidates
         {
             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 @@ class Candidates
      * @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 @@ class Candidates
             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 @@ class Candidates
         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 @@ class Candidates
      * @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 @@ class Candidates
         // 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 @@ class Candidates
         {
             if (!isPopulated(resource))
             {
-                throw getResolveException(resource);
+                return getResolutionError(resource);
             }
         }
 
@@ -1089,6 +1072,8 @@ class Candidates
 
         m_candidateMap.trim();
         m_dependentMap.trim();
+
+        return null;
     }
 
     // Maps a host capability to a map containing its potential fragments;
@@ -1155,11 +1140,9 @@ class Candidates
      * 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 @@ class Candidates
      * @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 @@ class Candidates
      * @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 @@ class Candidates
                     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 @@ class Candidates
         }
     }
 
+    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);
+        }
+
+    }
+
 }

Modified: felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Logger.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Logger.java?rev=1690702&r1=1690701&r2=1690702&view=diff
==============================================================================
--- felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Logger.java (original)
+++ felix/trunk/resolver/src/main/java/org/apache/felix/resolver/Logger.java Mon Jul 13 15:16:49 2015
@@ -129,7 +129,7 @@ public class Logger
         }
     }
 
-    public void logUsesConstraintViolation(Resource resource, ResolutionException error)
+    public void logUsesConstraintViolation(Resource resource, ResolutionError error)
     {
         // do nothing by default
     }

Added: felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java?rev=1690702&view=auto
==============================================================================
--- felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java (added)
+++ felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolutionError.java Mon Jul 13 15:16:49 2015
@@ -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();
+    }
+}

Modified: felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java?rev=1690702&r1=1690701&r2=1690702&view=diff
==============================================================================
--- felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java (original)
+++ felix/trunk/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java Mon Jul 13 15:16:49 2015
@@ -150,7 +150,11 @@ public class ResolverImpl implements Res
                     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 @@ public class ResolverImpl implements Res
                     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 @@ public class ResolverImpl implements Res
                 // 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 @@ public class ResolverImpl implements Res
                 }
 
                 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 @@ public class ResolverImpl implements Res
                         continue;
                     }
 
-                    rethrow = null;
-
                     resourcePkgMap.clear();
                     session.getPackageSourcesCache().clear();
                     // Null out each time a new permutation is attempted.
@@ -240,14 +248,10 @@ public class ResolverImpl implements Res
 
 //allCandidates.dump();
 
-                    Map<Resource, ResolutionException> currentFaultyResources = null;
-                    try
-                    {
-                        allCandidates.checkSubstitutes(importPermutations);
-                    }
-                    catch (ResolutionException e)
+                    Map<Resource, ResolutionError> currentFaultyResources = null;
+                    rethrow = allCandidates.checkSubstitutes(importPermutations);
+                    if (rethrow != null)
                     {
-                        rethrow = e;
                         continue;
                     }
 
@@ -284,22 +288,18 @@ public class ResolverImpl implements Res
 //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 @@ public class ResolverImpl implements Res
                                     break;
                                 }
                             }
-                            currentFaultyResources.put(faultyResource, ex);
+                            currentFaultyResources.put(faultyResource, rethrow);
                         }
                     }
                     if (currentFaultyResources != null)
@@ -349,14 +349,14 @@ public class ResolverImpl implements Res
                             }
                         }
                         // 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 @@ public class ResolverImpl implements Res
                     // 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 @@ public class ResolverImpl implements Res
                     // Record the initial candidate permutation.
                     usesPermutations.add(allCandidates);
 
-                    ResolutionException rethrow;
-
                     do
                     {
-                        rethrow = null;
-
                         resourcePkgMap.clear();
                         session.getPackageSourcesCache().clear();
 
@@ -503,13 +506,9 @@ public class ResolverImpl implements Res
                             : importPermutations.remove(0);
 //allCandidates.dump();
 
-                        try
-                        {
-                            allCandidates.checkSubstitutes(importPermutations);
-                        }
-                        catch (ResolutionException e)
+                        rethrow = allCandidates.checkSubstitutes(importPermutations);
+                        if (rethrow != null)
                         {
-                            rethrow = e;
                             continue;
                         }
                         // For a dynamic import, the instigating resource
@@ -525,16 +524,9 @@ public class ResolverImpl implements Res
 //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 @@ public class ResolverImpl implements Res
                         }
                         else
                         {
-                            throw rethrow;
+                            throw rethrow.toException();
                         }
                     }
                     // If there is no exception to rethrow, then this was a clean
@@ -1168,36 +1160,37 @@ public class ResolverImpl implements Res
         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 @@ public class ResolverImpl implements Res
                         // 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 @@ public class ResolverImpl implements Res
                         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 @@ public class ResolverImpl implements Res
                 {
                     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 @@ public class ResolverImpl implements Res
                         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 @@ public class ResolverImpl implements Res
                         }
                     }
 
-                    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 @@ public class ResolverImpl implements Res
             {
                 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 @@ public class ResolverImpl implements Res
                         {
                             allCandidates.permutate(req, importPermutations);
                         }
-                        throw ex;
+                        return rethrow;
                     }
                 }
             }
         }
+        return null;
     }
 
     private boolean checkMultiple(
@@ -2247,4 +2205,201 @@ public class ResolverImpl implements Res
             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;
+        }
+    }
+
 }