package org.eclipse.aether.util.graph.transformer;

/*
 * 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 static java.util.Objects.requireNonNull;

import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.DependencyGraphTransformationContext;
import org.eclipse.aether.collection.DependencyGraphTransformer;
import org.eclipse.aether.graph.DefaultDependencyNode;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.util.ConfigUtils;

A dependency graph transformer that resolves version and scope conflicts among dependencies. For a given set of conflicting nodes, one node will be chosen as the winner and the other nodes are removed from the dependency graph. The exact rules by which a winning node and its effective scope are determined are controlled by user-supplied implementations of VersionSelector, ScopeSelector, OptionalitySelector and ScopeDeriver.

By default, this graph transformer will turn the dependency graph into a tree without duplicate artifacts. Using the configuration property CONFIG_PROP_VERBOSE, a verbose mode can be enabled where the graph is still turned into a tree but all nodes participating in a conflict are retained. The nodes that were rejected during conflict resolution have no children and link back to the winner node via the NODE_DATA_WINNER key in their custom data. Additionally, the keys NODE_DATA_ORIGINAL_SCOPE and NODE_DATA_ORIGINAL_OPTIONALITY are used to store the original scope and optionality of each node. Obviously, the resulting dependency tree is not suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies.

This transformer will query the keys TransformationContextKeys.CONFLICT_IDS, TransformationContextKeys.SORTED_CONFLICT_IDS, TransformationContextKeys.CYCLIC_CONFLICT_IDS for existing information about conflict ids. In absence of this information, it will automatically invoke the ConflictIdSorter to calculate it.

/** * A dependency graph transformer that resolves version and scope conflicts among dependencies. For a given set of * conflicting nodes, one node will be chosen as the winner and the other nodes are removed from the dependency graph. * The exact rules by which a winning node and its effective scope are determined are controlled by user-supplied * implementations of {@link VersionSelector}, {@link ScopeSelector}, {@link OptionalitySelector} and * {@link ScopeDeriver}. * <p> * By default, this graph transformer will turn the dependency graph into a tree without duplicate artifacts. Using the * configuration property {@link #CONFIG_PROP_VERBOSE}, a verbose mode can be enabled where the graph is still turned * into a tree but all nodes participating in a conflict are retained. The nodes that were rejected during conflict * resolution have no children and link back to the winner node via the {@link #NODE_DATA_WINNER} key in their custom * data. Additionally, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used * to store the original scope and optionality of each node. Obviously, the resulting dependency tree is not suitable * for artifact resolution unless a filter is employed to exclude the duplicate dependencies. * <p> * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS}, * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for * existing information about conflict ids. In absence of this information, it will automatically invoke the * {@link ConflictIdSorter} to calculate it. */
public final class ConflictResolver implements DependencyGraphTransformer {
The key in the repository session's configuration properties used to store a Boolean flag controlling the transformer's verbose mode.
/** * The key in the repository session's {@link org.eclipse.aether.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 custom data under which a reference to the DependencyNode which has won the conflict is stored.
/** * 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 custom data under which the scope of the dependency before scope derivation and conflict resolution is stored.
/** * 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 custom data under which the optional flag of the dependency before derivation and conflict resolution is stored.
/** * 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.
Params:
  • versionSelector – The version selector to use, must not be null.
  • scopeSelector – The scope selector to use, must not be null.
  • optionalitySelector – The optionality selector ot use, must not be null.
  • scopeDeriver – The scope deriver to use, must not be null.
/** * 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 ) { this.versionSelector = requireNonNull( versionSelector, "version selector cannot be null" ); this.scopeSelector = requireNonNull( scopeSelector, "scope selector cannot be null" ); this.optionalitySelector = requireNonNull( optionalitySelector, "optionality selector cannot be null" ); this.scopeDeriver = requireNonNull( scopeDeriver, "scope deriver cannot be null" ); } 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.nanoTime(); @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<>(); for ( Collection<?> cycle : conflictIdCycles ) { for ( Object conflictId : cycle ) { Collection<Object> predecessors = cyclicPredecessors.get( conflictId ); if ( predecessors == null ) { predecessors = new HashSet<>(); 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.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.nanoTime(); 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 } static final class NodeInfo {
The smallest depth at which the node was seen, used for "the" depth of its conflict items.
/** * 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 String and update to Set<String> if needed.
/** * 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).
/** * 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.
/** * 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<>(); 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<>( 1 ); } children.add( item ); } } final class State {
The conflict id currently processed.
/** * The conflict id currently processed. */
Object currentId;
Stats counter.
/** * Stats counter. */
int totalConflictItems;
Flag whether we should keep losers in the graph to enable visualization/troubleshooting of conflicts.
/** * 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.
/** * 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.
/** * 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
/** * The output from the conflict marker */
final Map<?, ?> conflictIds;
The conflict items we have gathered so far for the current conflict id.
/** * 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.
/** * 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.
/** * 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.
/** * The stack of parent nodes. */
final List<DependencyNode> parentNodes;
The stack of derived scopes for parent nodes.
/** * The stack of derived scopes for parent nodes. */
final List<String> parentScopes;
The stack of derived optional flags for parent nodes.
/** * The stack of derived optional flags for parent nodes. */
final List<Boolean> parentOptionals;
The stack of node infos for parent nodes, may contain 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).
/** * 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.
/** * 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.
/** * 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.
/** * The effective version selector, i.e. after initialization. */
final VersionSelector versionSelector;
The effective scope selector, i.e. after initialization.
/** * The effective scope selector, i.e. after initialization. */
final ScopeSelector scopeSelector;
The effective scope deriver, i.e. after initialization.
/** * The effective scope deriver, i.e. after initialization. */
final ScopeDeriver scopeDeriver;
The effective optionality selector, i.e. after initialization.
/** * 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<>( conflictIdCount * 2 ); resolvedIds = new HashMap<>( conflictIdCount * 2 ); items = new ArrayList<>( 256 ); infos = new IdentityHashMap<>( 64 ); stack = new IdentityHashMap<>( 64 ); parentNodes = new ArrayList<>( 64 ); parentScopes = new ArrayList<>( 64 ); parentOptionals = new ArrayList<>( 64 ); parentInfos = new ArrayList<>( 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.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 = scope( child ); } 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(); 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 Also:
  • ScopeDeriver
@noinstantiateThis 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.
/** * 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.
Params:
  • parentScope – The scope of the parent dependency, may be null.
  • childScope – The scope of the child dependency, may be null.
@noreferenceThis 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.
/** * 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 = ( childScope != null ) ? childScope : ""; }
Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of the scope deriver.
Returns:The scope of the parent dependency, never null.
/** * 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.
Returns:The original scope of the child dependency, never null.
/** * 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 getChildScope() until the scope deriver makes changes.
Returns:The derived scope of the child dependency, never null.
/** * 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.
Params:
  • derivedScope – The derived scope of the dependency, may be null.
/** * 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.
@noinstantiateThis 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.
/** * 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.
/** * 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.
/** * 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.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.
Params:
  • parent – The parent node of the conflicting dependency, may be null.
  • node – The conflicting dependency, must not be null.
  • depth – The zero-based depth of the conflicting dependency.
  • optionalities – The optionalities the dependency was encountered with, encoded as a bit field consisting of OPTIONAL_TRUE and OPTIONAL_FALSE.
  • scopes – The derived scopes of the conflicting dependency, must not be null.
@noreferenceThis 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.
/** * 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.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.
Params:
  • item – The other conflict item, must not be null.
Returns:true if the given item has the same parent as this item, false otherwise.
/** * 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.
Returns:The involved dependency node, never null.
/** * 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 getNode.getDependency().
Returns:The involved dependency, never null.
/** * 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.
Returns:The zero-based depth of the node in the graph.
/** * 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 Also:
  • ScopeDeriver
Returns:The (read-only) set of derived scopes of the dependency, never null.
/** * 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<>(); 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.
Returns:A bit field consisting of OPTIONAL_FALSE and/or OPTIONAL_TRUE indicating the derived optionalities the dependency was encountered with.
/** * 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 Also:
@noinstantiateThis 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.
/** * 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.
Params:
  • root – The root node of the dependency graph, must not be null.
  • conflictId – The conflict id for the set of conflicting dependencies in this context, must not be null.
  • conflictIds – The mapping from dependency node to conflict id, must not be null.
  • items – The conflict items in this context, must not be null.
@noreferenceThis 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.
/** * 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.
Returns:The root node of the dependeny graph, never null.
/** * 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.
Params:
  • node – The dependency node to check, must not be null.
Returns:true if the given node belongs to this conflict context, false otherwise.
/** * 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.
Returns:The (read-only) collection of conflict items in this context, never null.
/** * 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.
Returns:The winning conflict item or null if not set yet.
/** * 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.
Params:
  • winner – The winning conflict item, may be null.
/** * 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.
Returns:The effective scope of the winning dependency or null if none.
/** * 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.
Params:
  • scope – The effective scope, may be null.
/** * 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.
Returns:The effective optional flag or null if none.
/** * 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.
Params:
  • optional – The effective optional flag, may be null.
/** * 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 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 ScopeSelector.

Note: Implementations must be stateless.

/** * 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}. * <p> * <strong>Note:</strong> 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 ConflictResolver.transformGraph(DependencyNode, DependencyGraphTransformationContext) invocation to allow implementations to prepare any auxiliary data that is needed for their operation. Given that implementations must 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.
Params:
  • root – The root node of the (possibly cyclic!) graph to transform, must not be null.
  • context – The graph transformation context, must not be null.
Throws:
Returns:The scope deriver to use for the given graph transformation, never null.
/** * 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 must 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 ConflictContext.getItems(), inspect ConflictItem.getNode() and eventually call ConflictContext.setWinner(ConflictItem) to deliver the winner. Failure to select a winner will automatically fail the entire conflict resolution.
Params:
  • context – The conflict context, must not be null.
Throws:
/** * 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 ConflictResolver that determines the effective scope of a dependency from a potentially conflicting set of derived scopes. The scope selector gets invoked after the VersionSelector has picked the winning node.

Note: Implementations must be stateless.

/** * 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. * <p> * <strong>Note:</strong> 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 ConflictResolver.transformGraph(DependencyNode, DependencyGraphTransformationContext) invocation to allow implementations to prepare any auxiliary data that is needed for their operation. Given that implementations must 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.
Params:
  • root – The root node of the (possibly cyclic!) graph to transform, must not be null.
  • context – The graph transformation context, must not be null.
Throws:
Returns:The scope selector to use for the given graph transformation, never null.
/** * 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 must 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 ConflictContext.getWinner(). Implementations will usually iterate ConflictContext.getItems(), inspect ConflictItem.getScopes() and eventually call ConflictContext.setScope(String) to deliver the effective scope.
Params:
  • context – The conflict context, must not be null.
Throws:
/** * 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 ConflictResolver that determines the scope of a dependency in relation to the scope of its parent.

Note: Implementations must be stateless.

/** * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope * of its parent. * <p> * <strong>Note:</strong> 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 ConflictResolver.transformGraph(DependencyNode, DependencyGraphTransformationContext) invocation to allow implementations to prepare any auxiliary data that is needed for their operation. Given that implementations must 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.
Params:
  • root – The root node of the (possibly cyclic!) graph to transform, must not be null.
  • context – The graph transformation context, must not be null.
Throws:
Returns:The scope deriver to use for the given graph transformation, never null.
/** * 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 must 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 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.
Params:
  • context – The scope context, must not be null.
Throws:
/** * 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 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 VersionSelector has picked the winning node.

Note: Implementations must be stateless.

/** * 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. * <p> * <strong>Note:</strong> 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 ConflictResolver.transformGraph(DependencyNode, DependencyGraphTransformationContext) invocation to allow implementations to prepare any auxiliary data that is needed for their operation. Given that implementations must 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.
Params:
  • root – The root node of the (possibly cyclic!) graph to transform, must not be null.
  • context – The graph transformation context, must not be null.
Throws:
Returns:The optionality selector to use for the given graph transformation, never null.
/** * 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 must 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 ConflictContext.getWinner(). Implementations will usually iterate ConflictContext.getItems(), inspect ConflictItem.getOptionalities() and eventually call ConflictContext.setOptional(Boolean) to deliver the effective optional flag.
Params:
  • context – The conflict context, must not be null.
Throws:
/** * 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; } }