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 12:04:30 UTC

[maven-dependency-tree] branch master updated: [MSHARED-788] Add functionality to collect raw dependencies in Maven 3+

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7e225ab  [MSHARED-788] Add functionality to collect raw dependencies in Maven 3+
7e225ab is described below

commit 7e225abf11faf1a0114d735b9aa2ca9f0669c694
Author: Gabriel Belingueres <be...@gmail.com>
AuthorDate: Sat May 22 14:04:15 2021 +0200

    [MSHARED-788] Add functionality to collect raw dependencies in Maven 3+
    
    Signed-off-by: rfscholte <rf...@apache.org>
---
 .../graph/DependencyCollectorBuilder.java          |   50 +
 .../graph/DependencyCollectorBuilderException.java |   54 +
 .../DefaultDependencyCollectorBuilder.java         |  116 ++
 .../graph/internal/ExceptionHandler.java           |   30 +
 .../shared/dependency/graph/internal/Invoker.java  |   62 +-
 .../Maven31DependencyCollectorBuilder.java         |  310 +++++
 .../internal/Maven31DependencyGraphBuilder.java    |   20 +-
 .../internal/Maven3DependencyCollectorBuilder.java |  316 +++++
 .../internal/Maven3DependencyGraphBuilder.java     |    2 +-
 .../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 ++
 18 files changed, 3522 insertions(+), 39 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
