View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.graph.transformer;
20  
21  import java.util.Arrays;
22  import java.util.Collection;
23  
24  import org.eclipse.aether.ConfigurationProperties;
25  import org.eclipse.aether.RepositoryException;
26  import org.eclipse.aether.RepositorySystemSession;
27  import org.eclipse.aether.collection.DependencyGraphTransformationContext;
28  import org.eclipse.aether.collection.DependencyGraphTransformer;
29  import org.eclipse.aether.graph.Dependency;
30  import org.eclipse.aether.graph.DependencyNode;
31  import org.eclipse.aether.util.ConfigUtils;
32  
33  import static java.util.Objects.requireNonNull;
34  
35  /**
36   * Abstract base class for dependency graph transformers that resolve version and scope conflicts among dependencies.
37   * For a given set of conflicting nodes, one node will be chosen as the winner. How losing nodes are handled depends
38   * on the configured verbosity level: they may be removed entirely, have their children removed, or be left in place
39   * with conflict information. The exact rules by which a winning node and its effective scope are determined are
40   * controlled by user-supplied implementations of {@link VersionSelector}, {@link ScopeSelector},
41   * {@link OptionalitySelector} and {@link ScopeDeriver}.
42   * <p>
43   * <strong>Available Implementations:</strong>
44   * <ul>
45   * <li><strong>{@link PathConflictResolver}</strong> - Recommended high-performance implementation with O(N) complexity</li>
46   * <li><strong>{@link ClassicConflictResolver}</strong> - Legacy implementation for backward compatibility (O(N²) worst-case)</li>
47   * </ul>
48   * <p>
49   * <strong>Implementation Selection Guide:</strong>
50   * <ul>
51   * <li><strong>New Projects:</strong> Use {@link PathConflictResolver} for optimal performance</li>
52   * <li><strong>Large Multi-Module Projects:</strong> Use {@link PathConflictResolver} to avoid performance bottlenecks</li>
53   * <li><strong>Maven 4+ Environments:</strong> Use {@link PathConflictResolver} for best build performance</li>
54   * <li><strong>Legacy Compatibility:</strong> Use {@link ClassicConflictResolver} only when exact Maven 3.x behavior is required</li>
55   * </ul>
56   * <p>
57   * <strong>Usage Example:</strong>
58   * <pre>{@code
59   * // Recommended: High-performance path-based resolver
60   * DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer(
61   *     new PathConflictResolver(
62   *         new NearestVersionSelector(),
63   *         new JavaScopeSelector(),
64   *         new SimpleOptionalitySelector(),
65   *         new JavaScopeDeriver()),
66   *     // other transformers...
67   * );
68   *
69   * // Legacy: Classic resolver for backward compatibility
70   * DependencyGraphTransformer legacyTransformer = new ChainedDependencyGraphTransformer(
71   *     new ClassicConflictResolver(
72   *         new NearestVersionSelector(),
73   *         new JavaScopeSelector(),
74   *         new SimpleOptionalitySelector(),
75   *         new JavaScopeDeriver()),
76   *     // other transformers...
77   * );
78   * }</pre>
79   * <p>
80   * <strong>Verbosity Levels and Conflict Handling:</strong>
81   * <ul>
82   * <li><strong>NONE (default):</strong> Creates a clean dependency tree without duplicate artifacts.
83   *     Losing nodes are completely removed from the graph, so are cycles as well.</li>
84   * <li><strong>STANDARD:</strong> Retains losing nodes for analysis but removes their children to prevent
85   *     duplicate dependencies. Special handling for version ranges: redundant nodes may still be removed
86   *     if multiple versions of the same artifact exist. Losing nodes link back to the winner via
87   *     {@link #NODE_DATA_WINNER} and preserve original scope/optionality information. This mode removes cycles only,
88   *     while conflict nodes/duplicates are left in place. Graphs in this verbosity level cannot be resolved,
89   *     their purpose is for analysis only.</li>
90   * <li><strong>FULL:</strong> Preserves the complete original graph structure including all conflicts and cycles.
91   *     All nodes remain with their children, but conflict information is recorded for analysis.
92   *     Graphs in this verbosity level cannot be resolved, their purpose is for analysis only.</li>
93   * </ul>
94   * The verbosity level is controlled by the {@link #CONFIG_PROP_VERBOSE} configuration property.
95   * <p>
96   * <strong>Conflict Metadata:</strong> In STANDARD and FULL modes, the keys {@link #NODE_DATA_ORIGINAL_SCOPE}
97   * and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used to store the original scope and optionality of each node.
98   * Obviously, dependency trees with verbosity STANDARD or FULL are not suitable for artifact resolution unless
99   * a filter is employed to exclude the duplicate dependencies.
100  * <p>
101  * <strong>Conflict ID Processing Pipeline:</strong>
102  * <ol>
103  * <li><strong>{@link ConflictMarker}:</strong> Assigns conflict IDs based on GACE (groupId:artifactId:classifier:extension)
104  *     coordinates, grouping artifacts that differ only in version (partitions the graph, assigning same conflict IDs
105  *     to nodes belonging to same conflict group).</li>
106  * <li><strong>{@link ConflictIdSorter}:</strong> Creates topological ordering of conflict IDs and detects cycles</li>
107  * <li><strong>ConflictResolver implementation:</strong> Uses the sorted conflict IDs to resolve conflicts in dependency order</li>
108  * </ol>
109  * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS},
110  * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for
111  * existing information about conflict ids. In absence of this information, it will automatically invoke the
112  * {@link ConflictIdSorter} to calculate it.
113  *
114  * @see PathConflictResolver
115  * @see ClassicConflictResolver
116  */
117 public class ConflictResolver implements DependencyGraphTransformer {
118 
119     /**
120      * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties()
121      * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode.
122      * Accepted values are Boolean types, String type (where "true" would be interpreted as {@code true})
123      * or Verbosity enum instances.
124      *
125      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
126      * @configurationType {@link java.lang.Object}
127      * @configurationDefaultValue "NONE"
128      */
129     public static final String CONFIG_PROP_VERBOSE = ConfigurationProperties.PREFIX_AETHER + "conflictResolver.verbose";
130 
131     /**
132      * The name of the conflict resolver implementation to use: "path" (default) or "classic" (same as Maven 3).
133      *
134      * @since 2.0.11
135      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
136      * @configurationType {@link java.lang.String}
137      * @configurationDefaultValue {@link #DEFAULT_CONFLICT_RESOLVER_IMPL}
138      */
139     public static final String CONFIG_PROP_CONFLICT_RESOLVER_IMPL =
140             ConfigurationProperties.PREFIX_AETHER + "conflictResolver.impl";
141 
142     public static final String CLASSIC_CONFLICT_RESOLVER = "classic";
143     public static final String PATH_CONFLICT_RESOLVER = "path";
144 
145     public static final String DEFAULT_CONFLICT_RESOLVER_IMPL = PATH_CONFLICT_RESOLVER;
146 
147     /**
148      * The enum representing verbosity levels of conflict resolver.
149      *
150      * @since 1.9.8
151      */
152     public enum Verbosity {
153         /**
154          * Verbosity level to be used in all "common" resolving use cases (ie dependencies to build class path). The
155          * {@link ConflictResolver} in this mode will trim down the graph to the barest minimum: will not leave
156          * any conflicting node in place, hence no conflicts will be present in transformed graph either.
157          */
158         NONE,
159 
160         /**
161          * Verbosity level to be used in "analyze" resolving use cases (ie dependency convergence calculations). The
162          * {@link ConflictResolver} in this mode will remove any redundant collected nodes and cycles, in turn it will
163          * leave one with recorded conflicting information. This mode corresponds to "classic verbose" mode when
164          * {@link #CONFIG_PROP_VERBOSE} was set to {@code true}. Obviously, the resulting dependency tree is not
165          * suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
166          */
167         STANDARD,
168 
169         /**
170          * Verbosity level to be used in "analyze" resolving use cases (ie dependency convergence calculations). The
171          * {@link ConflictResolver} in this mode will not remove any collected node nor cycle, in turn it will record
172          * on all eliminated nodes the conflicting information. Obviously, the resulting dependency tree is not suitable
173          * for artifact resolution unless a filter is employed to exclude the duplicate dependencies and possible cycles.
174          * Because of left in cycles, user of this verbosity level should ensure that graph post-processing does not
175          * contain elements that would explode on them. In other words, session should be modified with proper
176          * graph transformers.
177          *
178          * @see RepositorySystemSession#getDependencyGraphTransformer()
179          */
180         FULL
181     }
182 
183     /**
184      * Helper method that uses {@link RepositorySystemSession} and {@link #CONFIG_PROP_VERBOSE} key to figure out
185      * current {@link Verbosity}: if {@link Boolean} or {@code String} found, returns {@link Verbosity#STANDARD}
186      * or {@link Verbosity#NONE}, depending on value (string is parsed with {@link Boolean#parseBoolean(String)}
187      * for {@code true} or {@code false} correspondingly. This is to retain "existing" behavior, where the config
188      * key accepted only these values.
189      * Since 1.9.8 release, this key may contain {@link Verbosity} enum instance as well, in which case that instance
190      * is returned.
191      * This method never returns {@code null}.
192      */
193     public static Verbosity getVerbosity(RepositorySystemSession session) {
194         final Object verbosityValue = session.getConfigProperties().get(CONFIG_PROP_VERBOSE);
195         if (verbosityValue instanceof Boolean) {
196             return (Boolean) verbosityValue ? Verbosity.STANDARD : Verbosity.NONE;
197         } else if (verbosityValue instanceof String) {
198             return Boolean.parseBoolean(verbosityValue.toString()) ? Verbosity.STANDARD : Verbosity.NONE;
199         } else if (verbosityValue instanceof Verbosity) {
200             return (Verbosity) verbosityValue;
201         } else if (verbosityValue != null) {
202             throw new IllegalArgumentException("Unsupported Verbosity configuration: " + verbosityValue);
203         }
204         return Verbosity.NONE;
205     }
206 
207     /**
208      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the
209      * {@link DependencyNode} which has won the conflict is stored.
210      */
211     public static final String NODE_DATA_WINNER = "conflict.winner";
212 
213     /**
214      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the
215      * dependency before scope derivation and conflict resolution is stored.
216      */
217     public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope";
218 
219     /**
220      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of
221      * the dependency before derivation and conflict resolution is stored.
222      */
223     public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality";
224 
225     private final ConflictResolver.VersionSelector versionSelector;
226     private final ConflictResolver.ScopeSelector scopeSelector;
227     private final ConflictResolver.ScopeDeriver scopeDeriver;
228     private final ConflictResolver.OptionalitySelector optionalitySelector;
229 
230     /**
231      * No arg ctor for subclasses and default cases.
232      */
233     protected ConflictResolver() {
234         this.versionSelector = null;
235         this.scopeSelector = null;
236         this.scopeDeriver = null;
237         this.optionalitySelector = null;
238     }
239 
240     /**
241      * Creates a new conflict resolver instance with the specified hooks that delegates to configured conflict resolver
242      * dynamically.
243      *
244      * @param versionSelector the version selector to use, must not be {@code null}
245      * @param scopeSelector the scope selector to use, must not be {@code null}
246      * @param optionalitySelector the optionality selector ot use, must not be {@code null}
247      * @param scopeDeriver the scope deriver to use, must not be {@code null}
248      */
249     public ConflictResolver(
250             VersionSelector versionSelector,
251             ScopeSelector scopeSelector,
252             OptionalitySelector optionalitySelector,
253             ScopeDeriver scopeDeriver) {
254         this.versionSelector = requireNonNull(versionSelector, "version selector cannot be null");
255         this.scopeSelector = requireNonNull(scopeSelector, "scope selector cannot be null");
256         this.optionalitySelector = requireNonNull(optionalitySelector, "optionality selector cannot be null");
257         this.scopeDeriver = requireNonNull(scopeDeriver, "scope deriver cannot be null");
258     }
259 
260     @Override
261     public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
262             throws RepositoryException {
263         String cf = ConfigUtils.getString(
264                 context.getSession(), DEFAULT_CONFLICT_RESOLVER_IMPL, CONFIG_PROP_CONFLICT_RESOLVER_IMPL);
265         ConflictResolver delegate;
266         if (PATH_CONFLICT_RESOLVER.equals(cf)) {
267             delegate = new PathConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
268         } else if (CLASSIC_CONFLICT_RESOLVER.equals(cf)) {
269             delegate = new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
270         } else {
271             throw new IllegalArgumentException("Unknown conflict resolver: " + cf + "; known are "
272                     + Arrays.asList(PATH_CONFLICT_RESOLVER, CLASSIC_CONFLICT_RESOLVER));
273         }
274         return delegate.transformGraph(node, context);
275     }
276 
277     /**
278      * A context used to hold information that is relevant for deriving the scope of a child dependency.
279      *
280      * @see ScopeDeriver
281      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
282      *                change without notice and only exists to enable unit testing
283      */
284     public abstract static class ScopeContext {
285         /**
286          * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of
287          * the scope deriver.
288          *
289          * @return the scope of the parent dependency, never {@code null}
290          */
291         public abstract String getParentScope();
292 
293         /**
294          * Gets the original scope of the child dependency. This is the scope that was declared in the artifact
295          * descriptor of the parent dependency.
296          *
297          * @return the original scope of the child dependency, never {@code null}
298          */
299         public abstract String getChildScope();
300 
301         /**
302          * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the
303          * scope deriver makes changes.
304          *
305          * @return the derived scope of the child dependency, never {@code null}
306          */
307         public abstract String getDerivedScope();
308 
309         /**
310          * Sets the derived scope of the child dependency.
311          *
312          * @param derivedScope the derived scope of the dependency, may be {@code null}
313          */
314         public abstract void setDerivedScope(String derivedScope);
315     }
316 
317     /**
318      * A conflicting dependency.
319      *
320      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
321      *                change without notice and only exists to enable unit testing
322      */
323     public abstract static class ConflictItem {
324         /**
325          * Determines whether the specified conflict item is a sibling of this item.
326          *
327          * @param item the other conflict item, must not be {@code null}
328          * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise
329          */
330         public abstract boolean isSibling(ConflictItem item);
331 
332         /**
333          * Gets the dependency node involved in the conflict.
334          *
335          * @return the involved dependency node, never {@code null}
336          */
337         public abstract DependencyNode getNode();
338 
339         /**
340          * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}.
341          *
342          * @return the involved dependency, never {@code null}
343          */
344         public abstract Dependency getDependency();
345 
346         /**
347          * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the
348          * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest
349          * possible depth.
350          *
351          * @return the zero-based depth of the node in the graph
352          */
353         public abstract int getDepth();
354 
355         /**
356          * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via
357          * different paths and each path might result in a different derived scope.
358          *
359          * @return the (read-only) set of derived scopes of the dependency, never {@code null}
360          * @see ScopeDeriver
361          */
362         public abstract Collection<String> getScopes();
363 
364         /**
365          * Bit flag indicating whether one or more paths consider the dependency non-optional.
366          */
367         public static final int OPTIONAL_FALSE = 0x01;
368 
369         /**
370          * Bit flag indicating whether one or more paths consider the dependency optional.
371          */
372         public static final int OPTIONAL_TRUE = 0x02;
373 
374         /**
375          * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via
376          * different paths and each path might result in a different derived optionality.
377          *
378          * @return a bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or
379          *         {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the
380          *         dependency was encountered with
381          */
382         public abstract int getOptionalities();
383     }
384 
385     /**
386      * A context used to hold information that is relevant for resolving version and scope conflicts.
387      *
388      * @see VersionSelector
389      * @see ScopeSelector
390      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
391      *                change without notice and only exists to enable unit testing
392      */
393     public abstract static class ConflictContext {
394         /**
395          * Gets the root node of the dependency graph being transformed.
396          *
397          * @return the root node of the dependency graph, never {@code null}
398          */
399         public abstract DependencyNode getRoot();
400 
401         /**
402          * Determines whether the specified dependency node belongs to this conflict context.
403          *
404          * @param node the dependency node to check, must not be {@code null}
405          * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise
406          */
407         public abstract boolean isIncluded(DependencyNode node);
408 
409         /**
410          * Gets the collection of conflict items in this context.
411          *
412          * @return the (read-only) collection of conflict items in this context, never {@code null}
413          */
414         public abstract Collection<ConflictItem> getItems();
415 
416         /**
417          * Gets the conflict item which has been selected as the winner among the conflicting dependencies.
418          *
419          * @return the winning conflict item or {@code null} if not set yet
420          */
421         public abstract ConflictItem getWinner();
422 
423         /**
424          * Sets the conflict item which has been selected as the winner among the conflicting dependencies.
425          *
426          * @param winner the winning conflict item, may be {@code null}
427          */
428         public abstract void setWinner(ConflictItem winner);
429 
430         /**
431          * Gets the effective scope of the winning dependency.
432          *
433          * @return the effective scope of the winning dependency or {@code null} if none
434          */
435         public abstract String getScope();
436 
437         /**
438          * Sets the effective scope of the winning dependency.
439          *
440          * @param scope the effective scope, may be {@code null}
441          */
442         public abstract void setScope(String scope);
443 
444         /**
445          * Gets the effective optional flag of the winning dependency.
446          *
447          * @return the effective optional flag or {@code null} if none
448          */
449         public abstract Boolean getOptional();
450 
451         /**
452          * Sets the effective optional flag of the winning dependency.
453          *
454          * @param optional the effective optional flag, may be {@code null}
455          */
456         public abstract void setOptional(Boolean optional);
457     }
458 
459     /**
460      * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The
461      * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The
462      * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the
463      * {@link ScopeSelector}.
464      * <p>
465      * <strong>Note:</strong> Implementations must be stateless.
466      */
467     public abstract static class VersionSelector {
468 
469         /**
470          * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls
471          * this method once per
472          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
473          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
474          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
475          * default implementation simply returns the current instance which is appropriate for implementations which do
476          * not require auxiliary data.
477          *
478          * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null}
479          * @param context the graph transformation context, must not be {@code null}
480          * @return the scope deriver to use for the given graph transformation, never {@code null}
481          * @throws RepositoryException if the instance could not be retrieved
482          */
483         public VersionSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
484                 throws RepositoryException {
485             return this;
486         }
487 
488         /**
489          * Determines the winning node among conflicting dependencies. Implementations will usually iterate
490          * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call
491          * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a
492          * winner will automatically fail the entire conflict resolution.
493          *
494          * @param context the conflict context, must not be {@code null}
495          * @throws RepositoryException if the version selection failed
496          */
497         public abstract void selectVersion(ConflictContext context) throws RepositoryException;
498     }
499 
500     /**
501      * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a
502      * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the
503      * {@link VersionSelector} has picked the winning node.
504      * <p>
505      * <strong>Note:</strong> Implementations must be stateless.
506      */
507     public abstract static class ScopeSelector {
508 
509         /**
510          * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls
511          * this method once per
512          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
513          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
514          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
515          * default implementation simply returns the current instance which is appropriate for implementations which do
516          * not require auxiliary data.
517          *
518          * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null}
519          * @param context the graph transformation context, must not be {@code null}
520          * @return the scope selector to use for the given graph transformation, never {@code null}
521          * @throws RepositoryException if the instance could not be retrieved
522          */
523         public ScopeSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
524                 throws RepositoryException {
525             return this;
526         }
527 
528         /**
529          * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}.
530          * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
531          * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the
532          * effective scope.
533          *
534          * @param context the conflict context, must not be {@code null}
535          * @throws RepositoryException if the scope selection failed
536          */
537         public abstract void selectScope(ConflictContext context) throws RepositoryException;
538     }
539 
540     /**
541      * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope
542      * of its parent.
543      * <p>
544      * <strong>Note:</strong> Implementations must be stateless.
545      */
546     public abstract static class ScopeDeriver {
547 
548         /**
549          * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls
550          * this method once per
551          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
552          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
553          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
554          * default implementation simply returns the current instance which is appropriate for implementations which do
555          * not require auxiliary data.
556          *
557          * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null}
558          * @param context the graph transformation context, must not be {@code null}
559          * @return the scope deriver to use for the given graph transformation, never {@code null}
560          * @throws RepositoryException if the instance could not be retrieved
561          */
562         public ScopeDeriver getInstance(DependencyNode root, DependencyGraphTransformationContext context)
563                 throws RepositoryException {
564             return this;
565         }
566 
567         /**
568          * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call
569          * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is
570          * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged.
571          *
572          * @param context the scope context, must not be {@code null}
573          * @throws RepositoryException if the scope deriviation failed
574          */
575         public abstract void deriveScope(ScopeContext context) throws RepositoryException;
576     }
577 
578     /**
579      * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a
580      * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the
581      * {@link VersionSelector} has picked the winning node.
582      * <p>
583      * <strong>Note:</strong> Implementations must be stateless.
584      */
585     public abstract static class OptionalitySelector {
586 
587         /**
588          * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver
589          * calls this method once per
590          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
591          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
592          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
593          * default implementation simply returns the current instance which is appropriate for implementations which do
594          * not require auxiliary data.
595          *
596          * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null}
597          * @param context the graph transformation context, must not be {@code null}
598          * @return the optionality selector to use for the given graph transformation, never {@code null}
599          * @throws RepositoryException if the instance could not be retrieved
600          */
601         public OptionalitySelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
602                 throws RepositoryException {
603             return this;
604         }
605 
606         /**
607          * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}.
608          * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
609          * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to
610          * deliver the effective optional flag.
611          *
612          * @param context the conflict context, must not be {@code null}
613          * @throws RepositoryException if the optionality selection failed
614          */
615         public abstract void selectOptionality(ConflictContext context) throws RepositoryException;
616     }
617 }