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 }