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