new file mode 100644
index 0000000..43cfb40
--- /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.1.0
+ */
+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 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
new file mode 100644
index 0000000..e29cc9d
--- /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.DependencyCollectorBuilderException;
+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.1.0
+ */
+@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 DependencyCollectorBuilderException
+    {
+        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 DependencyCollectorBuilderException( 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/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 f591973..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 )
+        catch ( ReflectiveOperationException 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( Object object, String method, Class<?> clazz, Object arg )
+    static Object invoke( Object object, String method, Class<?> clazz, Object arg )
         throws DependencyGraphBuilderException
     {
         try
@@ -68,38 +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 );
+            throw exceptionHandler.create( e.getMessage(), e );
         }
-        catch ( InvocationTargetException e )
+    }
+
+    static <T extends Exception> Object invoke( Class<?> objectClazz, String staticMethod, Class<?> argClazz1,
+                                                Class<?> argClazz2, Object arg1, Object arg2,
+                                                ExceptionHandler<T> exceptionHandler )
+        throws T
+    {
+        try
         {
-            throw new DependencyGraphBuilderException( e.getMessage(), e );
+            return objectClazz.getMethod( staticMethod, argClazz1, argClazz2 ).invoke( null, arg1, arg2 );
         }
-        catch ( NoSuchMethodException e )
+        catch ( ReflectiveOperationException 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
new file mode 100644
index 0000000..15c173d
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven31DependencyCollectorBuilder.java
@@ -0,0 +1,310 @@
+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.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;
+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.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;
+import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+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.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.1.0
+ */
+@Component( role = DependencyCollectorBuilder.class, hint = "maven31" )
+public class Maven31DependencyCollectorBuilder
+    extends AbstractLogEnabled
+    implements DependencyCollectorBuilder
+{
+    @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 DependencyCollectorBuilderException
+    {
+        DefaultRepositorySystemSession session = null;
+        try
+        {
+            MavenProject project = buildingRequest.getProject();
+
+            Artifact projectArtifact = project.getArtifact();
+            List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
+
+            DefaultRepositorySystemSession repositorySession =
+                (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession",
+                                                                 exceptionHandler );
+
+            session = new DefaultRepositorySystemSession( repositorySession );
+
+            DependencyGraphTransformer transformer =
+                new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
+                                      new SimpleOptionalitySelector(), new JavaScopeDeriver() );
+            session.setDependencyGraphTransformer( transformer );
+
+            DependencySelector depFilter =
+                new AndDependencySelector( new Maven31DirectScopeDependencySelector( JavaScopes.TEST ),
+                                           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,
+                                                                       exceptionHandler );
+
+            @SuppressWarnings( "unchecked" )
+            List<org.eclipse.aether.repository.RemoteRepository> aetherRepos =
+                (List<org.eclipse.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos",
+                                                                                       List.class,
+                                                                                       remoteArtifactRepositories,
+                                                                                       exceptionHandler );
+
+            CollectRequest collectRequest = new CollectRequest();
+            collectRequest.setRoot( new org.eclipse.aether.graph.Dependency( aetherArtifact, "" ) );
+            collectRequest.setRepositories( aetherRepos );
+
+            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();
+
+            if ( getLogger().isDebugEnabled() )
+            {
+                logTree( rootNode );
+            }
+
+            return buildDependencyNode( null, rootNode, projectArtifact, filter );
+        }
+        catch ( DependencyCollectionException e )
+        {
+            throw new DependencyCollectorBuilderException( "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 DependencyCollectorBuilderException
+    {
+        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 DependencyCollectorBuilderException
+    {
+        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 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, exceptionHandler );
+        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, exceptionHandler );
+
+            mavenArtifact.setScope( dep.getScope() );
+            mavenArtifact.setOptional( dep.isOptional() );
+
+            return mavenArtifact;
+        }
+        catch ( DependencyCollectorBuilderException 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();
+        }
+        
+        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,
+                                       exclusions );
+
+        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/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
new file mode 100644
index 0000000..24c0d65
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/graph/internal/Maven3DependencyCollectorBuilder.java
@@ -0,0 +1,316 @@
+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.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;
+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;
+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.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;
+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.version.VersionConstraint;
+
+/**
+ * Project dependency raw dependency collector API, abstracting Maven 3's Aether implementation.
+ * 
+ * @author Gabriel Belingueres
+ * @since 3.1.0
+ */
+@Component( role = DependencyCollectorBuilder.class, hint = "maven3" )
+public class Maven3DependencyCollectorBuilder
+    extends AbstractLogEnabled
+    implements DependencyCollectorBuilder
+{
+    @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 DependencyCollectorBuilderException
+    {
+        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 );
+
+            DependencyGraphTransformer transformer =
+                new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
+                                      new SimpleOptionalitySelector(), new JavaScopeDeriver() );
+            session.setDependencyGraphTransformer( transformer );
+
+            DependencySelector depFilter =
+                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,
+                                                                        exceptionHandler );
+
+            @SuppressWarnings( "unchecked" )
+            List<org.sonatype.aether.repository.RemoteRepository> aetherRepos =
+                (List<org.sonatype.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class,
+                                                                                        "toRepos", List.class,
+                                                                                        remoteArtifactRepositories,
+                                                                                        exceptionHandler );
+
+            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();
+
+            if ( getLogger().isDebugEnabled() )
+            {
+                logTree( rootNode );
+            }
+
+            return buildDependencyNode( null, rootNode, projectArtifact, filter );
+        }
+        catch ( DependencyCollectionException e )
+        {
+            throw new DependencyCollectorBuilderException( "Could not collect dependencies: " + e.getResult(), e );
+        }
+    }
+
+    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 DependencyCollectorBuilderException
+    {
+        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 DependencyCollectorBuilderException
+    {
+        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 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, exceptionHandler );
+        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, exceptionHandler );
+
+            mavenArtifact.setScope( dep.getScope() );
+            mavenArtifact.setOptional( dep.isOptional() );
+
+            return mavenArtifact;
+        }
+        catch ( DependencyCollectorBuilderException 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();
+        }
+
+        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,
+                                       exclusions );
+
+        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();
+    }
+
+}
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
new file mode 100644
index 0000000..d686842
--- /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.1.0
+ */
+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..abbb36e
--- /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.1.0
+ */
+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..e9555f9
--- /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.1.0
+ */
+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..d35abf0
--- /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.1.0
+ */
+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..9a810e7
--- /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.1.0
+ */
+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..4776e6d
--- /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.1.0
+ */
+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..c793b96
--- /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.1.0
+ */
+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..d63df0b
--- /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.1.0
+ */
+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..95ee25c
--- /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.1.0
+ */
+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;
+    }
+
+}