You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2021/05/22 11:32:25 UTC

[maven-dependency-tree] branch MSHARED-788 created (now 9608318)

This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a change to branch MSHARED-788
in repository https://gitbox.apache.org/repos/asf/maven-dependency-tree.git.


      at 9608318  Adjust Exception Fix @since

This branch includes the following new commits:

     new b55e83e  Add functionality to collect raw dependencies in Maven 3+
     new 7f663c5  Fixed to also return dependencies with scope "provided".
     new 758de2b  Corrected some problems discovered with getting the right level of filtering of the dependencies.
     new 9608318  Adjust Exception Fix @since

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[maven-dependency-tree] 04/04: Adjust Exception Fix @since

Posted by rf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a commit to branch MSHARED-788
in repository https://gitbox.apache.org/repos/asf/maven-dependency-tree.git

commit 960831889c24110d0ab3dfcae0f1d5d43269b401
Author: rfscholte <rf...@apache.org>
AuthorDate: Sat May 22 13:32:07 2021 +0200

    Adjust Exception
    Fix @since
---
 .../graph/DependencyCollectorBuilder.java          |  4 +-
 .../graph/DependencyCollectorBuilderException.java | 54 ++++++++++++++++
 .../DefaultDependencyCollectorBuilder.java         |  8 +--
 .../graph/internal/ExceptionHandler.java           | 30 +++++++++
 .../shared/dependency/graph/internal/Invoker.java  | 74 +++++++---------------
 .../Maven31DependencyCollectorBuilder.java         | 69 ++++++++++++++------
 .../internal/Maven31DependencyGraphBuilder.java    | 20 +++++-
 .../internal/Maven3DependencyCollectorBuilder.java | 60 ++++++++++++++----
 .../internal/Maven3DependencyGraphBuilder.java     |  2 +-
 .../graph/internal/maven30/ConflictIdSorter.java   |  2 +-
 .../graph/internal/maven30/ConflictResolver.java   |  2 +-
 .../maven30/ExclusionDependencySelector.java       |  2 +-
 .../graph/internal/maven30/JavaScopeDeriver.java   |  2 +-
 .../graph/internal/maven30/JavaScopeSelector.java  |  2 +-
 .../Maven3DirectScopeDependencySelector.java       |  2 +-
 .../internal/maven30/NearestVersionSelector.java   |  2 +-
 .../maven30/SimpleOptionalitySelector.java         |  2 +-
 .../Maven31DirectScopeDependencySelector.java      |  2 +-
 18 files changed, 233 insertions(+), 106 deletions(-)

diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java
index 8865fad..43cfb40 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java
@@ -28,7 +28,7 @@ import org.apache.maven.project.ProjectBuildingRequest;
  * particular Aether implementations.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public interface DependencyCollectorBuilder
 {
@@ -45,6 +45,6 @@ public interface DependencyCollectorBuilder
      */
     DependencyNode collectDependencyGraph( ArtifactRepository localRepository, ProjectBuildingRequest buildingRequest,
                                          ArtifactFilter filter )
-        throws DependencyGraphBuilderException;
+        throws DependencyCollectorBuilderException;
 
 }
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilderException.java b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilderException.java
new file mode 100644
index 0000000..866213d
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilderException.java
@@ -0,0 +1,54 @@
+package org.apache.maven.shared.dependency.graph;
+
+/*
+ * 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.
+ */
+
+/**
+ * Indicates an issue with the DependencyCollectorBuilder
+ * 
+ * @author Robert Scholte
+ * @since 3.1.0
+ */
+public class DependencyCollectorBuilderException
+    extends Exception
+{
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1305852327231950979L;
+
+    // constructors -----------------------------------------------------------
+
+    /**
+     * @param message   Message indicating why dependency graph could not be resolved.
+     */
+    public DependencyCollectorBuilderException( String message )
+    {
+        super( message );
+    }
+
+    /**
+     * @param message   Message indicating why dependency graph could not be resolved.
+     * @param cause     Throwable indicating at which point the graph failed to be resolved.
+     */
+    public DependencyCollectorBuilderException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java
index ff420b9..e29cc9d 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java
@@ -24,7 +24,7 @@ import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
 import org.codehaus.plexus.PlexusConstants;
 import org.codehaus.plexus.PlexusContainer;
@@ -40,7 +40,7 @@ import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
  * 3.1+ particular Aether implementations.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 @Component( role = DependencyCollectorBuilder.class )
 public class DefaultDependencyCollectorBuilder
@@ -52,7 +52,7 @@ public class DefaultDependencyCollectorBuilder
     @Override
     public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
                                                 ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         try
         {
@@ -74,7 +74,7 @@ public class DefaultDependencyCollectorBuilder
         }
         catch ( ComponentLookupException e )
         {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
+            throw new DependencyCollectorBuilderException( e.getMessage(), e );
         }
     }
 
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/ExceptionHandler.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/ExceptionHandler.java
new file mode 100644
index 0000000..2dd86cc
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/ExceptionHandler.java
@@ -0,0 +1,30 @@
+package org.apache.maven.shared.dependency.graph.internal;
+
+/*
+ * 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.
+ */
+
+/**
+ * 
+ * 
+ * @param <T>
+ */
+interface ExceptionHandler<T extends Exception>
+{
+    T create( String message, Exception exception );
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
index cabfe37..8cc8851 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
@@ -19,8 +19,6 @@ package org.apache.maven.shared.dependency.graph.internal;
  * under the License.
  */
 
-import java.lang.reflect.InvocationTargetException;
-
 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
 
 /**
@@ -33,34 +31,27 @@ final class Invoker
         // do not instantiate
     }
 
-    public static Object invoke( Object object, String method )
-        throws DependencyGraphBuilderException
+    static <T extends Exception> Object invoke( Object object, String method, ExceptionHandler<T> exceptionHandler )
+        throws T
     {
-        return invoke( object.getClass(), object, method );
+        return invoke( object.getClass(), object, method, exceptionHandler );
     }
 
-    public static Object invoke( Class<?> objectClazz, Object object, String method )
-        throws DependencyGraphBuilderException
+    static <T extends Exception> Object invoke( Class<?> objectClazz, Object object, String method,
+                                                ExceptionHandler<T> exceptionHandler )
+        throws T
     {
         try
         {
             return objectClazz.getMethod( method ).invoke( object );
         }
-        catch ( IllegalAccessException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( NoSuchMethodException e )
+        catch ( ReflectiveOperationException e )
         {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
+            throw exceptionHandler.create( e.getMessage(), e );
         }
     }
 
-    public static Object invoke( Object object, String method, Class<?> clazz, Object arg )
+    static Object invoke( Object object, String method, Class<?> clazz, Object arg )
         throws DependencyGraphBuilderException
     {
         try
@@ -68,60 +59,39 @@ final class Invoker
             final Class<?> objectClazz = object.getClass();
             return objectClazz.getMethod( method, clazz ).invoke( object, arg );
         }
-        catch ( IllegalAccessException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( NoSuchMethodException e )
+        catch ( ReflectiveOperationException e )
         {
             throw new DependencyGraphBuilderException( e.getMessage(), e );
         }
     }
     
-    public static Object invoke( Class<?> objectClazz, String staticMethod, Class<?> argClazz, Object arg )
-                    throws DependencyGraphBuilderException
+    static <T extends Exception> Object invoke( Class<?> objectClazz, String staticMethod,
+                                                               Class<?> argClazz, Object arg,
+                                                               ExceptionHandler<T> exceptionHandler )
+        throws T
     {
         try
         {
             return objectClazz.getMethod( staticMethod, argClazz ).invoke( null, arg );
         }
-        catch ( IllegalAccessException e )
+        catch ( ReflectiveOperationException e )
         {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( NoSuchMethodException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
+            throw exceptionHandler.create( e.getMessage(), e );
         }
     }
 
-    public static Object invoke( Class<?> objectClazz, String staticMethod, Class<?> argClazz1, Class<?> argClazz2,
-                                 Object arg1, Object arg2 )
-        throws DependencyGraphBuilderException
+    static <T extends Exception> Object invoke( Class<?> objectClazz, String staticMethod, Class<?> argClazz1,
+                                                Class<?> argClazz2, Object arg1, Object arg2,
+                                                ExceptionHandler<T> exceptionHandler )
+        throws T
     {
         try
         {
             return objectClazz.getMethod( staticMethod, argClazz1, argClazz2 ).invoke( null, arg1, arg2 );
         }
-        catch ( IllegalAccessException e )
+        catch ( ReflectiveOperationException e )
         {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
-        }
-        catch ( NoSuchMethodException e )
-        {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
+            throw exceptionHandler.create( e.getMessage(), e );
         }
     }
 }
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
index 62e659c..15c173d 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
@@ -31,7 +31,7 @@ import org.apache.maven.model.Dependency;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
 import org.apache.maven.shared.dependency.graph.internal.maven31.Maven31DirectScopeDependencySelector;
 import org.codehaus.plexus.component.annotations.Component;
@@ -46,6 +46,7 @@ import org.eclipse.aether.collection.DependencyCollectionException;
 import org.eclipse.aether.collection.DependencyGraphTransformer;
 import org.eclipse.aether.collection.DependencySelector;
 import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.graph.Exclusion;
 import org.eclipse.aether.util.artifact.JavaScopes;
 import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
 import org.eclipse.aether.util.graph.selector.AndDependencySelector;
@@ -63,7 +64,7 @@ import org.eclipse.aether.version.VersionConstraint;
  * Project dependency raw dependency collector API, abstracting Maven 3.1+'s Aether implementation.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 @Component( role = DependencyCollectorBuilder.class, hint = "maven31" )
 public class Maven31DependencyCollectorBuilder
@@ -72,11 +73,25 @@ public class Maven31DependencyCollectorBuilder
 {
     @Requirement
     private RepositorySystem repositorySystem;
+    
+    private final ExceptionHandler<DependencyCollectorBuilderException> exceptionHandler;
+    
+    public Maven31DependencyCollectorBuilder()
+    {
+        this.exceptionHandler = new ExceptionHandler<DependencyCollectorBuilderException>()
+        {
+            @Override
+            public DependencyCollectorBuilderException create( String message, Exception exception )
+            {
+                return new DependencyCollectorBuilderException( message, exception );
+            }
+        };
+    }
 
     @Override
     public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
                                                   ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         DefaultRepositorySystemSession session = null;
         try
@@ -87,7 +102,8 @@ public class Maven31DependencyCollectorBuilder
             List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
 
             DefaultRepositorySystemSession repositorySession =
-                (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
+                (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession",
+                                                                 exceptionHandler );
 
             session = new DefaultRepositorySystemSession( repositorySession );
 
@@ -107,13 +123,15 @@ public class Maven31DependencyCollectorBuilder
 
             org.eclipse.aether.artifact.Artifact aetherArtifact =
                 (org.eclipse.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
-                                                                       Artifact.class, projectArtifact );
+                                                                       Artifact.class, projectArtifact,
+                                                                       exceptionHandler );
 
             @SuppressWarnings( "unchecked" )
             List<org.eclipse.aether.repository.RemoteRepository> aetherRepos =
                 (List<org.eclipse.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos",
                                                                                        List.class,
-                                                                                       remoteArtifactRepositories );
+                                                                                       remoteArtifactRepositories,
+                                                                                       exceptionHandler );
 
             CollectRequest collectRequest = new CollectRequest();
             collectRequest.setRoot( new org.eclipse.aether.graph.Dependency( aetherArtifact, "" ) );
@@ -127,12 +145,6 @@ public class Maven31DependencyCollectorBuilder
 
             org.eclipse.aether.graph.DependencyNode rootNode = collectResult.getRoot();
 
-//            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
-//            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
-//            rootNode.accept( treeVisitor );
-//
-//            rootNode = cloner.getRootNode();
-
             if ( getLogger().isDebugEnabled() )
             {
                 logTree( rootNode );
@@ -142,7 +154,7 @@ public class Maven31DependencyCollectorBuilder
         }
         catch ( DependencyCollectionException e )
         {
-            throw new DependencyGraphBuilderException( "Could not collect dependencies: " + e.getResult(), e );
+            throw new DependencyCollectorBuilderException( "Could not collect dependencies: " + e.getResult(), e );
         }
         finally
         {
@@ -180,7 +192,7 @@ public class Maven31DependencyCollectorBuilder
 
     private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
                                                ArtifactTypeRegistry stereotypes )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         if ( project.getDependencyManagement() != null )
         {
@@ -194,7 +206,7 @@ public class Maven31DependencyCollectorBuilder
 
     private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
                                         org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         for ( Dependency dependency : project.getDependencies() )
         {
@@ -206,13 +218,13 @@ public class Maven31DependencyCollectorBuilder
     // CHECKSTYLE_OFF: LineLength
     private org.eclipse.aether.graph.Dependency toAetherDependency( org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes,
                                                                     Dependency dependency )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         org.eclipse.aether.graph.Dependency aetherDep =
             (org.eclipse.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
                                                                   Dependency.class,
                                                                   org.eclipse.aether.artifact.ArtifactTypeRegistry.class,
-                                                                  dependency, stereotypes );
+                                                                  dependency, stereotypes, exceptionHandler );
         return aetherDep;
     }
     // CHECKSTYLE_ON: LineLength
@@ -223,15 +235,16 @@ public class Maven31DependencyCollectorBuilder
 
         try
         {
-            Artifact mavenArtifact = (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
-                                                                org.eclipse.aether.artifact.Artifact.class, artifact );
+            Artifact mavenArtifact =
+                (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                           org.eclipse.aether.artifact.Artifact.class, artifact, exceptionHandler );
 
             mavenArtifact.setScope( dep.getScope() );
             mavenArtifact.setOptional( dep.isOptional() );
 
             return mavenArtifact;
         }
-        catch ( DependencyGraphBuilderException e )
+        catch ( DependencyCollectorBuilderException e )
         {
             // ReflectionException should not happen
             throw new RuntimeException( e.getMessage(), e );
@@ -249,10 +262,24 @@ public class Maven31DependencyCollectorBuilder
         {
             optional = node.getDependency().isOptional();
         }
+        
+        List<org.apache.maven.model.Exclusion> exclusions = null;
+        if ( node.getDependency() != null )
+        {
+            exclusions = new ArrayList<>( node.getDependency().getExclusions().size() );
+            for ( Exclusion exclusion : node.getDependency().getExclusions() )
+            {
+                org.apache.maven.model.Exclusion modelExclusion = new org.apache.maven.model.Exclusion();
+                modelExclusion.setGroupId( exclusion.getGroupId() );
+                modelExclusion.setArtifactId( exclusion.getArtifactId() );
+                exclusions.add( modelExclusion );
+            }
+        }
 
         DefaultDependencyNode current =
             new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
-                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional,
+                                       exclusions );
 
         List<DependencyNode> nodes = new ArrayList<DependencyNode>( node.getChildren().size() );
         for ( org.eclipse.aether.graph.DependencyNode child : node.getChildren() )
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyGraphBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyGraphBuilder.java
index 5a53dcb..c1f9ab4 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyGraphBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyGraphBuilder.java
@@ -62,6 +62,20 @@ public class Maven31DependencyGraphBuilder
     @Requirement
     private ProjectDependenciesResolver resolver;
 
+    private final ExceptionHandler<DependencyGraphBuilderException> exceptionHandler;
+    
+    public Maven31DependencyGraphBuilder()
+    {
+        this.exceptionHandler = new ExceptionHandler<DependencyGraphBuilderException>()
+        {
+            @Override
+            public DependencyGraphBuilderException create( String message, Exception exception )
+            {
+                return new DependencyGraphBuilderException( message, exception );
+            }
+        };
+    }
+
     /**
      * Builds the dependency graph for Maven 3.1+.
      *
@@ -95,7 +109,7 @@ public class Maven31DependencyGraphBuilder
         MavenProject project = buildingRequest.getProject();
 
         RepositorySystemSession session =
-            (RepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
+            (RepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession", exceptionHandler );
 
         /*
          * if ( Boolean.TRUE != ( (Boolean) session.getConfigProperties().get(
@@ -111,7 +125,7 @@ public class Maven31DependencyGraphBuilder
         final DependencyResolutionResult result = resolveDependencies( request, reactorProjects );
         org.eclipse.aether.graph.DependencyNode graph =
             (org.eclipse.aether.graph.DependencyNode) Invoker.invoke( DependencyResolutionResult.class, result,
-                                                                      "getDependencyGraph" );
+                                                                      "getDependencyGraph", exceptionHandler );
 
         return buildDependencyNode( null, graph, project.getArtifact(), filter );
     }
@@ -189,7 +203,7 @@ public class Maven31DependencyGraphBuilder
         try
         {
             Artifact mavenArtifact = (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
-                                              org.eclipse.aether.artifact.Artifact.class, artifact );
+                                              org.eclipse.aether.artifact.Artifact.class, artifact, exceptionHandler );
 
             mavenArtifact.setScope( dep.getScope() );
             mavenArtifact.setOptional( dep.isOptional() );
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
index e27f944..24c0d65 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
@@ -31,7 +31,7 @@ import org.apache.maven.model.Dependency;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
 import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver;
 import org.apache.maven.shared.dependency.graph.internal.maven30.JavaScopeDeriver;
@@ -51,6 +51,7 @@ import org.sonatype.aether.collection.DependencyCollectionException;
 import org.sonatype.aether.collection.DependencyGraphTransformer;
 import org.sonatype.aether.collection.DependencySelector;
 import org.sonatype.aether.graph.DependencyVisitor;
+import org.sonatype.aether.graph.Exclusion;
 import org.sonatype.aether.util.DefaultRepositorySystemSession;
 import org.sonatype.aether.util.artifact.JavaScopes;
 import org.sonatype.aether.util.graph.TreeDependencyVisitor;
@@ -63,7 +64,7 @@ import org.sonatype.aether.version.VersionConstraint;
  * Project dependency raw dependency collector API, abstracting Maven 3's Aether implementation.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 @Component( role = DependencyCollectorBuilder.class, hint = "maven3" )
 public class Maven3DependencyCollectorBuilder
@@ -72,11 +73,25 @@ public class Maven3DependencyCollectorBuilder
 {
     @Requirement
     private RepositorySystem repositorySystem;
+    
+    private final ExceptionHandler<DependencyCollectorBuilderException> exceptionHandler;
+    
+    public Maven3DependencyCollectorBuilder()
+    {
+        this.exceptionHandler = new ExceptionHandler<DependencyCollectorBuilderException>()
+        {
+            @Override
+            public DependencyCollectorBuilderException create( String message, Exception exception )
+            {
+                return new DependencyCollectorBuilderException( message, exception );
+            }
+        };
+    }
 
     @Override
     public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
                                                 ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         try
         {
@@ -108,13 +123,15 @@ public class Maven3DependencyCollectorBuilder
 
             org.sonatype.aether.artifact.Artifact aetherArtifact =
                 (org.sonatype.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
-                                                                       Artifact.class, projectArtifact );
+                                                                        Artifact.class, projectArtifact,
+                                                                        exceptionHandler );
 
             @SuppressWarnings( "unchecked" )
             List<org.sonatype.aether.repository.RemoteRepository> aetherRepos =
                 (List<org.sonatype.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class,
                                                                                         "toRepos", List.class,
-                                                                                        remoteArtifactRepositories );
+                                                                                        remoteArtifactRepositories,
+                                                                                        exceptionHandler );
 
             CollectRequest collectRequest = new CollectRequest();
             collectRequest.setRoot( new org.sonatype.aether.graph.Dependency( aetherArtifact, "" ) );
@@ -137,7 +154,7 @@ public class Maven3DependencyCollectorBuilder
         }
         catch ( DependencyCollectionException e )
         {
-            throw new DependencyGraphBuilderException( "Could not collect dependencies: " + e.getResult(), e );
+            throw new DependencyCollectorBuilderException( "Could not collect dependencies: " + e.getResult(), e );
         }
     }
 
@@ -181,7 +198,7 @@ public class Maven3DependencyCollectorBuilder
 
     private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
                                                ArtifactTypeRegistry stereotypes )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         if ( project.getDependencyManagement() != null )
         {
@@ -195,7 +212,7 @@ public class Maven3DependencyCollectorBuilder
 
     private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
                                         org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         for ( Dependency dependency : project.getDependencies() )
         {
@@ -207,13 +224,13 @@ public class Maven3DependencyCollectorBuilder
     // CHECKSTYLE_OFF: LineLength
     private org.sonatype.aether.graph.Dependency toAetherDependency( org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes,
                                                                     Dependency dependency )
-        throws DependencyGraphBuilderException
+        throws DependencyCollectorBuilderException
     {
         org.sonatype.aether.graph.Dependency aetherDep =
             (org.sonatype.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
                                                                   Dependency.class,
                                                                    org.sonatype.aether.artifact.ArtifactTypeRegistry.class,
-                                                                  dependency, stereotypes );
+                                                                  dependency, stereotypes, exceptionHandler );
         return aetherDep;
     }
     // CHECKSTYLE_ON: LineLength
@@ -224,15 +241,16 @@ public class Maven3DependencyCollectorBuilder
 
         try
         {
-            Artifact mavenArtifact = (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
-                                                                org.sonatype.aether.artifact.Artifact.class, artifact );
+            Artifact mavenArtifact =
+                (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                           org.sonatype.aether.artifact.Artifact.class, artifact, exceptionHandler );
 
             mavenArtifact.setScope( dep.getScope() );
             mavenArtifact.setOptional( dep.isOptional() );
 
             return mavenArtifact;
         }
-        catch ( DependencyGraphBuilderException e )
+        catch ( DependencyCollectorBuilderException e )
         {
             // ReflectionException should not happen
             throw new RuntimeException( e.getMessage(), e );
@@ -251,9 +269,23 @@ public class Maven3DependencyCollectorBuilder
             optional = node.getDependency().isOptional();
         }
 
+        List<org.apache.maven.model.Exclusion> exclusions = null;
+        if ( node.getDependency() != null )
+        {
+            exclusions = new ArrayList<>( node.getDependency().getExclusions().size() );
+            for ( Exclusion exclusion : node.getDependency().getExclusions() )
+            {
+                org.apache.maven.model.Exclusion modelExclusion = new org.apache.maven.model.Exclusion();
+                modelExclusion.setGroupId( exclusion.getGroupId() );
+                modelExclusion.setArtifactId( exclusion.getArtifactId() );
+                exclusions.add( modelExclusion );
+            }
+        }
+
         DefaultDependencyNode current =
             new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
-                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional,
+                                       exclusions );
 
         List<DependencyNode> nodes = new ArrayList<DependencyNode>( node.getChildren().size() );
         for ( org.sonatype.aether.graph.DependencyNode child : node.getChildren() )
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyGraphBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyGraphBuilder.java
index 2a2e58b..39e2a35 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyGraphBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyGraphBuilder.java
@@ -130,7 +130,7 @@ public class Maven3DependencyGraphBuilder
         List<Dependency> reactorDeps = getReactorDependencies( reactorProjects, result.getUnresolvedDependencies() );
 
         result.getUnresolvedDependencies().removeAll( reactorDeps );
-        Invoker.invoke( result.getResolvedDependencies(), "addAll", Collection.class, reactorDeps );
+        result.getResolvedDependencies().addAll( reactorDeps );
 
         if ( !result.getUnresolvedDependencies().isEmpty() )
         {
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java
index 44cbbe7..d686842 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java
@@ -40,7 +40,7 @@ import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public final class ConflictIdSorter
     implements DependencyGraphTransformer
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java
index 1e60a32..abbb36e 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java
@@ -45,7 +45,7 @@ import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public final class ConflictResolver
     implements DependencyGraphTransformer
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java
index 36f2bd7..e9555f9 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java
@@ -34,7 +34,7 @@ import org.sonatype.aether.graph.Exclusion;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public class ExclusionDependencySelector
     implements DependencySelector
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java
index 73367bf..d35abf0 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java
@@ -28,7 +28,7 @@ import org.sonatype.aether.util.artifact.JavaScopes;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public class JavaScopeDeriver
     extends ScopeDeriver
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java
index 6eeea83..9a810e7 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java
@@ -33,7 +33,7 @@ import org.sonatype.aether.util.artifact.JavaScopes;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public final class JavaScopeSelector
     extends ScopeSelector
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java
index e66b80b..4776e6d 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java
@@ -29,7 +29,7 @@ import org.sonatype.aether.graph.Dependency;
  * 
  * @see {@link Dependency#getScope()}
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public class Maven3DirectScopeDependencySelector
     implements DependencySelector
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java
index 8d2bc93..c793b96 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java
@@ -40,7 +40,7 @@ import org.sonatype.aether.version.VersionConstraint;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public final class NearestVersionSelector
     extends VersionSelector
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java
index ffd6a63..d63df0b 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java
@@ -30,7 +30,7 @@ import org.sonatype.aether.RepositoryException;
  * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
  * 
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public class SimpleOptionalitySelector
     extends OptionalitySelector
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java
index dbc71c5..95ee25c 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java
@@ -29,7 +29,7 @@ import org.eclipse.aether.graph.Dependency;
  * 
  * @see {@link Dependency#getScope()}
  * @author Gabriel Belingueres
- * @since 3.0.2
+ * @since 3.1.0
  */
 public class Maven31DirectScopeDependencySelector
     implements DependencySelector

[maven-dependency-tree] 02/04: Fixed to also return dependencies with scope "provided".

Posted by rf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a commit to branch MSHARED-788
in repository https://gitbox.apache.org/repos/asf/maven-dependency-tree.git

commit 7f663c54bbfbb3bb014a517ead3e5b550dc48b5f
Author: Gabriel Belingueres <be...@gmail.com>
AuthorDate: Thu Dec 27 00:02:51 2018 -0300

    Fixed to also return dependencies with scope "provided".
---
 .../Maven31DependencyCollectorBuilder.java         | 33 +++++++++++-----------
 .../internal/Maven3DependencyCollectorBuilder.java | 12 ++++++--
 2 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
index f4fd955..7152ab7 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
@@ -55,7 +55,6 @@ import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
 import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
 import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
 import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
-import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor;
 import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
 import org.eclipse.aether.version.VersionConstraint;
 
@@ -75,30 +74,35 @@ public class Maven31DependencyCollectorBuilder
 
     @Override
     public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
-                                                ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
+                                                  ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
         throws DependencyGraphBuilderException
     {
         DefaultRepositorySystemSession session = null;
         try
         {
             MavenProject project = buildingRequest.getProject();
+
             Artifact projectArtifact = project.getArtifact();
             List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
 
             DefaultRepositorySystemSession repositorySession =
                 (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
-            
+
             session = new DefaultRepositorySystemSession( repositorySession );
 
             DependencyGraphTransformer transformer =
                 new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
                                       new SimpleOptionalitySelector(), new JavaScopeDeriver() );
-
             session.setDependencyGraphTransformer( transformer );
 
+            DependencySelector depFilter =
+                new AndDependencySelector( new ScopeDependencySelector(), new OptionalDependencySelector(),
+                                           new ExclusionDependencySelector() );
+            session.setDependencySelector( depFilter );
+
             session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
             session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, true );
-            
+
             org.eclipse.aether.artifact.Artifact aetherArtifact =
                 (org.eclipse.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
                                                                        Artifact.class, projectArtifact );
@@ -113,11 +117,6 @@ public class Maven31DependencyCollectorBuilder
             collectRequest.setRoot( new org.eclipse.aether.graph.Dependency( aetherArtifact, "" ) );
             collectRequest.setRepositories( aetherRepos );
 
-            DependencySelector depFilter =
-                new AndDependencySelector( new ScopeDependencySelector( "provided" ), new OptionalDependencySelector(),
-                                           new ExclusionDependencySelector() );
-            session.setDependencySelector( depFilter );
-            
             org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
             collectDependencyList( collectRequest, project, stereotypes );
             collectManagedDependencyList( collectRequest, project, stereotypes );
@@ -126,11 +125,11 @@ public class Maven31DependencyCollectorBuilder
 
             org.eclipse.aether.graph.DependencyNode rootNode = collectResult.getRoot();
 
-            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
-            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
-            rootNode.accept( treeVisitor );
-
-            rootNode = cloner.getRootNode();
+//            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
+//            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
+//            rootNode.accept( treeVisitor );
+//
+//            rootNode = cloner.getRootNode();
 
             if ( getLogger().isDebugEnabled() )
             {
@@ -192,7 +191,7 @@ public class Maven31DependencyCollectorBuilder
     }
 
     private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
-                                         org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes )
+                                        org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes )
         throws DependencyGraphBuilderException
     {
         for ( Dependency dependency : project.getDependencies() )
@@ -242,7 +241,7 @@ public class Maven31DependencyCollectorBuilder
     {
         String premanagedVersion = DependencyManagerUtils.getPremanagedVersion( node );
         String premanagedScope = DependencyManagerUtils.getPremanagedScope( node );
-        
+
         Boolean optional = null;
         if ( node.getDependency() != null )
         {
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
index d6b6d49..d88b884 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
@@ -42,6 +42,7 @@ import org.sonatype.aether.artifact.ArtifactTypeRegistry;
 import org.sonatype.aether.collection.CollectRequest;
 import org.sonatype.aether.collection.CollectResult;
 import org.sonatype.aether.collection.DependencyCollectionException;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
 import org.sonatype.aether.collection.DependencySelector;
 import org.sonatype.aether.graph.DependencyVisitor;
 import org.sonatype.aether.util.DefaultRepositorySystemSession;
@@ -51,6 +52,10 @@ import org.sonatype.aether.util.graph.selector.AndDependencySelector;
 import org.sonatype.aether.util.graph.selector.ExclusionDependencySelector;
 import org.sonatype.aether.util.graph.selector.OptionalDependencySelector;
 import org.sonatype.aether.util.graph.selector.ScopeDependencySelector;
+import org.sonatype.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
+import org.sonatype.aether.util.graph.transformer.ConflictMarker;
+import org.sonatype.aether.util.graph.transformer.JavaDependencyContextRefiner;
+import org.sonatype.aether.util.graph.transformer.JavaEffectiveScopeCalculator;
 import org.sonatype.aether.version.VersionConstraint;
 
 /**
@@ -87,10 +92,13 @@ public class Maven3DependencyCollectorBuilder
 
             DefaultRepositorySystemSession session = new DefaultRepositorySystemSession( repositorySystemSession );
 
-            session.setDependencyGraphTransformer( null );
+            DependencyGraphTransformer transformer =
+                new ChainedDependencyGraphTransformer( new ConflictMarker(), new JavaEffectiveScopeCalculator(),
+                                                       new JavaDependencyContextRefiner() );
+            session.setDependencyGraphTransformer( transformer );
 
             DependencySelector depFilter =
-                new AndDependencySelector( new ScopeDependencySelector( "provided" ), new OptionalDependencySelector(),
+                new AndDependencySelector( new ScopeDependencySelector(), new OptionalDependencySelector(),
                                            new ExclusionDependencySelector() );
             session.setDependencySelector( depFilter );
 

[maven-dependency-tree] 03/04: Corrected some problems discovered with getting the right level of filtering of the dependencies.

Posted by rf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a commit to branch MSHARED-788
in repository https://gitbox.apache.org/repos/asf/maven-dependency-tree.git

commit 758de2bd07ae4d66c92d57cda2c08dd796a53a04
Author: Gabriel Belingueres <be...@gmail.com>
AuthorDate: Tue Jan 15 22:40:06 2019 -0300

    Corrected some problems discovered with getting the right level of filtering of the dependencies.
    
    Specially for Maven 3.0, I couldn't get the desired behavior using the
    provided DependencyGraphTransformer, DependencySelector, etc. so I
    copied those implementation from Eclipse aether to work on Sonatype
    aether classes. As a result it is expected to get the same output than
    Maven 3.1+ (though it will probably need more testing (just tested with
    enforcer plugin's DependencyConvergence and RequireUpperBoundDeps ITs).
---
 .../Maven31DependencyCollectorBuilder.java         |    6 +-
 .../internal/Maven3DependencyCollectorBuilder.java |   34 +-
 .../graph/internal/maven30/ConflictIdSorter.java   |  370 ++++++
 .../graph/internal/maven30/ConflictResolver.java   | 1318 ++++++++++++++++++++
 .../maven30/ExclusionDependencySelector.java       |  228 ++++
 .../graph/internal/maven30/JavaScopeDeriver.java   |   72 ++
 .../graph/internal/maven30/JavaScopeSelector.java  |  102 ++
 .../Maven3DirectScopeDependencySelector.java       |  132 ++
 .../internal/maven30/NearestVersionSelector.java   |  183 +++
 .../maven30/SimpleOptionalitySelector.java         |   64 +
 .../Maven31DirectScopeDependencySelector.java      |  132 ++
 11 files changed, 2619 insertions(+), 22 deletions(-)

diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
index 7152ab7..62e659c 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
@@ -33,6 +33,7 @@ import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.internal.maven31.Maven31DirectScopeDependencySelector;
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.component.annotations.Requirement;
 import org.codehaus.plexus.logging.AbstractLogEnabled;
@@ -45,11 +46,11 @@ import org.eclipse.aether.collection.DependencyCollectionException;
 import org.eclipse.aether.collection.DependencyGraphTransformer;
 import org.eclipse.aether.collection.DependencySelector;
 import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.util.artifact.JavaScopes;
 import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
 import org.eclipse.aether.util.graph.selector.AndDependencySelector;
 import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
 import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
-import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
 import org.eclipse.aether.util.graph.transformer.ConflictResolver;
 import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
 import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
@@ -96,7 +97,8 @@ public class Maven31DependencyCollectorBuilder
             session.setDependencyGraphTransformer( transformer );
 
             DependencySelector depFilter =
-                new AndDependencySelector( new ScopeDependencySelector(), new OptionalDependencySelector(),
+                new AndDependencySelector( new Maven31DirectScopeDependencySelector( JavaScopes.TEST ),
+                                           new OptionalDependencySelector(),
                                            new ExclusionDependencySelector() );
             session.setDependencySelector( depFilter );
 
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
index d88b884..e27f944 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
@@ -33,6 +33,12 @@ import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver;
+import org.apache.maven.shared.dependency.graph.internal.maven30.JavaScopeDeriver;
+import org.apache.maven.shared.dependency.graph.internal.maven30.JavaScopeSelector;
+import org.apache.maven.shared.dependency.graph.internal.maven30.Maven3DirectScopeDependencySelector;
+import org.apache.maven.shared.dependency.graph.internal.maven30.NearestVersionSelector;
+import org.apache.maven.shared.dependency.graph.internal.maven30.SimpleOptionalitySelector;
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.component.annotations.Requirement;
 import org.codehaus.plexus.logging.AbstractLogEnabled;
@@ -46,16 +52,11 @@ import org.sonatype.aether.collection.DependencyGraphTransformer;
 import org.sonatype.aether.collection.DependencySelector;
 import org.sonatype.aether.graph.DependencyVisitor;
 import org.sonatype.aether.util.DefaultRepositorySystemSession;
-import org.sonatype.aether.util.graph.CloningDependencyVisitor;
+import org.sonatype.aether.util.artifact.JavaScopes;
 import org.sonatype.aether.util.graph.TreeDependencyVisitor;
 import org.sonatype.aether.util.graph.selector.AndDependencySelector;
 import org.sonatype.aether.util.graph.selector.ExclusionDependencySelector;
 import org.sonatype.aether.util.graph.selector.OptionalDependencySelector;
-import org.sonatype.aether.util.graph.selector.ScopeDependencySelector;
-import org.sonatype.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
-import org.sonatype.aether.util.graph.transformer.ConflictMarker;
-import org.sonatype.aether.util.graph.transformer.JavaDependencyContextRefiner;
-import org.sonatype.aether.util.graph.transformer.JavaEffectiveScopeCalculator;
 import org.sonatype.aether.version.VersionConstraint;
 
 /**
@@ -77,7 +78,6 @@ public class Maven3DependencyCollectorBuilder
                                                 ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
         throws DependencyGraphBuilderException
     {
-        ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
         try
         {
             MavenProject project = buildingRequest.getProject();
@@ -93,15 +93,19 @@ public class Maven3DependencyCollectorBuilder
             DefaultRepositorySystemSession session = new DefaultRepositorySystemSession( repositorySystemSession );
 
             DependencyGraphTransformer transformer =
-                new ChainedDependencyGraphTransformer( new ConflictMarker(), new JavaEffectiveScopeCalculator(),
-                                                       new JavaDependencyContextRefiner() );
+                new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
+                                      new SimpleOptionalitySelector(), new JavaScopeDeriver() );
             session.setDependencyGraphTransformer( transformer );
 
             DependencySelector depFilter =
-                new AndDependencySelector( new ScopeDependencySelector(), new OptionalDependencySelector(),
+                new AndDependencySelector( new Maven3DirectScopeDependencySelector( JavaScopes.TEST ),
+                                           new OptionalDependencySelector(), 
                                            new ExclusionDependencySelector() );
             session.setDependencySelector( depFilter );
 
+            session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
+            session.setConfigProperty( "aether.dependencyManager.verbose", true );
+
             org.sonatype.aether.artifact.Artifact aetherArtifact =
                 (org.sonatype.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
                                                                        Artifact.class, projectArtifact );
@@ -124,12 +128,6 @@ public class Maven3DependencyCollectorBuilder
 
             org.sonatype.aether.graph.DependencyNode rootNode = collectResult.getRoot();
 
-            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
-            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
-            rootNode.accept( treeVisitor );
-
-            rootNode = cloner.getRootNode();
-
             if ( getLogger().isDebugEnabled() )
             {
                 logTree( rootNode );
@@ -141,10 +139,6 @@ public class Maven3DependencyCollectorBuilder
         {
             throw new DependencyGraphBuilderException( "Could not collect dependencies: " + e.getResult(), e );
         }
-        finally
-        {
-            Thread.currentThread().setContextClassLoader( prevClassLoader );
-        }
     }
 
     private void logTree( org.sonatype.aether.graph.DependencyNode rootNode )
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java
new file mode 100644
index 0000000..44cbbe7
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictIdSorter.java
@@ -0,0 +1,370 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.collection.DependencyGraphTransformationContext;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.util.graph.transformer.ConflictMarker;
+import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public final class ConflictIdSorter
+    implements DependencyGraphTransformer
+{
+
+    public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context )
+        throws RepositoryException
+    {
+        Map<?, ?> conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+        if ( conflictIds == null )
+        {
+            ConflictMarker marker = new ConflictMarker();
+            marker.transformGraph( node, context );
+
+            conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+        }
+
+//        @SuppressWarnings( "unchecked" )
+//        Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS );
+//        long time1 = System.currentTimeMillis();
+
+        Map<Object, ConflictId> ids = new LinkedHashMap<Object, ConflictId>( 256 );
+
+        // CHECKSTYLE_OFF: AvoidNestedBlocks
+        {
+            ConflictId id = null;
+            Object key = conflictIds.get( node );
+            if ( key != null )
+            {
+                id = new ConflictId( key, 0 );
+                ids.put( key, id );
+            }
+
+            Map<DependencyNode, Object> visited = new IdentityHashMap<DependencyNode, Object>( conflictIds.size() );
+
+            buildConflitIdDAG( ids, node, id, 0, visited, conflictIds );
+        }
+        // CHECKSTYLE_NO: AvoidNestedBlocks
+
+//        long time2 = System.currentTimeMillis();
+
+        topsortConflictIds( ids.values(), context );
+//        int cycles = topsortConflictIds( ids.values(), context );
+
+//        if ( stats != null )
+//        {
+//            long time3 = System.currentTimeMillis();
+//            stats.put( "ConflictIdSorter.graphTime", time2 - time1 );
+//            stats.put( "ConflictIdSorter.topsortTime", time3 - time2 );
+//            stats.put( "ConflictIdSorter.conflictIdCount", ids.size() );
+//            stats.put( "ConflictIdSorter.conflictIdCycleCount", cycles );
+//        }
+
+        return node;
+    }
+
+    private void buildConflitIdDAG( Map<Object, ConflictId> ids, DependencyNode node, ConflictId id, int depth,
+                                    Map<DependencyNode, Object> visited, Map<?, ?> conflictIds )
+    {
+        if ( visited.put( node, Boolean.TRUE ) != null )
+        {
+            return;
+        }
+
+        depth++;
+
+        for ( DependencyNode child : node.getChildren() )
+        {
+            Object key = conflictIds.get( child );
+            ConflictId childId = ids.get( key );
+            if ( childId == null )
+            {
+                childId = new ConflictId( key, depth );
+                ids.put( key, childId );
+            }
+            else
+            {
+                childId.pullup( depth );
+            }
+
+            if ( id != null )
+            {
+                id.add( childId );
+            }
+
+            buildConflitIdDAG( ids, child, childId, depth, visited, conflictIds );
+        }
+    }
+
+    private int topsortConflictIds( Collection<ConflictId> conflictIds, DependencyGraphTransformationContext context )
+    {
+        List<Object> sorted = new ArrayList<Object>( conflictIds.size() );
+
+        RootQueue roots = new RootQueue( conflictIds.size() / 2 );
+        for ( ConflictId id : conflictIds )
+        {
+            if ( id.inDegree <= 0 )
+            {
+                roots.add( id );
+            }
+        }
+
+        processRoots( sorted, roots );
+
+        boolean cycle = sorted.size() < conflictIds.size();
+
+        while ( sorted.size() < conflictIds.size() )
+        {
+            // cycle -> deal gracefully with nodes still having positive in-degree
+
+            ConflictId nearest = null;
+            for ( ConflictId id : conflictIds )
+            {
+                if ( id.inDegree <= 0 )
+                {
+                    continue;
+                }
+                if ( nearest == null || id.minDepth < nearest.minDepth
+                    || ( id.minDepth == nearest.minDepth && id.inDegree < nearest.inDegree ) )
+                {
+                    nearest = id;
+                }
+            }
+
+            nearest.inDegree = 0;
+            roots.add( nearest );
+
+            processRoots( sorted, roots );
+        }
+
+        Collection<Collection<Object>> cycles = Collections.emptySet();
+        if ( cycle )
+        {
+            cycles = findCycles( conflictIds );
+        }
+
+        context.put( TransformationContextKeys.SORTED_CONFLICT_IDS, sorted );
+        context.put( TransformationContextKeys.CYCLIC_CONFLICT_IDS, cycles );
+
+        return cycles.size();
+    }
+
+    private void processRoots( List<Object> sorted, RootQueue roots )
+    {
+        while ( !roots.isEmpty() )
+        {
+            ConflictId root = roots.remove();
+
+            sorted.add( root.key );
+
+            for ( ConflictId child : root.children )
+            {
+                child.inDegree--;
+                if ( child.inDegree == 0 )
+                {
+                    roots.add( child );
+                }
+            }
+        }
+    }
+
+    private Collection<Collection<Object>> findCycles( Collection<ConflictId> conflictIds )
+    {
+        Collection<Collection<Object>> cycles = new HashSet<Collection<Object>>();
+
+        Map<Object, Integer> stack = new HashMap<Object, Integer>( 128 );
+        Map<ConflictId, Object> visited = new IdentityHashMap<ConflictId, Object>( conflictIds.size() );
+        for ( ConflictId id : conflictIds )
+        {
+            findCycles( id, visited, stack, cycles );
+        }
+
+        return cycles;
+    }
+
+    private void findCycles( ConflictId id, Map<ConflictId, Object> visited, Map<Object, Integer> stack,
+                             Collection<Collection<Object>> cycles )
+    {
+        Integer depth = stack.put( id.key, stack.size() );
+        if ( depth != null )
+        {
+            stack.put( id.key, depth );
+            Collection<Object> cycle = new HashSet<Object>();
+            for ( Map.Entry<Object, Integer> entry : stack.entrySet() )
+            {
+                if ( entry.getValue() >= depth )
+                {
+                    cycle.add( entry.getKey() );
+                }
+            }
+            cycles.add( cycle );
+        }
+        else
+        {
+            if ( visited.put( id, Boolean.TRUE ) == null )
+            {
+                for ( ConflictId childId : id.children )
+                {
+                    findCycles( childId, visited, stack, cycles );
+                }
+            }
+            stack.remove( id.key );
+        }
+    }
+
+    static final class ConflictId
+    {
+
+        final Object key;
+
+        Collection<ConflictId> children = Collections.emptySet();
+
+        int inDegree;
+
+        int minDepth;
+
+        ConflictId( Object key, int depth )
+        {
+            this.key = key;
+            this.minDepth = depth;
+        }
+
+        public void add( ConflictId child )
+        {
+            if ( children.isEmpty() )
+            {
+                children = new HashSet<ConflictId>();
+            }
+            if ( children.add( child ) )
+            {
+                child.inDegree++;
+            }
+        }
+
+        public void pullup( int depth )
+        {
+            if ( depth < minDepth )
+            {
+                minDepth = depth;
+                depth++;
+                for ( ConflictId child : children )
+                {
+                    child.pullup( depth );
+                }
+            }
+        }
+
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( this == obj )
+            {
+                return true;
+            }
+            else if ( !( obj instanceof ConflictId ) )
+            {
+                return false;
+            }
+            ConflictId that = (ConflictId) obj;
+            return this.key.equals( that.key );
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return key.hashCode();
+        }
+
+        @Override
+        public String toString()
+        {
+            return key + " @ " + minDepth + " <" + inDegree;
+        }
+
+    }
+
+    static final class RootQueue
+    {
+
+        private int nextOut;
+
+        private int nextIn;
+
+        private ConflictId[] ids;
+
+        RootQueue( int capacity )
+        {
+            ids = new ConflictId[capacity + 16];
+        }
+
+        boolean isEmpty()
+        {
+            return nextOut >= nextIn;
+        }
+
+        void add( ConflictId id )
+        {
+            if ( nextOut >= nextIn && nextOut > 0 )
+            {
+                nextIn -= nextOut;
+                nextOut = 0;
+            }
+            if ( nextIn >= ids.length )
+            {
+                ConflictId[] tmp = new ConflictId[ids.length + ids.length / 2 + 16];
+                System.arraycopy( ids, nextOut, tmp, 0, nextIn - nextOut );
+                ids = tmp;
+                nextIn -= nextOut;
+                nextOut = 0;
+            }
+            int i;
+            for ( i = nextIn - 1; i >= nextOut && id.minDepth < ids[i].minDepth; i-- )
+            {
+                ids[i + 1] = ids[i];
+            }
+            ids[i + 1] = id;
+            nextIn++;
+        }
+
+        ConflictId remove()
+        {
+            return ids[nextOut++];
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java
new file mode 100644
index 0000000..1e60a32
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ConflictResolver.java
@@ -0,0 +1,1318 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.collection.DependencyGraphTransformationContext;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
+import org.sonatype.aether.graph.Dependency;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.util.ConfigUtils;
+import org.sonatype.aether.util.graph.DefaultDependencyNode;
+import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public final class ConflictResolver
+    implements DependencyGraphTransformer
+{
+
+    /**
+     * The key in the repository session's {@link RepositorySystemSession#getConfigProperties() configuration
+     * properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode.
+     */
+    public static final String CONFIG_PROP_VERBOSE = "aether.conflictResolver.verbose";
+
+    /**
+     * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the
+     * {@link DependencyNode} which has won the conflict is stored.
+     */
+    public static final String NODE_DATA_WINNER = "conflict.winner";
+
+    /**
+     * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the
+     * dependency before scope derivation and conflict resolution is stored.
+     */
+    public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope";
+
+    /**
+     * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of
+     * the dependency before derivation and conflict resolution is stored.
+     */
+    public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality";
+
+    private final VersionSelector versionSelector;
+
+    private final ScopeSelector scopeSelector;
+
+    private final ScopeDeriver scopeDeriver;
+
+    private final OptionalitySelector optionalitySelector;
+
+    /**
+     * Creates a new conflict resolver instance with the specified hooks.
+     * 
+     * @param versionSelector The version selector to use, must not be {@code null}.
+     * @param scopeSelector The scope selector to use, must not be {@code null}.
+     * @param optionalitySelector The optionality selector ot use, must not be {@code null}.
+     * @param scopeDeriver The scope deriver to use, must not be {@code null}.
+     */
+    public ConflictResolver( VersionSelector versionSelector, ScopeSelector scopeSelector,
+                             OptionalitySelector optionalitySelector, ScopeDeriver scopeDeriver )
+    {
+        if ( versionSelector == null )
+        {
+            throw new IllegalArgumentException( "version selector not specified" );
+        }
+        this.versionSelector = versionSelector;
+        if ( scopeSelector == null )
+        {
+            throw new IllegalArgumentException( "scope selector not specified" );
+        }
+        this.scopeSelector = scopeSelector;
+        if ( scopeDeriver == null )
+        {
+            throw new IllegalArgumentException( "scope deriver not specified" );
+        }
+        this.scopeDeriver = scopeDeriver;
+        if ( optionalitySelector == null )
+        {
+            throw new IllegalArgumentException( "optionality selector not specified" );
+        }
+        this.optionalitySelector = optionalitySelector;
+    }
+
+    public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context )
+        throws RepositoryException
+    {
+        List<?> sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS );
+        if ( sortedConflictIds == null )
+        {
+            ConflictIdSorter sorter = new ConflictIdSorter();
+            sorter.transformGraph( node, context );
+
+            sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS );
+        }
+
+//        @SuppressWarnings( "unchecked" )
+//        Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS );
+//        long time1 = System.currentTimeMillis();
+
+        @SuppressWarnings( "unchecked" )
+        Collection<Collection<?>> conflictIdCycles =
+            (Collection<Collection<?>>) context.get( TransformationContextKeys.CYCLIC_CONFLICT_IDS );
+        if ( conflictIdCycles == null )
+        {
+            throw new RepositoryException( "conflict id cycles have not been identified" );
+        }
+
+        Map<?, ?> conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+        if ( conflictIds == null )
+        {
+            throw new RepositoryException( "conflict groups have not been identified" );
+        }
+
+        Map<Object, Collection<Object>> cyclicPredecessors = new HashMap<Object, Collection<Object>>();
+        for ( Collection<?> cycle : conflictIdCycles )
+        {
+            for ( Object conflictId : cycle )
+            {
+                Collection<Object> predecessors = cyclicPredecessors.get( conflictId );
+                if ( predecessors == null )
+                {
+                    predecessors = new HashSet<Object>();
+                    cyclicPredecessors.put( conflictId, predecessors );
+                }
+                predecessors.addAll( cycle );
+            }
+        }
+
+        State state = new State( node, conflictIds, sortedConflictIds.size(), context );
+        for ( Iterator<?> it = sortedConflictIds.iterator(); it.hasNext(); )
+        {
+            Object conflictId = it.next();
+
+            // reset data structures for next graph walk
+            state.prepare( conflictId, cyclicPredecessors.get( conflictId ) );
+
+            // find nodes with the current conflict id and while walking the graph (more deeply), nuke leftover losers
+            gatherConflictItems( node, state );
+
+            // now that we know the min depth of the parents, update depth of conflict items
+            state.finish();
+
+            // earlier runs might have nuked all parents of the current conflict id, so it might not exist anymore
+            if ( !state.items.isEmpty() )
+            {
+                ConflictContext ctx = state.conflictCtx;
+                state.versionSelector.selectVersion( ctx );
+                if ( ctx.winner == null )
+                {
+                    throw new RepositoryException( "conflict resolver did not select winner among " + state.items );
+                }
+                DependencyNode winner = ctx.winner.node;
+
+                state.scopeSelector.selectScope( ctx );
+                if ( state.verbose )
+                {
+                    winner.setData( NODE_DATA_ORIGINAL_SCOPE, winner.getDependency().getScope() );
+                }
+                winner.setScope( ctx.scope );
+
+                state.optionalitySelector.selectOptionality( ctx );
+                if ( state.verbose )
+                {
+                    winner.setData( NODE_DATA_ORIGINAL_OPTIONALITY, winner.getDependency().isOptional() );
+                }
+                winner.getDependency().setOptional( ctx.optional );
+//                winner.setOptional( ctx.optional );
+
+                removeLosers( state );
+            }
+
+            // record the winner so we can detect leftover losers during future graph walks
+            state.winner();
+
+            // in case of cycles, trigger final graph walk to ensure all leftover losers are gone
+            if ( !it.hasNext() && !conflictIdCycles.isEmpty() && state.conflictCtx.winner != null )
+            {
+                DependencyNode winner = state.conflictCtx.winner.node;
+                state.prepare( state, null );
+                gatherConflictItems( winner, state );
+            }
+        }
+
+//        if ( stats != null )
+//        {
+//            long time2 = System.currentTimeMillis();
+//            stats.put( "ConflictResolver.totalTime", time2 - time1 );
+//            stats.put( "ConflictResolver.conflictItemCount", state.totalConflictItems );
+//        }
+
+        return node;
+    }
+
+    private boolean gatherConflictItems( DependencyNode node, State state )
+        throws RepositoryException
+    {
+        Object conflictId = state.conflictIds.get( node );
+        if ( state.currentId.equals( conflictId ) )
+        {
+            // found it, add conflict item (if not already done earlier by another path)
+            state.add( node );
+            // we don't recurse here so we might miss losers beneath us, those will be nuked during future walks below
+        }
+        else if ( state.loser( node, conflictId ) )
+        {
+            // found a leftover loser (likely in a cycle) of an already processed conflict id, tell caller to nuke it
+            return false;
+        }
+        else if ( state.push( node, conflictId ) )
+        {
+            // found potential parent, no cycle and not visisted before with the same derived scope, so recurse
+            for ( Iterator<DependencyNode> it = node.getChildren().iterator(); it.hasNext(); )
+            {
+                DependencyNode child = it.next();
+                if ( !gatherConflictItems( child, state ) )
+                {
+                    it.remove();
+                }
+            }
+            state.pop();
+        }
+        return true;
+    }
+
+    private void removeLosers( State state )
+    {
+        ConflictItem winner = state.conflictCtx.winner;
+        List<DependencyNode> previousParent = null;
+        ListIterator<DependencyNode> childIt = null;
+        boolean conflictVisualized = false;
+        for ( ConflictItem item : state.items )
+        {
+            if ( item == winner )
+            {
+                continue;
+            }
+            if ( item.parent != previousParent )
+            {
+                childIt = item.parent.listIterator();
+                previousParent = item.parent;
+                conflictVisualized = false;
+            }
+            while ( childIt.hasNext() )
+            {
+                DependencyNode child = childIt.next();
+                if ( child == item.node )
+                {
+                    if ( state.verbose && !conflictVisualized && item.parent != winner.parent )
+                    {
+                        conflictVisualized = true;
+                        DependencyNode loser = new DefaultDependencyNode( child );
+                        loser.setData( NODE_DATA_WINNER, winner.node );
+                        loser.setData( NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope() );
+                        loser.setData( NODE_DATA_ORIGINAL_OPTIONALITY, loser.getDependency().isOptional() );
+                        loser.setScope( item.getScopes().iterator().next() );
+//                        loser.setChildren( Collections.<DependencyNode>emptyList() );
+                        childIt.set( loser );
+                    }
+                    else
+                    {
+                        childIt.remove();
+                    }
+                    break;
+                }
+            }
+        }
+        // there might still be losers beneath the winner (e.g. in case of cycles)
+        // those will be nuked during future graph walks when we include the winner in the recursion
+    }
+
+    final class NodeInfo
+    {
+
+        /**
+         * The smallest depth at which the node was seen, used for "the" depth of its conflict items.
+         */
+        int minDepth;
+
+        /**
+         * The set of derived scopes the node was visited with, used to check whether an already seen node needs to be
+         * revisited again in context of another scope. To conserve memory, we start with {@code String} and update to
+         * {@code Set<String>} if needed.
+         */
+        Object derivedScopes;
+
+        /**
+         * The set of derived optionalities the node was visited with, used to check whether an already seen node needs
+         * to be revisited again in context of another optionality. To conserve memory, encoded as bit field (bit 0 ->
+         * optional=false, bit 1 -> optional=true).
+         */
+        int derivedOptionalities;
+
+        /**
+         * The conflict items which are immediate children of the node, used to easily update those conflict items after
+         * a new parent scope/optionality was encountered.
+         */
+        List<ConflictItem> children;
+
+        static final int CHANGE_SCOPE = 0x01;
+
+        static final int CHANGE_OPTIONAL = 0x02;
+
+        private static final int OPT_FALSE = 0x01;
+
+        private static final int OPT_TRUE = 0x02;
+
+        NodeInfo( int depth, String derivedScope, boolean optional )
+        {
+            minDepth = depth;
+            derivedScopes = derivedScope;
+            derivedOptionalities = optional ? OPT_TRUE : OPT_FALSE;
+        }
+
+        @SuppressWarnings( "unchecked" )
+        int update( int depth, String derivedScope, boolean optional )
+        {
+            if ( depth < minDepth )
+            {
+                minDepth = depth;
+            }
+            int changes;
+            if ( derivedScopes.equals( derivedScope ) )
+            {
+                changes = 0;
+            }
+            else if ( derivedScopes instanceof Collection )
+            {
+                changes = ( (Collection<String>) derivedScopes ).add( derivedScope ) ? CHANGE_SCOPE : 0;
+            }
+            else
+            {
+                Collection<String> scopes = new HashSet<String>();
+                scopes.add( (String) derivedScopes );
+                scopes.add( derivedScope );
+                derivedScopes = scopes;
+                changes = CHANGE_SCOPE;
+            }
+            int bit = optional ? OPT_TRUE : OPT_FALSE;
+            if ( ( derivedOptionalities & bit ) == 0 )
+            {
+                derivedOptionalities |= bit;
+                changes |= CHANGE_OPTIONAL;
+            }
+            return changes;
+        }
+
+        void add( ConflictItem item )
+        {
+            if ( children == null )
+            {
+                children = new ArrayList<ConflictItem>( 1 );
+            }
+            children.add( item );
+        }
+
+    }
+
+    final class State
+    {
+
+        /**
+         * The conflict id currently processed.
+         */
+        Object currentId;
+
+        /**
+         * Stats counter.
+         */
+        int totalConflictItems;
+
+        /**
+         * Flag whether we should keep losers in the graph to enable visualization/troubleshooting of conflicts.
+         */
+        final boolean verbose;
+
+        /**
+         * A mapping from conflict id to winner node, helps to recognize nodes that have their effective
+         * scope&optionality set or are leftovers from previous removals.
+         */
+        final Map<Object, DependencyNode> resolvedIds;
+
+        /**
+         * The set of conflict ids which could apply to ancestors of nodes with the current conflict id, used to avoid
+         * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account
+         * for cyclic dependencies.
+         */
+        final Collection<Object> potentialAncestorIds;
+
+        /**
+         * The output from the conflict marker
+         */
+        final Map<?, ?> conflictIds;
+
+        /**
+         * The conflict items we have gathered so far for the current conflict id.
+         */
+        final List<ConflictItem> items;
+
+        /**
+         * The (conceptual) mapping from nodes to extra infos, technically keyed by the node's child list which better
+         * captures the identity of a node since we're basically concerned with effects towards children.
+         */
+        final Map<List<DependencyNode>, NodeInfo> infos;
+
+        /**
+         * The set of nodes on the DFS stack to detect cycles, technically keyed by the node's child list to match the
+         * dirty graph structure produced by the dependency collector for cycles.
+         */
+        final Map<List<DependencyNode>, Object> stack;
+
+        /**
+         * The stack of parent nodes.
+         */
+        final List<DependencyNode> parentNodes;
+
+        /**
+         * The stack of derived scopes for parent nodes.
+         */
+        final List<String> parentScopes;
+
+        /**
+         * The stack of derived optional flags for parent nodes.
+         */
+        final List<Boolean> parentOptionals;
+
+        /**
+         * The stack of node infos for parent nodes, may contain {@code null} which is used to disable creating new
+         * conflict items when visiting their parent again (conflict items are meant to be unique by parent-node combo).
+         */
+        final List<NodeInfo> parentInfos;
+
+        /**
+         * The conflict context passed to the version/scope/optionality selectors, updated as we move along rather than
+         * recreated to avoid tmp objects.
+         */
+        final ConflictContext conflictCtx;
+
+        /**
+         * The scope context passed to the scope deriver, updated as we move along rather than recreated to avoid tmp
+         * objects.
+         */
+        final ScopeContext scopeCtx;
+
+        /**
+         * The effective version selector, i.e. after initialization.
+         */
+        final VersionSelector versionSelector;
+
+        /**
+         * The effective scope selector, i.e. after initialization.
+         */
+        final ScopeSelector scopeSelector;
+
+        /**
+         * The effective scope deriver, i.e. after initialization.
+         */
+        final ScopeDeriver scopeDeriver;
+
+        /**
+         * The effective optionality selector, i.e. after initialization.
+         */
+        final OptionalitySelector optionalitySelector;
+
+        State( DependencyNode root, Map<?, ?> conflictIds, int conflictIdCount,
+               DependencyGraphTransformationContext context )
+            throws RepositoryException
+        {
+            this.conflictIds = conflictIds;
+            verbose = ConfigUtils.getBoolean( context.getSession(), false, CONFIG_PROP_VERBOSE );
+            potentialAncestorIds = new HashSet<Object>( conflictIdCount * 2 );
+            resolvedIds = new HashMap<Object, DependencyNode>( conflictIdCount * 2 );
+            items = new ArrayList<ConflictItem>( 256 );
+            infos = new IdentityHashMap<List<DependencyNode>, NodeInfo>( 64 );
+            stack = new IdentityHashMap<List<DependencyNode>, Object>( 64 );
+            parentNodes = new ArrayList<DependencyNode>( 64 );
+            parentScopes = new ArrayList<String>( 64 );
+            parentOptionals = new ArrayList<Boolean>( 64 );
+            parentInfos = new ArrayList<NodeInfo>( 64 );
+            conflictCtx = new ConflictContext( root, conflictIds, items );
+            scopeCtx = new ScopeContext( null, null );
+            versionSelector = ConflictResolver.this.versionSelector.getInstance( root, context );
+            scopeSelector = ConflictResolver.this.scopeSelector.getInstance( root, context );
+            scopeDeriver = ConflictResolver.this.scopeDeriver.getInstance( root, context );
+            optionalitySelector = ConflictResolver.this.optionalitySelector.getInstance( root, context );
+        }
+
+        void prepare( Object conflictId, Collection<Object> cyclicPredecessors )
+        {
+            currentId = conflictId;
+            conflictCtx.conflictId = conflictId;
+            conflictCtx.winner = null;
+            conflictCtx.scope = null;
+            conflictCtx.optional = null;
+            items.clear();
+            infos.clear();
+            if ( cyclicPredecessors != null )
+            {
+                potentialAncestorIds.addAll( cyclicPredecessors );
+            }
+        }
+
+        void finish()
+        {
+            List<DependencyNode> previousParent = null;
+            int previousDepth = 0;
+            totalConflictItems += items.size();
+            for ( int i = items.size() - 1; i >= 0; i-- )
+            {
+                ConflictItem item = items.get( i );
+                if ( item.parent == previousParent )
+                {
+                    item.depth = previousDepth;
+                }
+                else if ( item.parent != null )
+                {
+                    previousParent = item.parent;
+                    NodeInfo info = infos.get( previousParent );
+                    previousDepth = info.minDepth + 1;
+                    item.depth = previousDepth;
+                }
+            }
+            potentialAncestorIds.add( currentId );
+        }
+
+        void winner()
+        {
+            resolvedIds.put( currentId, ( conflictCtx.winner != null ) ? conflictCtx.winner.node : null );
+        }
+
+        boolean loser( DependencyNode node, Object conflictId )
+        {
+            DependencyNode winner = resolvedIds.get( conflictId );
+            return winner != null && winner != node;
+        }
+
+        boolean push( DependencyNode node, Object conflictId )
+            throws RepositoryException
+        {
+            if ( conflictId == null )
+            {
+                if ( node.getDependency() != null )
+                {
+                    if ( node.getData().get( NODE_DATA_WINNER ) != null )
+                    {
+                        return false;
+                    }
+                    throw new RepositoryException( "missing conflict id for node " + node );
+                }
+            }
+            else if ( !potentialAncestorIds.contains( conflictId ) )
+            {
+                return false;
+            }
+
+            List<DependencyNode> graphNode = node.getChildren();
+            if ( stack.put( graphNode, Boolean.TRUE ) != null )
+            {
+                return false;
+            }
+
+            int depth = depth();
+            String scope = deriveScope( node, conflictId );
+            boolean optional = deriveOptional( node, conflictId );
+            NodeInfo info = infos.get( graphNode );
+            if ( info == null )
+            {
+                info = new NodeInfo( depth, scope, optional );
+                infos.put( graphNode, info );
+                parentInfos.add( info );
+                parentNodes.add( node );
+                parentScopes.add( scope );
+                parentOptionals.add( optional );
+            }
+            else
+            {
+                int changes = info.update( depth, scope, optional );
+                if ( changes == 0 )
+                {
+                    stack.remove( graphNode );
+                    return false;
+                }
+                parentInfos.add( null ); // disable creating new conflict items, we update the existing ones below
+                parentNodes.add( node );
+                parentScopes.add( scope );
+                parentOptionals.add( optional );
+                if ( info.children != null )
+                {
+                    if ( ( changes & NodeInfo.CHANGE_SCOPE ) != 0 )
+                    {
+                        for ( int i = info.children.size() - 1; i >= 0; i-- )
+                        {
+                            ConflictItem item = info.children.get( i );
+                            String childScope = deriveScope( item.node, null );
+                            item.addScope( childScope );
+                        }
+                    }
+                    if ( ( changes & NodeInfo.CHANGE_OPTIONAL ) != 0 )
+                    {
+                        for ( int i = info.children.size() - 1; i >= 0; i-- )
+                        {
+                            ConflictItem item = info.children.get( i );
+                            boolean childOptional = deriveOptional( item.node, null );
+                            item.addOptional( childOptional );
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        void pop()
+        {
+            int last = parentInfos.size() - 1;
+            parentInfos.remove( last );
+            parentScopes.remove( last );
+            parentOptionals.remove( last );
+            DependencyNode node = parentNodes.remove( last );
+            stack.remove( node.getChildren() );
+        }
+
+        void add( DependencyNode node )
+            throws RepositoryException
+        {
+            DependencyNode parent = parent();
+            if ( parent == null )
+            {
+                ConflictItem item = newConflictItem( parent, node );
+                items.add( item );
+            }
+            else
+            {
+                NodeInfo info = parentInfos.get( parentInfos.size() - 1 );
+                if ( info != null )
+                {
+                    ConflictItem item = newConflictItem( parent, node );
+                    info.add( item );
+                    items.add( item );
+                }
+            }
+        }
+
+        private ConflictItem newConflictItem( DependencyNode parent, DependencyNode node )
+            throws RepositoryException
+        {
+            return new ConflictItem( parent, node, deriveScope( node, null ), deriveOptional( node, null ) );
+        }
+
+        private int depth()
+        {
+            return parentNodes.size();
+        }
+
+        private DependencyNode parent()
+        {
+            int size = parentNodes.size();
+            return ( size <= 0 ) ? null : parentNodes.get( size - 1 );
+        }
+
+        private String deriveScope( DependencyNode node, Object conflictId )
+            throws RepositoryException
+        {
+            if ( node.getPremanagedScope() != null
+                || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+//            if ( ( node.getManagedBits() & DependencyNode.MANAGED_SCOPE ) != 0
+//                || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+            {
+                return scope( node.getDependency() );
+            }
+
+            int depth = parentNodes.size();
+            scopes( depth, node.getDependency() );
+            if ( depth > 0 )
+            {
+                scopeDeriver.deriveScope( scopeCtx );
+            }
+            return scopeCtx.derivedScope;
+        }
+
+        private void scopes( int parent, Dependency child )
+        {
+            scopeCtx.parentScope = ( parent > 0 ) ? parentScopes.get( parent - 1 ) : null;
+            scopeCtx.derivedScope = scope( child );
+            scopeCtx.childScope = scopeCtx.derivedScope;
+        }
+
+        private String scope( Dependency dependency )
+        {
+            return ( dependency != null ) ? dependency.getScope() : null;
+        }
+
+        private boolean deriveOptional( DependencyNode node, Object conflictId )
+        {
+            Dependency dep = node.getDependency();
+            boolean optional = ( dep != null ) ? dep.isOptional() : false;
+            if ( optional
+                || ( node.getData().get( NODE_DATA_ORIGINAL_OPTIONALITY ) != null
+                    && ( (Boolean) node.getData().get( NODE_DATA_ORIGINAL_OPTIONALITY ) ).booleanValue() )
+                || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+//            if ( optional || ( node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL ) != 0
+//                || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+            {
+                return optional;
+            }
+            int depth = parentNodes.size();
+            return ( depth > 0 ) ? parentOptionals.get( depth - 1 ) : false;
+        }
+
+    }
+
+    /**
+     * A context used to hold information that is relevant for deriving the scope of a child dependency.
+     * 
+     * @see ScopeDeriver
+     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+     *                change without notice and only exists to enable unit testing.
+     */
+    public static final class ScopeContext
+    {
+
+        String parentScope;
+
+        String childScope;
+
+        String derivedScope;
+
+        /**
+         * Creates a new scope context with the specified properties.
+         * 
+         * @param parentScope The scope of the parent dependency, may be {@code null}.
+         * @param childScope The scope of the child dependency, may be {@code null}.
+         * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+         *              change without notice and only exists to enable unit testing.
+         */
+        public ScopeContext( String parentScope, String childScope )
+        {
+            this.parentScope = ( parentScope != null ) ? parentScope : "";
+            derivedScope = ( childScope != null ) ? childScope : "";
+            this.childScope = derivedScope;
+        }
+
+        /**
+         * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of
+         * the scope deriver.
+         * 
+         * @return The scope of the parent dependency, never {@code null}.
+         */
+        public String getParentScope()
+        {
+            return parentScope;
+        }
+
+        /**
+         * Gets the original scope of the child dependency. This is the scope that was declared in the artifact
+         * descriptor of the parent dependency.
+         * 
+         * @return The original scope of the child dependency, never {@code null}.
+         */
+        public String getChildScope()
+        {
+            return childScope;
+        }
+
+        /**
+         * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the
+         * scope deriver makes changes.
+         * 
+         * @return The derived scope of the child dependency, never {@code null}.
+         */
+        public String getDerivedScope()
+        {
+            return derivedScope;
+        }
+
+        /**
+         * Sets the derived scope of the child dependency.
+         * 
+         * @param derivedScope The derived scope of the dependency, may be {@code null}.
+         */
+        public void setDerivedScope( String derivedScope )
+        {
+            this.derivedScope = ( derivedScope != null ) ? derivedScope : "";
+        }
+
+    }
+
+    /**
+     * A conflicting dependency.
+     * 
+     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+     *                change without notice and only exists to enable unit testing.
+     */
+    public static final class ConflictItem
+    {
+
+        // nodes can share child lists, we care about the unique owner of a child node which is the child list
+        final List<DependencyNode> parent;
+
+        // only for debugging/toString() to help identify the parent node(s)
+        final Artifact artifact;
+
+        final DependencyNode node;
+
+        int depth;
+
+        // we start with String and update to Set<String> if needed
+        Object scopes;
+
+        // bit field of OPTIONAL_FALSE and OPTIONAL_TRUE
+        int optionalities;
+
+        /**
+         * Bit flag indicating whether one or more paths consider the dependency non-optional.
+         */
+        public static final int OPTIONAL_FALSE = 0x01;
+
+        /**
+         * Bit flag indicating whether one or more paths consider the dependency optional.
+         */
+        public static final int OPTIONAL_TRUE = 0x02;
+
+        ConflictItem( DependencyNode parent, DependencyNode node, String scope, boolean optional )
+        {
+            if ( parent != null )
+            {
+                this.parent = parent.getChildren();
+                this.artifact = parent.getDependency().getArtifact();
+//                this.artifact = parent.getArtifact();
+            }
+            else
+            {
+                this.parent = null;
+                this.artifact = null;
+            }
+            this.node = node;
+            this.scopes = scope;
+            this.optionalities = optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
+        }
+
+        /**
+         * Creates a new conflict item with the specified properties.
+         * 
+         * @param parent The parent node of the conflicting dependency, may be {@code null}.
+         * @param node The conflicting dependency, must not be {@code null}.
+         * @param depth The zero-based depth of the conflicting dependency.
+         * @param optionalities The optionalities the dependency was encountered with, encoded as a bit field consisting
+         *            of {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} and
+         *            {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE}.
+         * @param scopes The derived scopes of the conflicting dependency, must not be {@code null}.
+         * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+         *              change without notice and only exists to enable unit testing.
+         */
+        public ConflictItem( DependencyNode parent, DependencyNode node, int depth, int optionalities,
+                             String... scopes )
+        {
+            this.parent = ( parent != null ) ? parent.getChildren() : null;
+            this.artifact = ( parent != null ) ? parent.getDependency().getArtifact() : null;
+//            this.artifact = ( parent != null ) ? parent.getArtifact() : null;
+            this.node = node;
+            this.depth = depth;
+            this.optionalities = optionalities;
+            this.scopes = Arrays.asList( scopes );
+        }
+
+        /**
+         * Determines whether the specified conflict item is a sibling of this item.
+         * 
+         * @param item The other conflict item, must not be {@code null}.
+         * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise.
+         */
+        public boolean isSibling( ConflictItem item )
+        {
+            return parent == item.parent;
+        }
+
+        /**
+         * Gets the dependency node involved in the conflict.
+         * 
+         * @return The involved dependency node, never {@code null}.
+         */
+        public DependencyNode getNode()
+        {
+            return node;
+        }
+
+        /**
+         * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}.
+         * 
+         * @return The involved dependency, never {@code null}.
+         */
+        public Dependency getDependency()
+        {
+            return node.getDependency();
+        }
+
+        /**
+         * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the
+         * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest
+         * possible depth.
+         * 
+         * @return The zero-based depth of the node in the graph.
+         */
+        public int getDepth()
+        {
+            return depth;
+        }
+
+        /**
+         * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via
+         * different paths and each path might result in a different derived scope.
+         * 
+         * @see ScopeDeriver
+         * @return The (read-only) set of derived scopes of the dependency, never {@code null}.
+         */
+        @SuppressWarnings( "unchecked" )
+        public Collection<String> getScopes()
+        {
+            if ( scopes instanceof String )
+            {
+                return Collections.singleton( (String) scopes );
+            }
+            return (Collection<String>) scopes;
+        }
+
+        @SuppressWarnings( "unchecked" )
+        void addScope( String scope )
+        {
+            if ( scopes instanceof Collection )
+            {
+                ( (Collection<String>) scopes ).add( scope );
+            }
+            else if ( !scopes.equals( scope ) )
+            {
+                Collection<Object> set = new HashSet<Object>();
+                set.add( scopes );
+                set.add( scope );
+                scopes = set;
+            }
+        }
+
+        /**
+         * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via
+         * different paths and each path might result in a different derived optionality.
+         * 
+         * @return A bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or
+         *         {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the
+         *         dependency was encountered with.
+         */
+        public int getOptionalities()
+        {
+            return optionalities;
+        }
+
+        void addOptional( boolean optional )
+        {
+            optionalities |= optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
+        }
+
+        @Override
+        public String toString()
+        {
+            return node + " @ " + depth + " < " + artifact;
+        }
+
+    }
+
+    /**
+     * A context used to hold information that is relevant for resolving version and scope conflicts.
+     * 
+     * @see VersionSelector
+     * @see ScopeSelector
+     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+     *                change without notice and only exists to enable unit testing.
+     */
+    public static final class ConflictContext
+    {
+
+        final DependencyNode root;
+
+        final Map<?, ?> conflictIds;
+
+        final Collection<ConflictItem> items;
+
+        Object conflictId;
+
+        ConflictItem winner;
+
+        String scope;
+
+        Boolean optional;
+
+        ConflictContext( DependencyNode root, Map<?, ?> conflictIds, Collection<ConflictItem> items )
+        {
+            this.root = root;
+            this.conflictIds = conflictIds;
+            this.items = Collections.unmodifiableCollection( items );
+        }
+
+        /**
+         * Creates a new conflict context.
+         * 
+         * @param root The root node of the dependency graph, must not be {@code null}.
+         * @param conflictId The conflict id for the set of conflicting dependencies in this context, must not be
+         *            {@code null}.
+         * @param conflictIds The mapping from dependency node to conflict id, must not be {@code null}.
+         * @param items The conflict items in this context, must not be {@code null}.
+         * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+         *              change without notice and only exists to enable unit testing.
+         */
+        public ConflictContext( DependencyNode root, Object conflictId, Map<DependencyNode, Object> conflictIds,
+                                Collection<ConflictItem> items )
+        {
+            this( root, conflictIds, items );
+            this.conflictId = conflictId;
+        }
+
+        /**
+         * Gets the root node of the dependency graph being transformed.
+         * 
+         * @return The root node of the dependeny graph, never {@code null}.
+         */
+        public DependencyNode getRoot()
+        {
+            return root;
+        }
+
+        /**
+         * Determines whether the specified dependency node belongs to this conflict context.
+         * 
+         * @param node The dependency node to check, must not be {@code null}.
+         * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise.
+         */
+        public boolean isIncluded( DependencyNode node )
+        {
+            return conflictId.equals( conflictIds.get( node ) );
+        }
+
+        /**
+         * Gets the collection of conflict items in this context.
+         * 
+         * @return The (read-only) collection of conflict items in this context, never {@code null}.
+         */
+        public Collection<ConflictItem> getItems()
+        {
+            return items;
+        }
+
+        /**
+         * Gets the conflict item which has been selected as the winner among the conflicting dependencies.
+         * 
+         * @return The winning conflict item or {@code null} if not set yet.
+         */
+        public ConflictItem getWinner()
+        {
+            return winner;
+        }
+
+        /**
+         * Sets the conflict item which has been selected as the winner among the conflicting dependencies.
+         * 
+         * @param winner The winning conflict item, may be {@code null}.
+         */
+        public void setWinner( ConflictItem winner )
+        {
+            this.winner = winner;
+        }
+
+        /**
+         * Gets the effective scope of the winning dependency.
+         * 
+         * @return The effective scope of the winning dependency or {@code null} if none.
+         */
+        public String getScope()
+        {
+            return scope;
+        }
+
+        /**
+         * Sets the effective scope of the winning dependency.
+         * 
+         * @param scope The effective scope, may be {@code null}.
+         */
+        public void setScope( String scope )
+        {
+            this.scope = scope;
+        }
+
+        /**
+         * Gets the effective optional flag of the winning dependency.
+         * 
+         * @return The effective optional flag or {@code null} if none.
+         */
+        public Boolean getOptional()
+        {
+            return optional;
+        }
+
+        /**
+         * Sets the effective optional flag of the winning dependency.
+         * 
+         * @param optional The effective optional flag, may be {@code null}.
+         */
+        public void setOptional( Boolean optional )
+        {
+            this.optional = optional;
+        }
+
+        @Override
+        public String toString()
+        {
+            return winner + " @ " + scope + " < " + items;
+        }
+
+    }
+
+    /**
+     * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The
+     * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The
+     * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the
+     * {@link ScopeSelector}. Implementations must be stateless.
+     */
+    public abstract static class VersionSelector
+    {
+
+        /**
+         * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls
+         * this method once per
+         * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+         * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+         * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+         * default implementation simply returns the current instance which is appropriate for implementations which do
+         * not require auxiliary data.
+         * 
+         * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+         * @param context The graph transformation context, must not be {@code null}.
+         * @return The scope deriver to use for the given graph transformation, never {@code null}.
+         * @throws RepositoryException If the instance could not be retrieved.
+         */
+        public VersionSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+            throws RepositoryException
+        {
+            return this;
+        }
+
+        /**
+         * Determines the winning node among conflicting dependencies. Implementations will usually iterate
+         * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call
+         * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a
+         * winner will automatically fail the entire conflict resolution.
+         * 
+         * @param context The conflict context, must not be {@code null}.
+         * @throws RepositoryException If the version selection failed.
+         */
+        public abstract void selectVersion( ConflictContext context )
+            throws RepositoryException;
+
+    }
+
+    /**
+     * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a
+     * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the
+     * {@link VersionSelector} has picked the winning node. Implementations must be stateless.
+     */
+    public abstract static class ScopeSelector
+    {
+
+        /**
+         * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls
+         * this method once per
+         * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+         * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+         * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+         * default implementation simply returns the current instance which is appropriate for implementations which do
+         * not require auxiliary data.
+         * 
+         * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+         * @param context The graph transformation context, must not be {@code null}.
+         * @return The scope selector to use for the given graph transformation, never {@code null}.
+         * @throws RepositoryException If the instance could not be retrieved.
+         */
+        public ScopeSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+            throws RepositoryException
+        {
+            return this;
+        }
+
+        /**
+         * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}.
+         * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
+         * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the
+         * effective scope.
+         * 
+         * @param context The conflict context, must not be {@code null}.
+         * @throws RepositoryException If the scope selection failed.
+         */
+        public abstract void selectScope( ConflictContext context )
+            throws RepositoryException;
+
+    }
+
+    /**
+     * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope
+     * of its parent. Implementations must be stateless.
+     */
+    public abstract static class ScopeDeriver
+    {
+
+        /**
+         * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls
+         * this method once per
+         * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+         * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+         * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+         * default implementation simply returns the current instance which is appropriate for implementations which do
+         * not require auxiliary data.
+         * 
+         * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+         * @param context The graph transformation context, must not be {@code null}.
+         * @return The scope deriver to use for the given graph transformation, never {@code null}.
+         * @throws RepositoryException If the instance could not be retrieved.
+         */
+        public ScopeDeriver getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+            throws RepositoryException
+        {
+            return this;
+        }
+
+        /**
+         * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call
+         * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is
+         * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged.
+         * 
+         * @param context The scope context, must not be {@code null}.
+         * @throws RepositoryException If the scope deriviation failed.
+         */
+        public abstract void deriveScope( ScopeContext context )
+            throws RepositoryException;
+
+    }
+
+    /**
+     * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a
+     * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the
+     * {@link VersionSelector} has picked the winning node. Implementations must be stateless.
+     */
+    public abstract static class OptionalitySelector
+    {
+
+        /**
+         * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver
+         * calls this method once per
+         * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+         * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+         * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+         * default implementation simply returns the current instance which is appropriate for implementations which do
+         * not require auxiliary data.
+         * 
+         * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+         * @param context The graph transformation context, must not be {@code null}.
+         * @return The optionality selector to use for the given graph transformation, never {@code null}.
+         * @throws RepositoryException If the instance could not be retrieved.
+         */
+        public OptionalitySelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+            throws RepositoryException
+        {
+            return this;
+        }
+
+        /**
+         * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}.
+         * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
+         * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to
+         * deliver the effective optional flag.
+         * 
+         * @param context The conflict context, must not be {@code null}.
+         * @throws RepositoryException If the optionality selection failed.
+         */
+        public abstract void selectOptionality( ConflictContext context )
+            throws RepositoryException;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java
new file mode 100644
index 0000000..36f2bd7
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/ExclusionDependencySelector.java
@@ -0,0 +1,228 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.TreeSet;
+
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.collection.DependencyCollectionContext;
+import org.sonatype.aether.collection.DependencySelector;
+import org.sonatype.aether.graph.Dependency;
+import org.sonatype.aether.graph.Exclusion;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public class ExclusionDependencySelector
+    implements DependencySelector
+{
+
+    // sorted and dupe-free array, faster to iterate than LinkedHashSet
+    private final Exclusion[] exclusions;
+
+    private int hashCode;
+
+    /**
+     * Creates a new selector without any exclusions.
+     */
+    public ExclusionDependencySelector()
+    {
+        this.exclusions = new Exclusion[0];
+    }
+
+    /**
+     * Creates a new selector with the specified exclusions.
+     * 
+     * @param exclusions The exclusions, may be {@code null}.
+     */
+    public ExclusionDependencySelector( Collection<Exclusion> exclusions )
+    {
+        if ( exclusions != null && !exclusions.isEmpty() )
+        {
+            TreeSet<Exclusion> sorted = new TreeSet<Exclusion>( ExclusionComparator.INSTANCE );
+            sorted.addAll( exclusions );
+            this.exclusions = sorted.toArray( new Exclusion[sorted.size()] );
+        }
+        else
+        {
+            this.exclusions = new Exclusion[0];
+        }
+    }
+
+    private ExclusionDependencySelector( Exclusion[] exclusions )
+    {
+        this.exclusions = exclusions;
+    }
+
+    public boolean selectDependency( Dependency dependency )
+    {
+        Artifact artifact = dependency.getArtifact();
+        for ( Exclusion exclusion : exclusions )
+        {
+            if ( matches( exclusion, artifact ) )
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean matches( Exclusion exclusion, Artifact artifact )
+    {
+        if ( !matches( exclusion.getArtifactId(), artifact.getArtifactId() ) )
+        {
+            return false;
+        }
+        if ( !matches( exclusion.getGroupId(), artifact.getGroupId() ) )
+        {
+            return false;
+        }
+        if ( !matches( exclusion.getExtension(), artifact.getExtension() ) )
+        {
+            return false;
+        }
+        if ( !matches( exclusion.getClassifier(), artifact.getClassifier() ) )
+        {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean matches( String pattern, String value )
+    {
+        return "*".equals( pattern ) || pattern.equals( value );
+    }
+
+    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
+    {
+        Dependency dependency = context.getDependency();
+        Collection<Exclusion> exclusions = ( dependency != null ) ? dependency.getExclusions() : null;
+        if ( exclusions == null || exclusions.isEmpty() )
+        {
+            return this;
+        }
+
+        Exclusion[] merged = this.exclusions;
+        int count = merged.length;
+        for ( Exclusion exclusion : exclusions )
+        {
+            int index = Arrays.binarySearch( merged, exclusion, ExclusionComparator.INSTANCE );
+            if ( index < 0 )
+            {
+                index = -( index + 1 );
+                if ( count >= merged.length )
+                {
+                    Exclusion[] tmp = new Exclusion[merged.length + exclusions.size()];
+                    System.arraycopy( merged, 0, tmp, 0, index );
+                    tmp[index] = exclusion;
+                    System.arraycopy( merged, index, tmp, index + 1, count - index );
+                    merged = tmp;
+                }
+                else
+                {
+                    System.arraycopy( merged, index, merged, index + 1, count - index );
+                    merged[index] = exclusion;
+                }
+                count++;
+            }
+        }
+        if ( merged == this.exclusions )
+        {
+            return this;
+        }
+        if ( merged.length != count )
+        {
+            Exclusion[] tmp = new Exclusion[count];
+            System.arraycopy( merged, 0, tmp, 0, count );
+            merged = tmp;
+        }
+
+        return new ExclusionDependencySelector( merged );
+    }
+
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( this == obj )
+        {
+            return true;
+        }
+        else if ( null == obj || !getClass().equals( obj.getClass() ) )
+        {
+            return false;
+        }
+
+        ExclusionDependencySelector that = (ExclusionDependencySelector) obj;
+        return Arrays.equals( exclusions, that.exclusions );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        if ( hashCode == 0 )
+        {
+            int hash = getClass().hashCode();
+            hash = hash * 31 + Arrays.hashCode( exclusions );
+            hashCode = hash;
+        }
+        return hashCode;
+    }
+
+    private static class ExclusionComparator
+        implements Comparator<Exclusion>
+    {
+
+        static final ExclusionComparator INSTANCE = new ExclusionComparator();
+
+        public int compare( Exclusion e1, Exclusion e2 )
+        {
+            if ( e1 == null )
+            {
+                return ( e2 == null ) ? 0 : 1;
+            }
+            else if ( e2 == null )
+            {
+                return -1;
+            }
+            int rel = e1.getArtifactId().compareTo( e2.getArtifactId() );
+            if ( rel == 0 )
+            {
+                rel = e1.getGroupId().compareTo( e2.getGroupId() );
+                if ( rel == 0 )
+                {
+                    rel = e1.getExtension().compareTo( e2.getExtension() );
+                    if ( rel == 0 )
+                    {
+                        rel = e1.getClassifier().compareTo( e2.getClassifier() );
+                    }
+                }
+            }
+            return rel;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java
new file mode 100644
index 0000000..73367bf
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeDeriver.java
@@ -0,0 +1,72 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ScopeContext;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ScopeDeriver;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.util.artifact.JavaScopes;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public class JavaScopeDeriver
+    extends ScopeDeriver
+{
+
+    @Override
+    public void deriveScope( ScopeContext context )
+        throws RepositoryException
+    {
+        context.setDerivedScope( getDerivedScope( context.getParentScope(), context.getChildScope() ) );
+    }
+
+    private String getDerivedScope( String parentScope, String childScope )
+    {
+        String derivedScope;
+
+        if ( JavaScopes.SYSTEM.equals( childScope ) || JavaScopes.TEST.equals( childScope ) )
+        {
+            derivedScope = childScope;
+        }
+        else if ( parentScope == null || parentScope.length() <= 0 || JavaScopes.COMPILE.equals( parentScope ) )
+        {
+            derivedScope = childScope;
+        }
+        else if ( JavaScopes.TEST.equals( parentScope ) || JavaScopes.RUNTIME.equals( parentScope ) )
+        {
+            derivedScope = parentScope;
+        }
+        else if ( JavaScopes.SYSTEM.equals( parentScope ) || JavaScopes.PROVIDED.equals( parentScope ) )
+        {
+            derivedScope = JavaScopes.PROVIDED;
+        }
+        else
+        {
+            derivedScope = JavaScopes.RUNTIME;
+        }
+
+        return derivedScope;
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java
new file mode 100644
index 0000000..6eeea83
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/JavaScopeSelector.java
@@ -0,0 +1,102 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictContext;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictItem;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ScopeSelector;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.util.artifact.JavaScopes;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public final class JavaScopeSelector
+    extends ScopeSelector
+{
+
+    @Override
+    public void selectScope( ConflictContext context )
+        throws RepositoryException
+    {
+        String scope = context.getWinner().getDependency().getScope();
+        if ( !JavaScopes.SYSTEM.equals( scope ) )
+        {
+            scope = chooseEffectiveScope( context.getItems() );
+        }
+        context.setScope( scope );
+    }
+
+    private String chooseEffectiveScope( Collection<ConflictItem> items )
+    {
+        Set<String> scopes = new HashSet<String>();
+        for ( ConflictItem item : items )
+        {
+            if ( item.getDepth() <= 1 )
+            {
+                return item.getDependency().getScope();
+            }
+            scopes.addAll( item.getScopes() );
+        }
+        return chooseEffectiveScope( scopes );
+    }
+
+    private String chooseEffectiveScope( Set<String> scopes )
+    {
+        if ( scopes.size() > 1 )
+        {
+            scopes.remove( JavaScopes.SYSTEM );
+        }
+
+        String effectiveScope = "";
+
+        if ( scopes.size() == 1 )
+        {
+            effectiveScope = scopes.iterator().next();
+        }
+        else if ( scopes.contains( JavaScopes.COMPILE ) )
+        {
+            effectiveScope = JavaScopes.COMPILE;
+        }
+        else if ( scopes.contains( JavaScopes.RUNTIME ) )
+        {
+            effectiveScope = JavaScopes.RUNTIME;
+        }
+        else if ( scopes.contains( JavaScopes.PROVIDED ) )
+        {
+            effectiveScope = JavaScopes.PROVIDED;
+        }
+        else if ( scopes.contains( JavaScopes.TEST ) )
+        {
+            effectiveScope = JavaScopes.TEST;
+        }
+
+        return effectiveScope;
+    }
+
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java
new file mode 100644
index 0000000..e66b80b
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/Maven3DirectScopeDependencySelector.java
@@ -0,0 +1,132 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import org.sonatype.aether.collection.DependencyCollectionContext;
+import org.sonatype.aether.collection.DependencySelector;
+import org.sonatype.aether.graph.Dependency;
+
+/**
+ * A dependency selector that excludes dependencies of an specific Scope which occur beyond level one of the dependency
+ * graph.
+ * 
+ * @see {@link Dependency#getScope()}
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public class Maven3DirectScopeDependencySelector
+    implements DependencySelector
+{
+
+    private final String scope;
+
+    private final int depth;
+
+    public Maven3DirectScopeDependencySelector( String scope )
+    {
+        this( scope, 0 );
+    }
+
+    private Maven3DirectScopeDependencySelector( String scope, int depth )
+    {
+        if ( scope == null )
+        {
+            throw new IllegalArgumentException( "scope is null!" );
+        }
+        this.scope = scope;
+        this.depth = depth;
+    }
+
+    /**
+     * Decides whether the specified dependency should be included in the dependency graph.
+     * 
+     * @param dependency The dependency to check, must not be {@code null}.
+     * @return {@code false} if the dependency should be excluded from the children of the current node, {@code true}
+     *         otherwise.
+     */
+    @Override
+    public boolean selectDependency( Dependency dependency )
+    {
+        return depth < 2 || !scope.equals( dependency.getScope() );
+    }
+
+    /**
+     * Derives a dependency selector for the specified collection context. When calculating the child selector,
+     * implementors are strongly advised to simply return the current instance if nothing changed to help save memory.
+     * 
+     * @param context The dependency collection context, must not be {@code null}.
+     * @return The dependency selector for the target node, must not be {@code null}.
+     */
+    @Override
+    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
+    {
+        if ( depth >= 2 )
+        {
+            return this;
+        }
+
+        return new Maven3DirectScopeDependencySelector( scope, depth + 1 );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + depth;
+        result = prime * result + ( ( scope == null ) ? 0 : scope.hashCode() );
+        return result;
+    }
+
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( this == obj )
+        {
+            return true;
+        }
+        if ( obj == null )
+        {
+            return false;
+        }
+        if ( getClass() != obj.getClass() )
+        {
+            return false;
+        }
+        Maven3DirectScopeDependencySelector other = (Maven3DirectScopeDependencySelector) obj;
+        if ( depth != other.depth )
+        {
+            return false;
+        }
+        if ( scope == null )
+        {
+            if ( other.scope != null )
+            {
+                return false;
+            }
+        }
+        else if ( !scope.equals( other.scope ) )
+        {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java
new file mode 100644
index 0000000..8d2bc93
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/NearestVersionSelector.java
@@ -0,0 +1,183 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictContext;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictItem;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.VersionSelector;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.collection.UnsolvableVersionConflictException;
+import org.sonatype.aether.graph.DependencyFilter;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.util.graph.PathRecordingDependencyVisitor;
+import org.sonatype.aether.version.Version;
+import org.sonatype.aether.version.VersionConstraint;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public final class NearestVersionSelector
+    extends VersionSelector
+{
+
+    @Override
+    public void selectVersion( ConflictContext context )
+        throws RepositoryException
+    {
+        ConflictGroup group = new ConflictGroup();
+        for ( ConflictItem item : context.getItems() )
+        {
+            DependencyNode node = item.getNode();
+            VersionConstraint constraint = node.getVersionConstraint();
+
+            boolean backtrack = false;
+            boolean hardConstraint = !constraint.getRanges().isEmpty();
+//            boolean hardConstraint = constraint.getRange() != null;
+
+            if ( hardConstraint )
+            {
+                if ( group.constraints.add( constraint ) )
+                {
+                    if ( group.winner != null && !constraint.containsVersion( group.winner.getNode().getVersion() ) )
+                    {
+                        backtrack = true;
+                    }
+                }
+            }
+
+            if ( isAcceptable( group, node.getVersion() ) )
+            {
+                group.candidates.add( item );
+
+                if ( backtrack )
+                {
+                    backtrack( group, context );
+                }
+                else if ( group.winner == null || isNearer( item, group.winner ) )
+                {
+                    group.winner = item;
+                }
+            }
+            else if ( backtrack )
+            {
+                backtrack( group, context );
+            }
+        }
+        context.setWinner( group.winner );
+    }
+
+    private void backtrack( ConflictGroup group, ConflictContext context )
+        throws UnsolvableVersionConflictException
+    {
+        group.winner = null;
+
+        for ( Iterator<ConflictItem> it = group.candidates.iterator(); it.hasNext(); )
+        {
+            ConflictItem candidate = it.next();
+
+            if ( !isAcceptable( group, candidate.getNode().getVersion() ) )
+            {
+                it.remove();
+            }
+            else if ( group.winner == null || isNearer( candidate, group.winner ) )
+            {
+                group.winner = candidate;
+            }
+        }
+
+        if ( group.winner == null )
+        {
+            throw newFailure( context );
+        }
+    }
+
+    private boolean isAcceptable( ConflictGroup group, Version version )
+    {
+        for ( VersionConstraint constraint : group.constraints )
+        {
+            if ( !constraint.containsVersion( version ) )
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isNearer( ConflictItem item1, ConflictItem item2 )
+    {
+        if ( item1.isSibling( item2 ) )
+        {
+            return item1.getNode().getVersion().compareTo( item2.getNode().getVersion() ) > 0;
+        }
+        else
+        {
+            return item1.getDepth() < item2.getDepth();
+        }
+    }
+
+    private UnsolvableVersionConflictException newFailure( final ConflictContext context )
+    {
+        DependencyFilter filter = new DependencyFilter()
+        {
+            public boolean accept( DependencyNode node, List<DependencyNode> parents )
+            {
+                return context.isIncluded( node );
+            }
+        };
+        PathRecordingDependencyVisitor visitor = new PathRecordingDependencyVisitor( filter );
+        context.getRoot().accept( visitor );
+        return new UnsolvableVersionConflictException( visitor.getPaths(), context.conflictId );
+//        return new UnsolvableVersionConflictException( visitor.getPaths() );
+    }
+
+    static final class ConflictGroup
+    {
+
+        final Collection<VersionConstraint> constraints;
+
+        final Collection<ConflictItem> candidates;
+
+        ConflictItem winner;
+
+        ConflictGroup()
+        {
+            constraints = new HashSet<VersionConstraint>();
+            candidates = new ArrayList<ConflictItem>( 64 );
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.valueOf( winner );
+        }
+
+    }
+
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java
new file mode 100644
index 0000000..ffd6a63
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven30/SimpleOptionalitySelector.java
@@ -0,0 +1,64 @@
+package org.apache.maven.shared.dependency.graph.internal.maven30;
+
+/*
+ * 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.
+ */
+
+import java.util.Collection;
+
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictContext;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.ConflictItem;
+import org.apache.maven.shared.dependency.graph.internal.maven30.ConflictResolver.OptionalitySelector;
+import org.sonatype.aether.RepositoryException;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public class SimpleOptionalitySelector
+    extends OptionalitySelector
+{
+
+    @Override
+    public void selectOptionality( ConflictContext context )
+        throws RepositoryException
+    {
+        boolean optional = chooseEffectiveOptionality( context.getItems() );
+        context.setOptional( optional );
+    }
+
+    private boolean chooseEffectiveOptionality( Collection<ConflictItem> items )
+    {
+        boolean optional = true;
+        for ( ConflictItem item : items )
+        {
+            if ( item.getDepth() <= 1 )
+            {
+                return item.getDependency().isOptional();
+            }
+            if ( ( item.getOptionalities() & ConflictItem.OPTIONAL_FALSE ) != 0 )
+            {
+                optional = false;
+            }
+        }
+        return optional;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java
new file mode 100644
index 0000000..dbc71c5
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/maven31/Maven31DirectScopeDependencySelector.java
@@ -0,0 +1,132 @@
+package org.apache.maven.shared.dependency.graph.internal.maven31;
+
+/*
+ * 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.
+ */
+
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.Dependency;
+
+/**
+ * A dependency selector that excludes dependencies of an specific Scope which occur beyond level one of the dependency
+ * graph.
+ * 
+ * @see {@link Dependency#getScope()}
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public class Maven31DirectScopeDependencySelector
+    implements DependencySelector
+{
+
+    private final String scope;
+
+    private final int depth;
+
+    public Maven31DirectScopeDependencySelector( String scope )
+    {
+        this( scope, 0 );
+    }
+
+    private Maven31DirectScopeDependencySelector( String scope, int depth )
+    {
+        if ( scope == null )
+        {
+            throw new IllegalArgumentException( "scope is null!" );
+        }
+        this.scope = scope;
+        this.depth = depth;
+    }
+
+    /**
+     * Decides whether the specified dependency should be included in the dependency graph.
+     * 
+     * @param dependency The dependency to check, must not be {@code null}.
+     * @return {@code false} if the dependency should be excluded from the children of the current node, {@code true}
+     *         otherwise.
+     */
+    @Override
+    public boolean selectDependency( Dependency dependency )
+    {
+        return depth < 2 || !scope.equals( dependency.getScope() );
+    }
+
+    /**
+     * Derives a dependency selector for the specified collection context. When calculating the child selector,
+     * implementors are strongly advised to simply return the current instance if nothing changed to help save memory.
+     * 
+     * @param context The dependency collection context, must not be {@code null}.
+     * @return The dependency selector for the target node, must not be {@code null}.
+     */
+    @Override
+    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
+    {
+        if ( depth >= 2 )
+        {
+            return this;
+        }
+
+        return new Maven31DirectScopeDependencySelector( scope, depth + 1 );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + depth;
+        result = prime * result + ( ( scope == null ) ? 0 : scope.hashCode() );
+        return result;
+    }
+
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( this == obj )
+        {
+            return true;
+        }
+        if ( obj == null )
+        {
+            return false;
+        }
+        if ( getClass() != obj.getClass() )
+        {
+            return false;
+        }
+        Maven31DirectScopeDependencySelector other = (Maven31DirectScopeDependencySelector) obj;
+        if ( depth != other.depth )
+        {
+            return false;
+        }
+        if ( scope == null )
+        {
+            if ( other.scope != null )
+            {
+                return false;
+            }
+        }
+        else if ( !scope.equals( other.scope ) )
+        {
+            return false;
+        }
+        return true;
+    }
+
+}

[maven-dependency-tree] 01/04: Add functionality to collect raw dependencies in Maven 3+

Posted by rf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a commit to branch MSHARED-788
in repository https://gitbox.apache.org/repos/asf/maven-dependency-tree.git

commit b55e83e3ae7a642187ff125abd796d044f648446
Author: Gabriel Belingueres <be...@gmail.com>
AuthorDate: Wed Dec 26 16:08:23 2018 -0300

    Add functionality to collect raw dependencies in Maven 3+
    
    Added the funcionality.
    No added ITs, instead tested the maven-enforcer-plugin with this version
    of m-dependency-tree. Tested and passed all tests with: Maven 3.0.4,
    3.0.5, 3.1.0, 3.5.4, 3.6.0. Didn't work with Maven 3.0.0 (sonatype
    aether 1.7). Other Maven versions not tested.
---
 .../graph/DependencyCollectorBuilder.java          |  50 ++++
 .../DefaultDependencyCollectorBuilder.java         | 116 +++++++++
 .../shared/dependency/graph/internal/Invoker.java  |  22 ++
 .../Maven31DependencyCollectorBuilder.java         | 282 +++++++++++++++++++++
 .../internal/Maven3DependencyCollectorBuilder.java | 282 +++++++++++++++++++++
 5 files changed, 752 insertions(+)

diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java
new file mode 100644
index 0000000..8865fad
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/DependencyCollectorBuilder.java
@@ -0,0 +1,50 @@
+package org.apache.maven.shared.dependency.graph;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.project.ProjectBuildingRequest;
+
+/**
+ * Maven project dependency raw dependency collector API, providing an abstraction layer against Maven 3 and Maven 3.1+
+ * particular Aether implementations.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+public interface DependencyCollectorBuilder
+{
+
+    /**
+     * collect the project's raw dependency graph, with information to allow the API client to reason on its own about
+     * dependencies.
+     * 
+     * @param localRepository the local repository.
+     * @param buildingRequest the request with the project to process its dependencies.
+     * @param filter an artifact filter if not all dependencies are required (can be <code>null</code>)
+     * @return the raw dependency tree
+     * @throws DependencyGraphBuilderException if some of the dependencies could not be collected.
+     */
+    DependencyNode collectDependencyGraph( ArtifactRepository localRepository, ProjectBuildingRequest buildingRequest,
+                                         ArtifactFilter filter )
+        throws DependencyGraphBuilderException;
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java
new file mode 100644
index 0000000..ff420b9
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/DefaultDependencyCollectorBuilder.java
@@ -0,0 +1,116 @@
+package org.apache.maven.shared.dependency.graph.internal;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.codehaus.plexus.PlexusConstants;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.context.Context;
+import org.codehaus.plexus.context.ContextException;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
+
+/**
+ * Default project dependency raw dependency collector API, providing an abstraction layer against Maven 3 and Maven
+ * 3.1+ particular Aether implementations.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+@Component( role = DependencyCollectorBuilder.class )
+public class DefaultDependencyCollectorBuilder
+    extends AbstractLogEnabled
+    implements DependencyCollectorBuilder, Contextualizable
+{
+    protected PlexusContainer container;
+
+    @Override
+    public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
+                                                ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
+        throws DependencyGraphBuilderException
+    {
+        try
+        {
+            String hint = isMaven31() ? "maven31" : "maven3";
+
+            DependencyCollectorBuilder effectiveGraphBuilder =
+                (DependencyCollectorBuilder) container.lookup( DependencyCollectorBuilder.class.getCanonicalName(),
+                                                               hint );
+
+            if ( getLogger().isDebugEnabled() )
+            {
+                MavenProject project = buildingRequest.getProject();
+
+                getLogger().debug( "building " + hint + " RAW dependency tree for " + project.getId() + " with "
+                    + effectiveGraphBuilder.getClass().getSimpleName() );
+            }
+
+            return effectiveGraphBuilder.collectDependencyGraph( localRepository, buildingRequest, filter );
+        }
+        catch ( ComponentLookupException e )
+        {
+            throw new DependencyGraphBuilderException( e.getMessage(), e );
+        }
+    }
+
+    /**
+     * @return true if the current Maven version is Maven 3.1.
+     */
+    protected static boolean isMaven31()
+    {
+        return canFindCoreClass( "org.eclipse.aether.artifact.Artifact" ); // Maven 3.1 specific
+    }
+
+    private static boolean canFindCoreClass( String className )
+    {
+        try
+        {
+            Thread.currentThread().getContextClassLoader().loadClass( className );
+
+            return true;
+        }
+        catch ( ClassNotFoundException e )
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Injects the Plexus content.
+     *
+     * @param context Plexus context to inject.
+     * @throws ContextException if the PlexusContainer could not be located.
+     */
+    @Override
+    public void contextualize( Context context )
+        throws ContextException
+    {
+        container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
index f591973..cabfe37 100644
--- a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Invoker.java
@@ -102,4 +102,26 @@ final class Invoker
             throw new DependencyGraphBuilderException( e.getMessage(), e );
         }
     }
+
+    public static Object invoke( Class<?> objectClazz, String staticMethod, Class<?> argClazz1, Class<?> argClazz2,
+                                 Object arg1, Object arg2 )
+        throws DependencyGraphBuilderException
+    {
+        try
+        {
+            return objectClazz.getMethod( staticMethod, argClazz1, argClazz2 ).invoke( null, arg1, arg2 );
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new DependencyGraphBuilderException( e.getMessage(), e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            throw new DependencyGraphBuilderException( e.getMessage(), e );
+        }
+        catch ( NoSuchMethodException e )
+        {
+            throw new DependencyGraphBuilderException( e.getMessage(), e );
+        }
+    }
 }
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
new file mode 100644
index 0000000..f4fd955
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
@@ -0,0 +1,282 @@
+package org.apache.maven.shared.dependency.graph.internal;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
+import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
+import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
+import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
+import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor;
+import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
+import org.eclipse.aether.version.VersionConstraint;
+
+/**
+ * Project dependency raw dependency collector API, abstracting Maven 3.1+'s Aether implementation.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+@Component( role = DependencyCollectorBuilder.class, hint = "maven31" )
+public class Maven31DependencyCollectorBuilder
+    extends AbstractLogEnabled
+    implements DependencyCollectorBuilder
+{
+    @Requirement
+    private RepositorySystem repositorySystem;
+
+    @Override
+    public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
+                                                ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
+        throws DependencyGraphBuilderException
+    {
+        DefaultRepositorySystemSession session = null;
+        try
+        {
+            MavenProject project = buildingRequest.getProject();
+            Artifact projectArtifact = project.getArtifact();
+            List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
+
+            DefaultRepositorySystemSession repositorySession =
+                (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
+            
+            session = new DefaultRepositorySystemSession( repositorySession );
+
+            DependencyGraphTransformer transformer =
+                new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
+                                      new SimpleOptionalitySelector(), new JavaScopeDeriver() );
+
+            session.setDependencyGraphTransformer( transformer );
+
+            session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
+            session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, true );
+            
+            org.eclipse.aether.artifact.Artifact aetherArtifact =
+                (org.eclipse.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                                                       Artifact.class, projectArtifact );
+
+            @SuppressWarnings( "unchecked" )
+            List<org.eclipse.aether.repository.RemoteRepository> aetherRepos =
+                (List<org.eclipse.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos",
+                                                                                       List.class,
+                                                                                       remoteArtifactRepositories );
+
+            CollectRequest collectRequest = new CollectRequest();
+            collectRequest.setRoot( new org.eclipse.aether.graph.Dependency( aetherArtifact, "" ) );
+            collectRequest.setRepositories( aetherRepos );
+
+            DependencySelector depFilter =
+                new AndDependencySelector( new ScopeDependencySelector( "provided" ), new OptionalDependencySelector(),
+                                           new ExclusionDependencySelector() );
+            session.setDependencySelector( depFilter );
+            
+            org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+            collectDependencyList( collectRequest, project, stereotypes );
+            collectManagedDependencyList( collectRequest, project, stereotypes );
+
+            CollectResult collectResult = repositorySystem.collectDependencies( session, collectRequest );
+
+            org.eclipse.aether.graph.DependencyNode rootNode = collectResult.getRoot();
+
+            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
+            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
+            rootNode.accept( treeVisitor );
+
+            rootNode = cloner.getRootNode();
+
+            if ( getLogger().isDebugEnabled() )
+            {
+                logTree( rootNode );
+            }
+
+            return buildDependencyNode( null, rootNode, projectArtifact, filter );
+        }
+        catch ( DependencyCollectionException e )
+        {
+            throw new DependencyGraphBuilderException( "Could not collect dependencies: " + e.getResult(), e );
+        }
+        finally
+        {
+            if ( session != null )
+            {
+                session.setReadOnly();
+            }
+        }
+    }
+
+    private void logTree( org.eclipse.aether.graph.DependencyNode rootNode )
+    {
+        // print the node tree with its associated data Map
+        rootNode.accept( new TreeDependencyVisitor( new DependencyVisitor()
+        {
+            String indent = "";
+
+            @Override
+            public boolean visitEnter( org.eclipse.aether.graph.DependencyNode dependencyNode )
+            {
+                getLogger().debug( indent + "Aether node: " + dependencyNode + " data map: "
+                    + dependencyNode.getData() );
+                indent += "    ";
+                return true;
+            }
+
+            @Override
+            public boolean visitLeave( org.eclipse.aether.graph.DependencyNode dependencyNode )
+            {
+                indent = indent.substring( 0, indent.length() - 4 );
+                return true;
+            }
+        } ) );
+    }
+
+    private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
+                                               ArtifactTypeRegistry stereotypes )
+        throws DependencyGraphBuilderException
+    {
+        if ( project.getDependencyManagement() != null )
+        {
+            for ( Dependency dependency : project.getDependencyManagement().getDependencies() )
+            {
+                org.eclipse.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
+                collectRequest.addManagedDependency( aetherDep );
+            }
+        }
+    }
+
+    private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
+                                         org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes )
+        throws DependencyGraphBuilderException
+    {
+        for ( Dependency dependency : project.getDependencies() )
+        {
+            org.eclipse.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
+            collectRequest.addDependency( aetherDep );
+        }
+    }
+
+    // CHECKSTYLE_OFF: LineLength
+    private org.eclipse.aether.graph.Dependency toAetherDependency( org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes,
+                                                                    Dependency dependency )
+        throws DependencyGraphBuilderException
+    {
+        org.eclipse.aether.graph.Dependency aetherDep =
+            (org.eclipse.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
+                                                                  Dependency.class,
+                                                                  org.eclipse.aether.artifact.ArtifactTypeRegistry.class,
+                                                                  dependency, stereotypes );
+        return aetherDep;
+    }
+    // CHECKSTYLE_ON: LineLength
+
+    private Artifact getDependencyArtifact( org.eclipse.aether.graph.Dependency dep )
+    {
+        org.eclipse.aether.artifact.Artifact artifact = dep.getArtifact();
+
+        try
+        {
+            Artifact mavenArtifact = (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                                                org.eclipse.aether.artifact.Artifact.class, artifact );
+
+            mavenArtifact.setScope( dep.getScope() );
+            mavenArtifact.setOptional( dep.isOptional() );
+
+            return mavenArtifact;
+        }
+        catch ( DependencyGraphBuilderException e )
+        {
+            // ReflectionException should not happen
+            throw new RuntimeException( e.getMessage(), e );
+        }
+    }
+
+    private DependencyNode buildDependencyNode( DependencyNode parent, org.eclipse.aether.graph.DependencyNode node,
+                                                Artifact artifact, ArtifactFilter filter )
+    {
+        String premanagedVersion = DependencyManagerUtils.getPremanagedVersion( node );
+        String premanagedScope = DependencyManagerUtils.getPremanagedScope( node );
+        
+        Boolean optional = null;
+        if ( node.getDependency() != null )
+        {
+            optional = node.getDependency().isOptional();
+        }
+
+        DefaultDependencyNode current =
+            new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
+                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+
+        List<DependencyNode> nodes = new ArrayList<DependencyNode>( node.getChildren().size() );
+        for ( org.eclipse.aether.graph.DependencyNode child : node.getChildren() )
+        {
+            Artifact childArtifact = getDependencyArtifact( child.getDependency() );
+
+            if ( ( filter == null ) || filter.include( childArtifact ) )
+            {
+                nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
+            }
+        }
+
+        current.setChildren( Collections.unmodifiableList( nodes ) );
+
+        return current;
+    }
+
+    private String getVersionSelectedFromRange( VersionConstraint constraint )
+    {
+        if ( ( constraint == null ) || ( constraint.getVersion() != null ) )
+        {
+            return null;
+        }
+
+        return constraint.getRange().toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
new file mode 100644
index 0000000..d6b6d49
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
@@ -0,0 +1,282 @@
+package org.apache.maven.shared.dependency.graph.internal;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.sonatype.aether.RepositorySystem;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.ArtifactTypeRegistry;
+import org.sonatype.aether.collection.CollectRequest;
+import org.sonatype.aether.collection.CollectResult;
+import org.sonatype.aether.collection.DependencyCollectionException;
+import org.sonatype.aether.collection.DependencySelector;
+import org.sonatype.aether.graph.DependencyVisitor;
+import org.sonatype.aether.util.DefaultRepositorySystemSession;
+import org.sonatype.aether.util.graph.CloningDependencyVisitor;
+import org.sonatype.aether.util.graph.TreeDependencyVisitor;
+import org.sonatype.aether.util.graph.selector.AndDependencySelector;
+import org.sonatype.aether.util.graph.selector.ExclusionDependencySelector;
+import org.sonatype.aether.util.graph.selector.OptionalDependencySelector;
+import org.sonatype.aether.util.graph.selector.ScopeDependencySelector;
+import org.sonatype.aether.version.VersionConstraint;
+
+/**
+ * Project dependency raw dependency collector API, abstracting Maven 3's Aether implementation.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.0.2
+ */
+@Component( role = DependencyCollectorBuilder.class, hint = "maven3" )
+public class Maven3DependencyCollectorBuilder
+    extends AbstractLogEnabled
+    implements DependencyCollectorBuilder
+{
+    @Requirement
+    private RepositorySystem repositorySystem;
+
+    @Override
+    public DependencyNode collectDependencyGraph( ArtifactRepository localRepository,
+                                                ProjectBuildingRequest buildingRequest, ArtifactFilter filter )
+        throws DependencyGraphBuilderException
+    {
+        ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            MavenProject project = buildingRequest.getProject();
+
+            Artifact projectArtifact = project.getArtifact();
+            List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
+
+            // throws ClassCastException (classloading issues?)
+            // DefaultRepositorySystemSession repositorySystemSession =
+            // (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
+            RepositorySystemSession repositorySystemSession = buildingRequest.getRepositorySession();
+
+            DefaultRepositorySystemSession session = new DefaultRepositorySystemSession( repositorySystemSession );
+
+            session.setDependencyGraphTransformer( null );
+
+            DependencySelector depFilter =
+                new AndDependencySelector( new ScopeDependencySelector( "provided" ), new OptionalDependencySelector(),
+                                           new ExclusionDependencySelector() );
+            session.setDependencySelector( depFilter );
+
+            org.sonatype.aether.artifact.Artifact aetherArtifact =
+                (org.sonatype.aether.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                                                       Artifact.class, projectArtifact );
+
+            @SuppressWarnings( "unchecked" )
+            List<org.sonatype.aether.repository.RemoteRepository> aetherRepos =
+                (List<org.sonatype.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class,
+                                                                                        "toRepos", List.class,
+                                                                                        remoteArtifactRepositories );
+
+            CollectRequest collectRequest = new CollectRequest();
+            collectRequest.setRoot( new org.sonatype.aether.graph.Dependency( aetherArtifact, "" ) );
+            collectRequest.setRepositories( aetherRepos );
+
+            org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+            collectDependencyList( collectRequest, project, stereotypes );
+            collectManagedDependencyList( collectRequest, project, stereotypes );
+
+            CollectResult collectResult = repositorySystem.collectDependencies( session, collectRequest );
+
+            org.sonatype.aether.graph.DependencyNode rootNode = collectResult.getRoot();
+
+            CloningDependencyVisitor cloner = new CloningDependencyVisitor();
+            TreeDependencyVisitor treeVisitor = new TreeDependencyVisitor( cloner );
+            rootNode.accept( treeVisitor );
+
+            rootNode = cloner.getRootNode();
+
+            if ( getLogger().isDebugEnabled() )
+            {
+                logTree( rootNode );
+            }
+
+            return buildDependencyNode( null, rootNode, projectArtifact, filter );
+        }
+        catch ( DependencyCollectionException e )
+        {
+            throw new DependencyGraphBuilderException( "Could not collect dependencies: " + e.getResult(), e );
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader( prevClassLoader );
+        }
+    }
+
+    private void logTree( org.sonatype.aether.graph.DependencyNode rootNode )
+    {
+        // print the node tree with its associated data Map
+        rootNode.accept( new TreeDependencyVisitor( new DependencyVisitor()
+        {
+            String indent = "";
+
+            @Override
+            public boolean visitEnter( org.sonatype.aether.graph.DependencyNode dependencyNode )
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.append( indent ).append( "Aether node: " ).append( dependencyNode );
+                if ( !dependencyNode.getData().isEmpty() )
+                {
+                    sb.append( "data map: " ).append( dependencyNode.getData() );
+                }
+                if ( dependencyNode.getPremanagedVersion() != null && !dependencyNode.getPremanagedVersion().isEmpty() )
+                {
+                    sb.append( "Premanaged.version: " ).append( dependencyNode.getPremanagedVersion() );
+                }
+                if ( dependencyNode.getPremanagedScope() != null && !dependencyNode.getPremanagedScope().isEmpty() )
+                {
+                    sb.append( "Premanaged.scope: " ).append( dependencyNode.getPremanagedScope() );
+                }
+                getLogger().debug( sb.toString() );
+                indent += "    ";
+                return true;
+            }
+
+            @Override
+            public boolean visitLeave( org.sonatype.aether.graph.DependencyNode dependencyNode )
+            {
+                indent = indent.substring( 0, indent.length() - 4 );
+                return true;
+            }
+        } ) );
+    }
+
+    private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
+                                               ArtifactTypeRegistry stereotypes )
+        throws DependencyGraphBuilderException
+    {
+        if ( project.getDependencyManagement() != null )
+        {
+            for ( Dependency dependency : project.getDependencyManagement().getDependencies() )
+            {
+                org.sonatype.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
+                collectRequest.addManagedDependency( aetherDep );
+            }
+        }
+    }
+
+    private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
+                                        org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes )
+        throws DependencyGraphBuilderException
+    {
+        for ( Dependency dependency : project.getDependencies() )
+        {
+            org.sonatype.aether.graph.Dependency aetherDep = toAetherDependency( stereotypes, dependency );
+            collectRequest.addDependency( aetherDep );
+        }
+    }
+
+    // CHECKSTYLE_OFF: LineLength
+    private org.sonatype.aether.graph.Dependency toAetherDependency( org.sonatype.aether.artifact.ArtifactTypeRegistry stereotypes,
+                                                                    Dependency dependency )
+        throws DependencyGraphBuilderException
+    {
+        org.sonatype.aether.graph.Dependency aetherDep =
+            (org.sonatype.aether.graph.Dependency) Invoker.invoke( RepositoryUtils.class, "toDependency",
+                                                                  Dependency.class,
+                                                                   org.sonatype.aether.artifact.ArtifactTypeRegistry.class,
+                                                                  dependency, stereotypes );
+        return aetherDep;
+    }
+    // CHECKSTYLE_ON: LineLength
+
+    private Artifact getDependencyArtifact( org.sonatype.aether.graph.Dependency dep )
+    {
+        org.sonatype.aether.artifact.Artifact artifact = dep.getArtifact();
+
+        try
+        {
+            Artifact mavenArtifact = (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+                                                                org.sonatype.aether.artifact.Artifact.class, artifact );
+
+            mavenArtifact.setScope( dep.getScope() );
+            mavenArtifact.setOptional( dep.isOptional() );
+
+            return mavenArtifact;
+        }
+        catch ( DependencyGraphBuilderException e )
+        {
+            // ReflectionException should not happen
+            throw new RuntimeException( e.getMessage(), e );
+        }
+    }
+
+    private DependencyNode buildDependencyNode( DependencyNode parent, org.sonatype.aether.graph.DependencyNode node,
+                                                Artifact artifact, ArtifactFilter filter )
+    {
+        String premanagedVersion = node.getPremanagedVersion();
+        String premanagedScope = node.getPremanagedScope();
+
+        Boolean optional = null;
+        if ( node.getDependency() != null )
+        {
+            optional = node.getDependency().isOptional();
+        }
+
+        DefaultDependencyNode current =
+            new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
+                                       getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+
+        List<DependencyNode> nodes = new ArrayList<DependencyNode>( node.getChildren().size() );
+        for ( org.sonatype.aether.graph.DependencyNode child : node.getChildren() )
+        {
+            Artifact childArtifact = getDependencyArtifact( child.getDependency() );
+
+            if ( ( filter == null ) || filter.include( childArtifact ) )
+            {
+                nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
+            }
+        }
+
+        current.setChildren( Collections.unmodifiableList( nodes ) );
+
+        return current;
+    }
+
+    private String getVersionSelectedFromRange( VersionConstraint constraint )
+    {
+        if ( ( constraint == null ) || ( constraint.getVersion() != null ) || ( constraint.getRanges().isEmpty() ) )
+        {
+            return null;
+        }
+
+        return constraint.getRanges().iterator().next().toString();
+    }
+
+}