Repository: maven-aether Updated Branches: refs/heads/master 5b69d98e7 -> 468240b70
Feature: Updated class 'JavaDependencyMediator' to support different scope prioritization strategies. Using 'NO_PRIORITIZATION', the class should behave the same way the 'ConflictResolver' does but some edge cases not sure about what the correct behaviour is or should be and which can be solved by enabling scope prioritization. Version ranges also are not supported yet. Project: http://git-wip-us.apache.org/repos/asf/maven-aether/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-aether/commit/468240b7 Tree: http://git-wip-us.apache.org/repos/asf/maven-aether/tree/468240b7 Diff: http://git-wip-us.apache.org/repos/asf/maven-aether/diff/468240b7 Branch: refs/heads/master Commit: 468240b7060c80268f75c49b121e9a63ca8d90da Parents: 5b69d98 Author: Christian Schulte <schu...@apache.org> Authored: Wed Jul 6 15:06:09 2016 +0200 Committer: Christian Schulte <schu...@apache.org> Committed: Wed Jul 6 15:32:42 2016 +0200 ---------------------------------------------------------------------- .../transformer/JavaDependencyMediator.java | 229 ++++++++++++++++--- 1 file changed, 195 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-aether/blob/468240b7/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 626fc5d..441b6df 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 @@ -20,6 +20,7 @@ package org.eclipse.aether.util.graph.transformer; */ import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -35,12 +36,66 @@ import org.eclipse.aether.util.artifact.JavaScopes; * * @author Christian Schulte * @since 1.2 - * @see <a href="http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">Introduction to the Dependency Mechanism</a> + * @see <a href="http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html">Introduction to the Dependency Mechanism</a> */ public final class JavaDependencyMediator implements DependencyGraphTransformer { + private static final Map<String, Integer> APPLICATION_SCOPE_PRIORITIES = new HashMap<String, Integer>( 5 ); + + private static final Map<String, Integer> TEST_SCOPE_PRIORITIES = new HashMap<String, Integer>( 5 ); + + static + { + APPLICATION_SCOPE_PRIORITIES.put( JavaScopes.TEST, 0 ); + APPLICATION_SCOPE_PRIORITIES.put( JavaScopes.RUNTIME, 1 ); + APPLICATION_SCOPE_PRIORITIES.put( JavaScopes.PROVIDED, 2 ); + APPLICATION_SCOPE_PRIORITIES.put( JavaScopes.COMPILE, 3 ); + APPLICATION_SCOPE_PRIORITIES.put( JavaScopes.SYSTEM, 4 ); + + TEST_SCOPE_PRIORITIES.put( JavaScopes.RUNTIME, 0 ); + TEST_SCOPE_PRIORITIES.put( JavaScopes.PROVIDED, 1 ); + TEST_SCOPE_PRIORITIES.put( JavaScopes.COMPILE, 2 ); + TEST_SCOPE_PRIORITIES.put( JavaScopes.TEST, 3 ); + TEST_SCOPE_PRIORITIES.put( JavaScopes.SYSTEM, 4 ); + } + + /** + * Application scope nodes are prioritized over non application scope nodes. + */ + public static final int APPLICATION_SCOPE_PRIORITIZATION = 1 << 1; + + /** + * Test scope nodes are prioritized over non test scope nodes. + */ + public static final int TEST_SCOPE_PRIORITIZATION = 1 << 2; + + /** + * Nearest wins only strategy. No scopes are prioritized. + */ + public static final int NO_PRIORITIZATION = 1 << 3; + + /** + * The prioritization to apply. + */ + private final int prioritization; + + /** + * Creates a new {@code DependencyGraphTransformer}. + * + * @param prioritization The prioritization to apply. + * + * @see #APPLICATION_SCOPE_PRIORITIZATION + * @see #TEST_SCOPE_PRIORITIZATION + * @see #NO_PRIORITIZATION + */ + public JavaDependencyMediator( final int prioritization ) + { + super(); + this.prioritization = prioritization; + } + @Override public DependencyNode transformGraph( final DependencyNode node, final DependencyGraphTransformationContext context ) @@ -49,7 +104,16 @@ public final class JavaDependencyMediator DependencyNode result = node; result = this.removeNonTransitiveNodes( result ); result = this.updateTransitiveScopes( result ); - result = this.removeDuplicateNodes( result, new HashMap<ConflictMarker.Key, DependencyNode>( 1024 ) ); + + for ( ;; ) + { + if ( this.removeDuplicateNodes( result, result, new HashMap<ConflictMarker.Key, DependencyNode>( 8192 ), + new HashMap<DependencyNode, DependencyNode>( 8192 ) ) ) + { + break; + } + } + return result; } @@ -154,75 +218,172 @@ public final class JavaDependencyMediator return parent; } - private DependencyNode removeDuplicateNodes( final DependencyNode candidate, - final Map<ConflictMarker.Key, DependencyNode> nodes ) + private boolean removeDuplicateNodes( final DependencyNode rootNode, + final DependencyNode candidateNode, + final Map<ConflictMarker.Key, DependencyNode> winnerNodes, + final Map<DependencyNode, DependencyNode> looserNodes ) { + boolean restart = false; + recurse: { - if ( candidate.getDependency() != null ) + if ( candidateNode.getDependency() != null ) { - final ConflictMarker.Key candidateKey = new ConflictMarker.Key( candidate.getArtifact() ); - final DependencyNode existing = nodes.get( candidateKey ); + final ConflictMarker.Key candidateKey = new ConflictMarker.Key( candidateNode.getArtifact() ); + final DependencyNode winnerNode = winnerNodes.get( candidateKey ); - if ( existing == null ) + if ( winnerNode == null ) { - // Candidate is selected. - nodes.put( candidateKey, candidate ); + // Conflict not yet seen. Candidate is selected. + winnerNodes.put( candidateKey, candidateNode ); } - else if ( this.isPreferredNode( existing, candidate ) ) + else if ( this.isPreferredNode( winnerNode, candidateNode ) ) { - // Candidate is selected. - nodes.put( candidateKey, candidate ); - existing.getParent().getChildren().remove( existing ); + // Conflict already seen. Candidate is preferred. + winnerNodes.put( candidateKey, candidateNode ); + looserNodes.put( candidateNode, winnerNode ); + + if ( winnerNode.getParent() != null ) + { + winnerNode.getParent().getChildren().remove( winnerNode ); + } + else + { + rootNode.getChildren().remove( winnerNode ); + } + + final DependencyNode winningChild = getWinningChild( winnerNode, winnerNodes.values() ); + + if ( winningChild != null ) + { + // The node eliminated by the current candidate node contains a child node which has been + // selected the winner in a previous iteration. As that winner is eliminated in this iteration, + // the former looser needs to be re-added and the whole transformation re-started (undo and + // restart). No need to maintain the maps here because they are thrown away when restarting. + // Doing it for completeness, however. + final DependencyNode looserNode = looserNodes.remove( winningChild ); // Can be get(). + + if ( looserNode != null ) + { + if ( looserNode.getParent() != null ) + { + if ( !looserNode.getParent().getChildren().contains( looserNode ) ) + { + looserNode.getParent().getChildren().add( looserNode ); + } + } + else if ( !rootNode.getChildren().contains( looserNode ) ) + { + rootNode.getChildren().add( looserNode ); + } + + // Not needed, but... + final DependencyNode winner = + winnerNodes.remove( new ConflictMarker.Key( looserNode.getArtifact() ) ); + + if ( winner != null ) + { + looserNodes.remove( winner ); + } + } + + restart = true; + break recurse; + } } else { - // Candidate is not selected. - candidate.getParent().getChildren().remove( candidate ); + // Conflict already seen. Candidate is not preferred. + looserNodes.put( winnerNode, candidateNode ); + if ( candidateNode.getParent() != null ) + { + candidateNode.getParent().getChildren().remove( candidateNode ); + } + else + { + rootNode.getChildren().remove( candidateNode ); + } // No need to inspect children. break recurse; } } - for ( final DependencyNode child : new ArrayList<DependencyNode>( candidate.getChildren() ) ) + for ( final DependencyNode child : new ArrayList<DependencyNode>( candidateNode.getChildren() ) ) { - this.removeDuplicateNodes( child, nodes ); + if ( !this.removeDuplicateNodes( rootNode, child, winnerNodes, looserNodes ) ) + { + restart = true; + break recurse; + } } } - return candidate; + return !restart; } 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() ); + Integer p1 = null; + Integer p2 = null; + boolean prioritize = true; + + if ( this.prioritization == APPLICATION_SCOPE_PRIORITIZATION ) + { + p1 = APPLICATION_SCOPE_PRIORITIES.get( existing.getDependency().getScope() ); + p2 = APPLICATION_SCOPE_PRIORITIES.get( candidate.getDependency().getScope() ); + } + else if ( this.prioritization == TEST_SCOPE_PRIORITIZATION ) + { + p1 = TEST_SCOPE_PRIORITIES.get( existing.getDependency().getScope() ); + p2 = TEST_SCOPE_PRIORITIES.get( candidate.getDependency().getScope() ); + } + else if ( this.prioritization == NO_PRIORITIZATION ) + { + prioritize = false; + } + else + { + throw new AssertionError( this.prioritization ); + } + + 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; + preferred = !prioritize || equalPriority || candidateScopePrioritized; } else if ( candidate.getDepth() == existing.getDepth() ) { - preferred = !equalPriority && candidateScopePrioritized; + preferred = prioritize && !equalPriority && candidateScopePrioritized; } return preferred; } - private static final Map<String, Integer> SCOPE_PRIORITIES = new HashMap<String, Integer>(); - - static + private static DependencyNode getWinningChild( final DependencyNode node, + final Collection<DependencyNode> winnerNodes ) { - 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 ); + DependencyNode winningChild = winnerNodes.contains( node ) + ? node + : null; + + if ( winningChild == null ) + { + for ( final DependencyNode child : node.getChildren() ) + { + winningChild = getWinningChild( child, winnerNodes ); + + if ( winningChild != null ) + { + break; + } + } + } + + return winningChild; } }