Feature: Updated class 'JavaDependencyMediator' to become a very simple but complete replacement for the 'ConflictResolver'. In addition to the nearest node wins strategy, this implementation supports scope priorities. Currently the priorities are hard-coded to (from highest to lowest): system, compile, runtime, test, provided. This may be changed in the future to allow for different priorities based on the target classpath to transform the graph to. So that when transforming to a test classpath the test scope gets higher priority than the compile scope. See MNG-5988.
Project: http://git-wip-us.apache.org/repos/asf/maven-aether/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-aether/commit/5b69d98e Tree: http://git-wip-us.apache.org/repos/asf/maven-aether/tree/5b69d98e Diff: http://git-wip-us.apache.org/repos/asf/maven-aether/diff/5b69d98e Branch: refs/heads/master Commit: 5b69d98e7bce7e49e1af031db59990f22d91526b Parents: 0a82c39 Author: Christian Schulte <schu...@apache.org> Authored: Sat Jul 2 17:14:33 2016 +0200 Committer: Christian Schulte <schu...@apache.org> Committed: Sat Jul 2 17:23:50 2016 +0200 ---------------------------------------------------------------------- .../transformer/JavaDependencyMediator.java | 132 +++++++++++++++++-- 1 file changed, 118 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-aether/blob/5b69d98e/aether-util/src/main/java/org/eclipse/aether/util/graph/transformer/JavaDependencyMediator.java ---------------------------------------------------------------------- diff --git a/aether-util/src/main/java/org/eclipse/aether/util/graph/transformer/JavaDependencyMediator.java b/aether-util/src/main/java/org/eclipse/aether/util/graph/transformer/JavaDependencyMediator.java index ae385e6..626fc5d 100644 --- a/aether-util/src/main/java/org/eclipse/aether/util/graph/transformer/JavaDependencyMediator.java +++ b/aether-util/src/main/java/org/eclipse/aether/util/graph/transformer/JavaDependencyMediator.java @@ -19,7 +19,10 @@ package org.eclipse.aether.util.graph.transformer; * under the License. */ +import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import org.eclipse.aether.RepositoryException; import org.eclipse.aether.collection.DependencyGraphTransformationContext; @@ -43,10 +46,14 @@ public final class JavaDependencyMediator final DependencyGraphTransformationContext context ) throws RepositoryException { - return this.recurse( node ); + DependencyNode result = node; + result = this.removeNonTransitiveNodes( result ); + result = this.updateTransitiveScopes( result ); + result = this.removeDuplicateNodes( result, new HashMap<ConflictMarker.Key, DependencyNode>( 1024 ) ); + return result; } - private DependencyNode recurse( final DependencyNode parent ) + private DependencyNode removeNonTransitiveNodes( final DependencyNode parent ) { final String parentScope = parent.getDependency() != null ? parent.getDependency().getScope() != null @@ -58,8 +65,45 @@ public final class JavaDependencyMediator for ( final Iterator<DependencyNode> it = parent.getChildren().iterator(); it.hasNext(); ) { final DependencyNode child = it.next(); - boolean removed = false; + recurse: + { + if ( parentScope != null ) + { + String childScope = child.getDependency().getScope() != null + && child.getDependency().getScope().length() >= 0 + ? child.getDependency().getScope() + : JavaScopes.COMPILE; + + // Provided and test scopes are non-transitive. + // Optional dependencies are non-transitive. + if ( JavaScopes.PROVIDED.equals( childScope ) + || JavaScopes.TEST.equals( childScope ) + || child.getDependency().isOptional() ) + { + it.remove(); + break recurse; + } + } + + this.removeNonTransitiveNodes( child ); + } + } + + return parent; + } + + private DependencyNode updateTransitiveScopes( final DependencyNode parent ) + { + final String parentScope = parent.getDependency() != null + ? parent.getDependency().getScope() != null + && parent.getDependency().getScope().length() >= 0 + ? parent.getDependency().getScope() + : JavaScopes.COMPILE + : null; + + for ( final DependencyNode child : parent.getChildren() ) + { if ( parentScope != null ) { String childScope = child.getDependency().getScope() != null @@ -79,7 +123,7 @@ public final class JavaDependencyMediator || JavaScopes.RUNTIME.equals( childScope ) ) { childScope = JavaScopes.PROVIDED; - child.getDependency().setScope( childScope ); + child.setScope( childScope ); } } else if ( JavaScopes.RUNTIME.equals( parentScope ) ) @@ -88,7 +132,7 @@ public final class JavaDependencyMediator if ( JavaScopes.COMPILE.equals( childScope ) ) { childScope = JavaScopes.RUNTIME; - child.getDependency().setScope( childScope ); + child.setScope( childScope ); } } else if ( JavaScopes.TEST.equals( parentScope ) ) @@ -98,27 +142,87 @@ public final class JavaDependencyMediator || JavaScopes.RUNTIME.equals( childScope ) ) { childScope = JavaScopes.TEST; - child.getDependency().setScope( childScope ); + child.setScope( childScope ); } } } + } + + this.updateTransitiveScopes( child ); + } - // Provided and test scopes are non-transitive. - if ( JavaScopes.PROVIDED.equals( childScope ) - || JavaScopes.TEST.equals( childScope ) ) + return parent; + } + + private DependencyNode removeDuplicateNodes( final DependencyNode candidate, + final Map<ConflictMarker.Key, DependencyNode> nodes ) + { + recurse: + { + if ( candidate.getDependency() != null ) + { + final ConflictMarker.Key candidateKey = new ConflictMarker.Key( candidate.getArtifact() ); + final DependencyNode existing = nodes.get( candidateKey ); + + if ( existing == null ) { - it.remove(); - removed = true; + // Candidate is selected. + nodes.put( candidateKey, candidate ); + } + else if ( this.isPreferredNode( existing, candidate ) ) + { + // Candidate is selected. + nodes.put( candidateKey, candidate ); + existing.getParent().getChildren().remove( existing ); + } + else + { + // Candidate is not selected. + candidate.getParent().getChildren().remove( candidate ); + // No need to inspect children. + break recurse; } } - if ( !removed ) + for ( final DependencyNode child : new ArrayList<DependencyNode>( candidate.getChildren() ) ) { - this.recurse( child ); + this.removeDuplicateNodes( child, nodes ); } } - return parent; + return candidate; + } + + private boolean isPreferredNode( final DependencyNode existing, final DependencyNode candidate ) + { + boolean preferred = false; + final Integer p1 = SCOPE_PRIORITIES.get( existing.getDependency().getScope() ); + final Integer p2 = SCOPE_PRIORITIES.get( candidate.getDependency().getScope() ); + final boolean candidateScopePrioritized = p1 != null && p2 != null ? p2 > p1 : false; + final boolean equalPriority = existing.getDependency().getScope(). + equals( candidate.getDependency().getScope() ); + + if ( candidate.getDepth() < existing.getDepth() ) + { + preferred = equalPriority || candidateScopePrioritized; + } + else if ( candidate.getDepth() == existing.getDepth() ) + { + preferred = !equalPriority && candidateScopePrioritized; + } + + return preferred; + } + + private static final Map<String, Integer> SCOPE_PRIORITIES = new HashMap<String, Integer>(); + + static + { + SCOPE_PRIORITIES.put( JavaScopes.PROVIDED, 0 ); + SCOPE_PRIORITIES.put( JavaScopes.TEST, 0 ); + SCOPE_PRIORITIES.put( JavaScopes.RUNTIME, 1 ); + SCOPE_PRIORITIES.put( JavaScopes.COMPILE, 2 ); + SCOPE_PRIORITIES.put( JavaScopes.SYSTEM, 3 ); } }