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.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.IdentityHashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.ListIterator;
31  import java.util.Map;
32  import java.util.Objects;
33  
34  import org.eclipse.aether.RepositoryException;
35  import org.eclipse.aether.RepositorySystemSession;
36  import org.eclipse.aether.artifact.Artifact;
37  import org.eclipse.aether.collection.DependencyGraphTransformationContext;
38  import org.eclipse.aether.collection.DependencyGraphTransformer;
39  import org.eclipse.aether.graph.DefaultDependencyNode;
40  import org.eclipse.aether.graph.Dependency;
41  import org.eclipse.aether.graph.DependencyNode;
42  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
43  
44  import static java.util.Objects.requireNonNull;
45  
46  /**
47   * A dependency graph transformer that resolves version and scope conflicts among dependencies. For a given set of
48   * conflicting nodes, one node will be chosen as the winner and the other nodes are removed from the dependency graph.
49   * The exact rules by which a winning node and its effective scope are determined are controlled by user-supplied
50   * implementations of {@link VersionSelector}, {@link ScopeSelector}, {@link OptionalitySelector} and
51   * {@link ScopeDeriver}.
52   * <p>
53   * By default, this graph transformer will turn the dependency graph into a tree without duplicate artifacts. Using the
54   * configuration property {@link #CONFIG_PROP_VERBOSE}, a verbose mode can be enabled where the graph is still turned
55   * into a tree but all nodes participating in a conflict are retained. The nodes that were rejected during conflict
56   * resolution have no children and link back to the winner node via the {@link #NODE_DATA_WINNER} key in their custom
57   * data. Additionally, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used
58   * to store the original scope and optionality of each node. Obviously, the resulting dependency tree is not suitable
59   * for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
60   * <p>
61   * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS},
62   * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for
63   * existing information about conflict ids. In absence of this information, it will automatically invoke the
64   * {@link ConflictIdSorter} to calculate it.
65   */
66  public final class ConflictResolver implements DependencyGraphTransformer {
67  
68      /**
69       * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties()
70       * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode.
71       * Accepted values are {@link Boolean} type, {@link String} type (where "true" would be interpreted as {@code true}
72       * or {@link Verbosity} enum instances.
73       */
74      public static final String CONFIG_PROP_VERBOSE = "aether.conflictResolver.verbose";
75  
76      /**
77       * The enum representing verbosity levels of conflict resolver.
78       *
79       * @since 1.9.8
80       */
81      public enum Verbosity {
82          /**
83           * Verbosity level to be used in all "common" resolving use cases (ie. dependencies to build class path). The
84           * {@link ConflictResolver} in this mode will trim down the graph to the barest minimum: will not leave
85           * any conflicting node in place, hence no conflicts will be present in transformed graph either.
86           */
87          NONE,
88  
89          /**
90           * Verbosity level to be used in "analyze" resolving use cases (ie. dependency convergence calculations). The
91           * {@link ConflictResolver} in this mode will remove any redundant collected nodes, in turn it will leave one
92           * with recorded conflicting information. This mode corresponds to "classic verbose" mode when
93           * {@link #CONFIG_PROP_VERBOSE} was set to {@code true}. Obviously, the resulting dependency tree is not
94           * suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
95           */
96          STANDARD,
97  
98          /**
99           * Verbosity level to be used in "analyze" resolving use cases (ie. dependency convergence calculations). The
100          * {@link ConflictResolver} in this mode will not remove any collected node, in turn it will record on all
101          * eliminated nodes the conflicting information. Obviously, the resulting dependency tree is not suitable
102          * for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
103          */
104         FULL
105     }
106 
107     /**
108      * Helper method that uses {@link RepositorySystemSession} and {@link #CONFIG_PROP_VERBOSE} key to figure out
109      * current {@link Verbosity}: if {@link Boolean} or {@code String} found, returns {@link Verbosity#STANDARD}
110      * or {@link Verbosity#NONE}, depending on value (string is parsed with {@link Boolean#parseBoolean(String)}
111      * for {@code true} or {@code false} correspondingly. This is to retain "existing" behavior, where the config
112      * key accepted only these values.
113      * Since 1.9.8 release, this key may contain {@link Verbosity} enum instance as well, in which case that instance
114      * is returned.
115      * This method never returns {@code null}.
116      */
117     private static Verbosity getVerbosity(RepositorySystemSession session) {
118         final Object verbosityValue = session.getConfigProperties().get(CONFIG_PROP_VERBOSE);
119         if (verbosityValue instanceof Boolean) {
120             return (Boolean) verbosityValue ? Verbosity.STANDARD : Verbosity.NONE;
121         } else if (verbosityValue instanceof String) {
122             return Boolean.parseBoolean(verbosityValue.toString()) ? Verbosity.STANDARD : Verbosity.NONE;
123         } else if (verbosityValue instanceof Verbosity) {
124             return (Verbosity) verbosityValue;
125         } else if (verbosityValue != null) {
126             throw new IllegalArgumentException("Unsupported Verbosity configuration: " + verbosityValue);
127         }
128         return Verbosity.NONE;
129     }
130 
131     /**
132      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the
133      * {@link DependencyNode} which has won the conflict is stored.
134      */
135     public static final String NODE_DATA_WINNER = "conflict.winner";
136 
137     /**
138      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the
139      * dependency before scope derivation and conflict resolution is stored.
140      */
141     public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope";
142 
143     /**
144      * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of
145      * the dependency before derivation and conflict resolution is stored.
146      */
147     public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality";
148 
149     private final VersionSelector versionSelector;
150 
151     private final ScopeSelector scopeSelector;
152 
153     private final ScopeDeriver scopeDeriver;
154 
155     private final OptionalitySelector optionalitySelector;
156 
157     /**
158      * Creates a new conflict resolver instance with the specified hooks.
159      *
160      * @param versionSelector The version selector to use, must not be {@code null}.
161      * @param scopeSelector The scope selector to use, must not be {@code null}.
162      * @param optionalitySelector The optionality selector ot use, must not be {@code null}.
163      * @param scopeDeriver The scope deriver to use, must not be {@code null}.
164      */
165     public ConflictResolver(
166             VersionSelector versionSelector,
167             ScopeSelector scopeSelector,
168             OptionalitySelector optionalitySelector,
169             ScopeDeriver scopeDeriver) {
170         this.versionSelector = requireNonNull(versionSelector, "version selector cannot be null");
171         this.scopeSelector = requireNonNull(scopeSelector, "scope selector cannot be null");
172         this.optionalitySelector = requireNonNull(optionalitySelector, "optionality selector cannot be null");
173         this.scopeDeriver = requireNonNull(scopeDeriver, "scope deriver cannot be null");
174     }
175 
176     public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
177             throws RepositoryException {
178         requireNonNull(node, "node cannot be null");
179         requireNonNull(context, "context cannot be null");
180         List<?> sortedConflictIds = (List<?>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
181         if (sortedConflictIds == null) {
182             ConflictIdSorter sorter = new ConflictIdSorter();
183             sorter.transformGraph(node, context);
184 
185             sortedConflictIds = (List<?>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
186         }
187 
188         @SuppressWarnings("unchecked")
189         Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
190         long time1 = System.nanoTime();
191 
192         @SuppressWarnings("unchecked")
193         Collection<Collection<?>> conflictIdCycles =
194                 (Collection<Collection<?>>) context.get(TransformationContextKeys.CYCLIC_CONFLICT_IDS);
195         if (conflictIdCycles == null) {
196             throw new RepositoryException("conflict id cycles have not been identified");
197         }
198 
199         Map<?, ?> conflictIds = (Map<?, ?>) context.get(TransformationContextKeys.CONFLICT_IDS);
200         if (conflictIds == null) {
201             throw new RepositoryException("conflict groups have not been identified");
202         }
203 
204         Map<Object, Collection<Object>> cyclicPredecessors = new HashMap<>();
205         for (Collection<?> cycle : conflictIdCycles) {
206             for (Object conflictId : cycle) {
207                 Collection<Object> predecessors = cyclicPredecessors.computeIfAbsent(conflictId, k -> new HashSet<>());
208                 predecessors.addAll(cycle);
209             }
210         }
211 
212         State state = new State(node, conflictIds, sortedConflictIds.size(), context);
213         for (Iterator<?> it = sortedConflictIds.iterator(); it.hasNext(); ) {
214             Object conflictId = it.next();
215 
216             // reset data structures for next graph walk
217             state.prepare(conflictId, cyclicPredecessors.get(conflictId));
218 
219             // find nodes with the current conflict id and while walking the graph (more deeply), nuke leftover losers
220             gatherConflictItems(node, state);
221 
222             // now that we know the min depth of the parents, update depth of conflict items
223             state.finish();
224 
225             // earlier runs might have nuked all parents of the current conflict id, so it might not exist anymore
226             if (!state.items.isEmpty()) {
227                 ConflictContext ctx = state.conflictCtx;
228                 state.versionSelector.selectVersion(ctx);
229                 if (ctx.winner == null) {
230                     throw new RepositoryException("conflict resolver did not select winner among " + state.items);
231                 }
232                 DependencyNode winner = ctx.winner.node;
233 
234                 state.scopeSelector.selectScope(ctx);
235                 if (Verbosity.NONE != state.verbosity) {
236                     winner.setData(
237                             NODE_DATA_ORIGINAL_SCOPE, winner.getDependency().getScope());
238                 }
239                 winner.setScope(ctx.scope);
240 
241                 state.optionalitySelector.selectOptionality(ctx);
242                 if (Verbosity.NONE != state.verbosity) {
243                     winner.setData(
244                             NODE_DATA_ORIGINAL_OPTIONALITY,
245                             winner.getDependency().isOptional());
246                 }
247                 winner.setOptional(ctx.optional);
248 
249                 removeLosers(state);
250             }
251 
252             // record the winner so we can detect leftover losers during future graph walks
253             state.winner();
254 
255             // in case of cycles, trigger final graph walk to ensure all leftover losers are gone
256             if (!it.hasNext() && !conflictIdCycles.isEmpty() && state.conflictCtx.winner != null) {
257                 DependencyNode winner = state.conflictCtx.winner.node;
258                 state.prepare(state, null);
259                 gatherConflictItems(winner, state);
260             }
261         }
262 
263         if (stats != null) {
264             long time2 = System.nanoTime();
265             stats.put("ConflictResolver.totalTime", time2 - time1);
266             stats.put("ConflictResolver.conflictItemCount", state.totalConflictItems);
267         }
268 
269         return node;
270     }
271 
272     private boolean gatherConflictItems(DependencyNode node, State state) throws RepositoryException {
273         Object conflictId = state.conflictIds.get(node);
274         if (state.currentId.equals(conflictId)) {
275             // found it, add conflict item (if not already done earlier by another path)
276             state.add(node);
277             // we don't recurse here so we might miss losers beneath us, those will be nuked during future walks below
278         } else if (state.loser(node, conflictId)) {
279             // found a leftover loser (likely in a cycle) of an already processed conflict id, tell caller to nuke it
280             return false;
281         } else if (state.push(node, conflictId)) {
282             // found potential parent, no cycle and not visisted before with the same derived scope, so recurse
283             for (Iterator<DependencyNode> it = node.getChildren().iterator(); it.hasNext(); ) {
284                 DependencyNode child = it.next();
285                 if (!gatherConflictItems(child, state)) {
286                     it.remove();
287                 }
288             }
289             state.pop();
290         }
291         return true;
292     }
293 
294     private static void removeLosers(State state) {
295         ConflictItem winner = state.conflictCtx.winner;
296         String winnerArtifactId = ArtifactIdUtils.toId(winner.node.getArtifact());
297         List<DependencyNode> previousParent = null;
298         ListIterator<DependencyNode> childIt = null;
299         HashSet<String> toRemoveIds = new HashSet<>();
300         for (ConflictItem item : state.items) {
301             if (item == winner) {
302                 continue;
303             }
304             if (item.parent != previousParent) {
305                 childIt = item.parent.listIterator();
306                 previousParent = item.parent;
307             }
308             while (childIt.hasNext()) {
309                 DependencyNode child = childIt.next();
310                 if (child == item.node) {
311                     // NONE: just remove it and done
312                     if (Verbosity.NONE == state.verbosity) {
313                         childIt.remove();
314                         break;
315                     }
316 
317                     // STANDARD: doing extra bookkeeping to select "which nodes to remove"
318                     if (Verbosity.STANDARD == state.verbosity) {
319                         String childArtifactId = ArtifactIdUtils.toId(child.getArtifact());
320                         // if two IDs are equal, it means "there is nearest", not conflict per se.
321                         // In that case we do NOT allow this child to be removed (but remove others)
322                         // and this keeps us safe from iteration (and in general, version) ordering
323                         // as we explicitly leave out ID that is "nearest found" state.
324                         //
325                         // This tackles version ranges mostly, where ranges are turned into list of
326                         // several nodes in collector (as many were discovered, ie. from metadata), and
327                         // old code would just "mark" the first hit as conflict, and remove the rest,
328                         // even if rest could contain "more suitable" version, that is not conflicting/diverging.
329                         // This resulted in verbose mode transformed tree, that was misrepresenting things
330                         // for dependency convergence calculations: it represented state like parent node
331                         // depends on "wrong" version (diverge), while "right" version was present (but removed)
332                         // as well, as it was contained in parents version range.
333                         if (!Objects.equals(winnerArtifactId, childArtifactId)) {
334                             toRemoveIds.add(childArtifactId);
335                         }
336                     }
337 
338                     // FULL: just record the facts
339                     DependencyNode loser = new DefaultDependencyNode(child);
340                     loser.setData(NODE_DATA_WINNER, winner.node);
341                     loser.setData(
342                             NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope());
343                     loser.setData(
344                             NODE_DATA_ORIGINAL_OPTIONALITY,
345                             loser.getDependency().isOptional());
346                     loser.setScope(item.getScopes().iterator().next());
347                     loser.setChildren(Collections.emptyList());
348                     childIt.set(loser);
349                     item.node = loser;
350                     break;
351                 }
352             }
353         }
354 
355         // 2nd pass to apply "standard" verbosity: leaving only 1 loser, but with care
356         if (Verbosity.STANDARD == state.verbosity && !toRemoveIds.isEmpty()) {
357             previousParent = null;
358             for (ConflictItem item : state.items) {
359                 if (item == winner) {
360                     continue;
361                 }
362                 if (item.parent != previousParent) {
363                     childIt = item.parent.listIterator();
364                     previousParent = item.parent;
365                 }
366                 while (childIt.hasNext()) {
367                     DependencyNode child = childIt.next();
368                     if (child == item.node) {
369                         String childArtifactId = ArtifactIdUtils.toId(child.getArtifact());
370                         if (toRemoveIds.contains(childArtifactId)
371                                 && relatedSiblingsCount(child.getArtifact(), item.parent) > 1) {
372                             childIt.remove();
373                         }
374                         break;
375                     }
376                 }
377             }
378         }
379 
380         // there might still be losers beneath the winner (e.g. in case of cycles)
381         // those will be nuked during future graph walks when we include the winner in the recursion
382     }
383 
384     private static long relatedSiblingsCount(Artifact artifact, List<DependencyNode> parent) {
385         String ga = artifact.getGroupId() + ":" + artifact.getArtifactId();
386         return parent.stream()
387                 .map(DependencyNode::getArtifact)
388                 .filter(a -> ga.equals(a.getGroupId() + ":" + a.getArtifactId()))
389                 .count();
390     }
391 
392     static final class NodeInfo {
393 
394         /**
395          * The smallest depth at which the node was seen, used for "the" depth of its conflict items.
396          */
397         int minDepth;
398 
399         /**
400          * The set of derived scopes the node was visited with, used to check whether an already seen node needs to be
401          * revisited again in context of another scope. To conserve memory, we start with {@code String} and update to
402          * {@code Set<String>} if needed.
403          */
404         Object derivedScopes;
405 
406         /**
407          * The set of derived optionalities the node was visited with, used to check whether an already seen node needs
408          * to be revisited again in context of another optionality. To conserve memory, encoded as bit field (bit 0 ->
409          * optional=false, bit 1 -> optional=true).
410          */
411         int derivedOptionalities;
412 
413         /**
414          * The conflict items which are immediate children of the node, used to easily update those conflict items after
415          * a new parent scope/optionality was encountered.
416          */
417         List<ConflictItem> children;
418 
419         static final int CHANGE_SCOPE = 0x01;
420 
421         static final int CHANGE_OPTIONAL = 0x02;
422 
423         private static final int OPT_FALSE = 0x01;
424 
425         private static final int OPT_TRUE = 0x02;
426 
427         NodeInfo(int depth, String derivedScope, boolean optional) {
428             minDepth = depth;
429             derivedScopes = derivedScope;
430             derivedOptionalities = optional ? OPT_TRUE : OPT_FALSE;
431         }
432 
433         @SuppressWarnings("unchecked")
434         int update(int depth, String derivedScope, boolean optional) {
435             if (depth < minDepth) {
436                 minDepth = depth;
437             }
438             int changes;
439             if (derivedScopes.equals(derivedScope)) {
440                 changes = 0;
441             } else if (derivedScopes instanceof Collection) {
442                 changes = ((Collection<String>) derivedScopes).add(derivedScope) ? CHANGE_SCOPE : 0;
443             } else {
444                 Collection<String> scopes = new HashSet<>();
445                 scopes.add((String) derivedScopes);
446                 scopes.add(derivedScope);
447                 derivedScopes = scopes;
448                 changes = CHANGE_SCOPE;
449             }
450             int bit = optional ? OPT_TRUE : OPT_FALSE;
451             if ((derivedOptionalities & bit) == 0) {
452                 derivedOptionalities |= bit;
453                 changes |= CHANGE_OPTIONAL;
454             }
455             return changes;
456         }
457 
458         void add(ConflictItem item) {
459             if (children == null) {
460                 children = new ArrayList<>(1);
461             }
462             children.add(item);
463         }
464     }
465 
466     final class State {
467 
468         /**
469          * The conflict id currently processed.
470          */
471         Object currentId;
472 
473         /**
474          * Stats counter.
475          */
476         int totalConflictItems;
477 
478         /**
479          * Flag whether we should keep losers in the graph to enable visualization/troubleshooting of conflicts.
480          */
481         final Verbosity verbosity;
482 
483         /**
484          * A mapping from conflict id to winner node, helps to recognize nodes that have their effective
485          * scope&optionality set or are leftovers from previous removals.
486          */
487         final Map<Object, DependencyNode> resolvedIds;
488 
489         /**
490          * The set of conflict ids which could apply to ancestors of nodes with the current conflict id, used to avoid
491          * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account
492          * for cyclic dependencies.
493          */
494         final Collection<Object> potentialAncestorIds;
495 
496         /**
497          * The output from the conflict marker
498          */
499         final Map<?, ?> conflictIds;
500 
501         /**
502          * The conflict items we have gathered so far for the current conflict id.
503          */
504         final List<ConflictItem> items;
505 
506         /**
507          * The (conceptual) mapping from nodes to extra infos, technically keyed by the node's child list which better
508          * captures the identity of a node since we're basically concerned with effects towards children.
509          */
510         final Map<List<DependencyNode>, NodeInfo> infos;
511 
512         /**
513          * The set of nodes on the DFS stack to detect cycles, technically keyed by the node's child list to match the
514          * dirty graph structure produced by the dependency collector for cycles.
515          */
516         final Map<List<DependencyNode>, Object> stack;
517 
518         /**
519          * The stack of parent nodes.
520          */
521         final List<DependencyNode> parentNodes;
522 
523         /**
524          * The stack of derived scopes for parent nodes.
525          */
526         final List<String> parentScopes;
527 
528         /**
529          * The stack of derived optional flags for parent nodes.
530          */
531         final List<Boolean> parentOptionals;
532 
533         /**
534          * The stack of node infos for parent nodes, may contain {@code null} which is used to disable creating new
535          * conflict items when visiting their parent again (conflict items are meant to be unique by parent-node combo).
536          */
537         final List<NodeInfo> parentInfos;
538 
539         /**
540          * The conflict context passed to the version/scope/optionality selectors, updated as we move along rather than
541          * recreated to avoid tmp objects.
542          */
543         final ConflictContext conflictCtx;
544 
545         /**
546          * The scope context passed to the scope deriver, updated as we move along rather than recreated to avoid tmp
547          * objects.
548          */
549         final ScopeContext scopeCtx;
550 
551         /**
552          * The effective version selector, i.e. after initialization.
553          */
554         final VersionSelector versionSelector;
555 
556         /**
557          * The effective scope selector, i.e. after initialization.
558          */
559         final ScopeSelector scopeSelector;
560 
561         /**
562          * The effective scope deriver, i.e. after initialization.
563          */
564         final ScopeDeriver scopeDeriver;
565 
566         /**
567          * The effective optionality selector, i.e. after initialization.
568          */
569         final OptionalitySelector optionalitySelector;
570 
571         State(
572                 DependencyNode root,
573                 Map<?, ?> conflictIds,
574                 int conflictIdCount,
575                 DependencyGraphTransformationContext context)
576                 throws RepositoryException {
577             this.conflictIds = conflictIds;
578             this.verbosity = getVerbosity(context.getSession());
579             potentialAncestorIds = new HashSet<>(conflictIdCount * 2);
580             resolvedIds = new HashMap<>(conflictIdCount * 2);
581             items = new ArrayList<>(256);
582             infos = new IdentityHashMap<>(64);
583             stack = new IdentityHashMap<>(64);
584             parentNodes = new ArrayList<>(64);
585             parentScopes = new ArrayList<>(64);
586             parentOptionals = new ArrayList<>(64);
587             parentInfos = new ArrayList<>(64);
588             conflictCtx = new ConflictContext(root, conflictIds, items);
589             scopeCtx = new ScopeContext(null, null);
590             versionSelector = ConflictResolver.this.versionSelector.getInstance(root, context);
591             scopeSelector = ConflictResolver.this.scopeSelector.getInstance(root, context);
592             scopeDeriver = ConflictResolver.this.scopeDeriver.getInstance(root, context);
593             optionalitySelector = ConflictResolver.this.optionalitySelector.getInstance(root, context);
594         }
595 
596         void prepare(Object conflictId, Collection<Object> cyclicPredecessors) {
597             currentId = conflictId;
598             conflictCtx.conflictId = conflictId;
599             conflictCtx.winner = null;
600             conflictCtx.scope = null;
601             conflictCtx.optional = null;
602             items.clear();
603             infos.clear();
604             if (cyclicPredecessors != null) {
605                 potentialAncestorIds.addAll(cyclicPredecessors);
606             }
607         }
608 
609         void finish() {
610             List<DependencyNode> previousParent = null;
611             int previousDepth = 0;
612             totalConflictItems += items.size();
613             for (ListIterator<ConflictItem> iterator = items.listIterator(items.size()); iterator.hasPrevious(); ) {
614                 ConflictItem item = iterator.previous();
615                 if (item.parent == previousParent) {
616                     item.depth = previousDepth;
617                 } else if (item.parent != null) {
618                     previousParent = item.parent;
619                     NodeInfo info = infos.get(previousParent);
620                     previousDepth = info.minDepth + 1;
621                     item.depth = previousDepth;
622                 }
623             }
624             potentialAncestorIds.add(currentId);
625         }
626 
627         void winner() {
628             resolvedIds.put(currentId, (conflictCtx.winner != null) ? conflictCtx.winner.node : null);
629         }
630 
631         boolean loser(DependencyNode node, Object conflictId) {
632             DependencyNode winner = resolvedIds.get(conflictId);
633             return winner != null && winner != node;
634         }
635 
636         boolean push(DependencyNode node, Object conflictId) throws RepositoryException {
637             if (conflictId == null) {
638                 if (node.getDependency() != null) {
639                     if (node.getData().get(NODE_DATA_WINNER) != null) {
640                         return false;
641                     }
642                     throw new RepositoryException("missing conflict id for node " + node);
643                 }
644             } else if (!potentialAncestorIds.contains(conflictId)) {
645                 return false;
646             }
647 
648             List<DependencyNode> graphNode = node.getChildren();
649             if (stack.put(graphNode, Boolean.TRUE) != null) {
650                 return false;
651             }
652 
653             int depth = depth();
654             String scope = deriveScope(node, conflictId);
655             boolean optional = deriveOptional(node, conflictId);
656             NodeInfo info = infos.get(graphNode);
657             if (info == null) {
658                 info = new NodeInfo(depth, scope, optional);
659                 infos.put(graphNode, info);
660                 parentInfos.add(info);
661                 parentNodes.add(node);
662                 parentScopes.add(scope);
663                 parentOptionals.add(optional);
664             } else {
665                 int changes = info.update(depth, scope, optional);
666                 if (changes == 0) {
667                     stack.remove(graphNode);
668                     return false;
669                 }
670                 parentInfos.add(null); // disable creating new conflict items, we update the existing ones below
671                 parentNodes.add(node);
672                 parentScopes.add(scope);
673                 parentOptionals.add(optional);
674                 if (info.children != null) {
675                     if ((changes & NodeInfo.CHANGE_SCOPE) != 0) {
676                         ListIterator<ConflictItem> itemIterator = info.children.listIterator(info.children.size());
677                         while (itemIterator.hasPrevious()) {
678                             ConflictItem item = itemIterator.previous();
679                             String childScope = deriveScope(item.node, null);
680                             item.addScope(childScope);
681                         }
682                     }
683                     if ((changes & NodeInfo.CHANGE_OPTIONAL) != 0) {
684                         ListIterator<ConflictItem> itemIterator = info.children.listIterator(info.children.size());
685                         while (itemIterator.hasPrevious()) {
686                             ConflictItem item = itemIterator.previous();
687                             boolean childOptional = deriveOptional(item.node, null);
688                             item.addOptional(childOptional);
689                         }
690                     }
691                 }
692             }
693 
694             return true;
695         }
696 
697         void pop() {
698             int last = parentInfos.size() - 1;
699             parentInfos.remove(last);
700             parentScopes.remove(last);
701             parentOptionals.remove(last);
702             DependencyNode node = parentNodes.remove(last);
703             stack.remove(node.getChildren());
704         }
705 
706         void add(DependencyNode node) throws RepositoryException {
707             DependencyNode parent = parent();
708             if (parent == null) {
709                 ConflictItem item = newConflictItem(parent, node);
710                 items.add(item);
711             } else {
712                 NodeInfo info = parentInfos.get(parentInfos.size() - 1);
713                 if (info != null) {
714                     ConflictItem item = newConflictItem(parent, node);
715                     info.add(item);
716                     items.add(item);
717                 }
718             }
719         }
720 
721         private ConflictItem newConflictItem(DependencyNode parent, DependencyNode node) throws RepositoryException {
722             return new ConflictItem(parent, node, deriveScope(node, null), deriveOptional(node, null));
723         }
724 
725         private int depth() {
726             return parentNodes.size();
727         }
728 
729         private DependencyNode parent() {
730             int size = parentNodes.size();
731             return (size <= 0) ? null : parentNodes.get(size - 1);
732         }
733 
734         private String deriveScope(DependencyNode node, Object conflictId) throws RepositoryException {
735             if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) != 0
736                     || (conflictId != null && resolvedIds.containsKey(conflictId))) {
737                 return scope(node.getDependency());
738             }
739 
740             int depth = parentNodes.size();
741             scopes(depth, node.getDependency());
742             if (depth > 0) {
743                 scopeDeriver.deriveScope(scopeCtx);
744             }
745             return scopeCtx.derivedScope;
746         }
747 
748         private void scopes(int parent, Dependency child) {
749             scopeCtx.parentScope = (parent > 0) ? parentScopes.get(parent - 1) : null;
750             scopeCtx.derivedScope = scope(child);
751             scopeCtx.childScope = scope(child);
752         }
753 
754         private String scope(Dependency dependency) {
755             return (dependency != null) ? dependency.getScope() : null;
756         }
757 
758         private boolean deriveOptional(DependencyNode node, Object conflictId) {
759             Dependency dep = node.getDependency();
760             boolean optional = (dep != null) && dep.isOptional();
761             if (optional
762                     || (node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) != 0
763                     || (conflictId != null && resolvedIds.containsKey(conflictId))) {
764                 return optional;
765             }
766             int depth = parentNodes.size();
767             return (depth > 0) ? parentOptionals.get(depth - 1) : false;
768         }
769     }
770 
771     /**
772      * A context used to hold information that is relevant for deriving the scope of a child dependency.
773      *
774      * @see ScopeDeriver
775      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
776      *                change without notice and only exists to enable unit testing.
777      */
778     public static final class ScopeContext {
779 
780         String parentScope;
781 
782         String childScope;
783 
784         String derivedScope;
785 
786         /**
787          * Creates a new scope context with the specified properties.
788          *
789          * @param parentScope The scope of the parent dependency, may be {@code null}.
790          * @param childScope The scope of the child dependency, may be {@code null}.
791          * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
792          *              change without notice and only exists to enable unit testing.
793          */
794         public ScopeContext(String parentScope, String childScope) {
795             this.parentScope = (parentScope != null) ? parentScope : "";
796             derivedScope = (childScope != null) ? childScope : "";
797             this.childScope = (childScope != null) ? childScope : "";
798         }
799 
800         /**
801          * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of
802          * the scope deriver.
803          *
804          * @return The scope of the parent dependency, never {@code null}.
805          */
806         public String getParentScope() {
807             return parentScope;
808         }
809 
810         /**
811          * Gets the original scope of the child dependency. This is the scope that was declared in the artifact
812          * descriptor of the parent dependency.
813          *
814          * @return The original scope of the child dependency, never {@code null}.
815          */
816         public String getChildScope() {
817             return childScope;
818         }
819 
820         /**
821          * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the
822          * scope deriver makes changes.
823          *
824          * @return The derived scope of the child dependency, never {@code null}.
825          */
826         public String getDerivedScope() {
827             return derivedScope;
828         }
829 
830         /**
831          * Sets the derived scope of the child dependency.
832          *
833          * @param derivedScope The derived scope of the dependency, may be {@code null}.
834          */
835         public void setDerivedScope(String derivedScope) {
836             this.derivedScope = (derivedScope != null) ? derivedScope : "";
837         }
838     }
839 
840     /**
841      * A conflicting dependency.
842      *
843      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
844      *                change without notice and only exists to enable unit testing.
845      */
846     public static final class ConflictItem {
847 
848         // nodes can share child lists, we care about the unique owner of a child node which is the child list
849         final List<DependencyNode> parent;
850 
851         // only for debugging/toString() to help identify the parent node(s)
852         final Artifact artifact;
853 
854         // is mutable as removeLosers will mutate it (if Verbosity==STANDARD)
855         DependencyNode node;
856 
857         int depth;
858 
859         // we start with String and update to Set<String> if needed
860         Object scopes;
861 
862         // bit field of OPTIONAL_FALSE and OPTIONAL_TRUE
863         int optionalities;
864 
865         /**
866          * Bit flag indicating whether one or more paths consider the dependency non-optional.
867          */
868         public static final int OPTIONAL_FALSE = 0x01;
869 
870         /**
871          * Bit flag indicating whether one or more paths consider the dependency optional.
872          */
873         public static final int OPTIONAL_TRUE = 0x02;
874 
875         ConflictItem(DependencyNode parent, DependencyNode node, String scope, boolean optional) {
876             if (parent != null) {
877                 this.parent = parent.getChildren();
878                 this.artifact = parent.getArtifact();
879             } else {
880                 this.parent = null;
881                 this.artifact = null;
882             }
883             this.node = node;
884             this.scopes = scope;
885             this.optionalities = optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
886         }
887 
888         /**
889          * Creates a new conflict item with the specified properties.
890          *
891          * @param parent The parent node of the conflicting dependency, may be {@code null}.
892          * @param node The conflicting dependency, must not be {@code null}.
893          * @param depth The zero-based depth of the conflicting dependency.
894          * @param optionalities The optionalities the dependency was encountered with, encoded as a bit field consisting
895          *            of {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} and
896          *            {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE}.
897          * @param scopes The derived scopes of the conflicting dependency, must not be {@code null}.
898          * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
899          *              change without notice and only exists to enable unit testing.
900          */
901         public ConflictItem(
902                 DependencyNode parent, DependencyNode node, int depth, int optionalities, String... scopes) {
903             this.parent = (parent != null) ? parent.getChildren() : null;
904             this.artifact = (parent != null) ? parent.getArtifact() : null;
905             this.node = node;
906             this.depth = depth;
907             this.optionalities = optionalities;
908             this.scopes = Arrays.asList(scopes);
909         }
910 
911         /**
912          * Determines whether the specified conflict item is a sibling of this item.
913          *
914          * @param item The other conflict item, must not be {@code null}.
915          * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise.
916          */
917         public boolean isSibling(ConflictItem item) {
918             return parent == item.parent;
919         }
920 
921         /**
922          * Gets the dependency node involved in the conflict.
923          *
924          * @return The involved dependency node, never {@code null}.
925          */
926         public DependencyNode getNode() {
927             return node;
928         }
929 
930         /**
931          * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}.
932          *
933          * @return The involved dependency, never {@code null}.
934          */
935         public Dependency getDependency() {
936             return node.getDependency();
937         }
938 
939         /**
940          * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the
941          * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest
942          * possible depth.
943          *
944          * @return The zero-based depth of the node in the graph.
945          */
946         public int getDepth() {
947             return depth;
948         }
949 
950         /**
951          * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via
952          * different paths and each path might result in a different derived scope.
953          *
954          * @see ScopeDeriver
955          * @return The (read-only) set of derived scopes of the dependency, never {@code null}.
956          */
957         @SuppressWarnings("unchecked")
958         public Collection<String> getScopes() {
959             if (scopes instanceof String) {
960                 return Collections.singleton((String) scopes);
961             }
962             return (Collection<String>) scopes;
963         }
964 
965         @SuppressWarnings("unchecked")
966         void addScope(String scope) {
967             if (scopes instanceof Collection) {
968                 ((Collection<String>) scopes).add(scope);
969             } else if (!scopes.equals(scope)) {
970                 Collection<Object> set = new HashSet<>();
971                 set.add(scopes);
972                 set.add(scope);
973                 scopes = set;
974             }
975         }
976 
977         /**
978          * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via
979          * different paths and each path might result in a different derived optionality.
980          *
981          * @return A bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or
982          *         {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the
983          *         dependency was encountered with.
984          */
985         public int getOptionalities() {
986             return optionalities;
987         }
988 
989         void addOptional(boolean optional) {
990             optionalities |= optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
991         }
992 
993         @Override
994         public String toString() {
995             return node + " @ " + depth + " < " + artifact;
996         }
997     }
998 
999     /**
1000      * A context used to hold information that is relevant for resolving version and scope conflicts.
1001      *
1002      * @see VersionSelector
1003      * @see ScopeSelector
1004      * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
1005      *                change without notice and only exists to enable unit testing.
1006      */
1007     public static final class ConflictContext {
1008 
1009         final DependencyNode root;
1010 
1011         final Map<?, ?> conflictIds;
1012 
1013         final Collection<ConflictItem> items;
1014 
1015         Object conflictId;
1016 
1017         ConflictItem winner;
1018 
1019         String scope;
1020 
1021         Boolean optional;
1022 
1023         ConflictContext(DependencyNode root, Map<?, ?> conflictIds, Collection<ConflictItem> items) {
1024             this.root = root;
1025             this.conflictIds = conflictIds;
1026             this.items = Collections.unmodifiableCollection(items);
1027         }
1028 
1029         /**
1030          * Creates a new conflict context.
1031          *
1032          * @param root The root node of the dependency graph, must not be {@code null}.
1033          * @param conflictId The conflict id for the set of conflicting dependencies in this context, must not be
1034          *            {@code null}.
1035          * @param conflictIds The mapping from dependency node to conflict id, must not be {@code null}.
1036          * @param items The conflict items in this context, must not be {@code null}.
1037          * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
1038          *              change without notice and only exists to enable unit testing.
1039          */
1040         public ConflictContext(
1041                 DependencyNode root,
1042                 Object conflictId,
1043                 Map<DependencyNode, Object> conflictIds,
1044                 Collection<ConflictItem> items) {
1045             this(root, conflictIds, items);
1046             this.conflictId = conflictId;
1047         }
1048 
1049         /**
1050          * Gets the root node of the dependency graph being transformed.
1051          *
1052          * @return The root node of the dependeny graph, never {@code null}.
1053          */
1054         public DependencyNode getRoot() {
1055             return root;
1056         }
1057 
1058         /**
1059          * Determines whether the specified dependency node belongs to this conflict context.
1060          *
1061          * @param node The dependency node to check, must not be {@code null}.
1062          * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise.
1063          */
1064         public boolean isIncluded(DependencyNode node) {
1065             return conflictId.equals(conflictIds.get(node));
1066         }
1067 
1068         /**
1069          * Gets the collection of conflict items in this context.
1070          *
1071          * @return The (read-only) collection of conflict items in this context, never {@code null}.
1072          */
1073         public Collection<ConflictItem> getItems() {
1074             return items;
1075         }
1076 
1077         /**
1078          * Gets the conflict item which has been selected as the winner among the conflicting dependencies.
1079          *
1080          * @return The winning conflict item or {@code null} if not set yet.
1081          */
1082         public ConflictItem getWinner() {
1083             return winner;
1084         }
1085 
1086         /**
1087          * Sets the conflict item which has been selected as the winner among the conflicting dependencies.
1088          *
1089          * @param winner The winning conflict item, may be {@code null}.
1090          */
1091         public void setWinner(ConflictItem winner) {
1092             this.winner = winner;
1093         }
1094 
1095         /**
1096          * Gets the effective scope of the winning dependency.
1097          *
1098          * @return The effective scope of the winning dependency or {@code null} if none.
1099          */
1100         public String getScope() {
1101             return scope;
1102         }
1103 
1104         /**
1105          * Sets the effective scope of the winning dependency.
1106          *
1107          * @param scope The effective scope, may be {@code null}.
1108          */
1109         public void setScope(String scope) {
1110             this.scope = scope;
1111         }
1112 
1113         /**
1114          * Gets the effective optional flag of the winning dependency.
1115          *
1116          * @return The effective optional flag or {@code null} if none.
1117          */
1118         public Boolean getOptional() {
1119             return optional;
1120         }
1121 
1122         /**
1123          * Sets the effective optional flag of the winning dependency.
1124          *
1125          * @param optional The effective optional flag, may be {@code null}.
1126          */
1127         public void setOptional(Boolean optional) {
1128             this.optional = optional;
1129         }
1130 
1131         @Override
1132         public String toString() {
1133             return winner + " @ " + scope + " < " + items;
1134         }
1135     }
1136 
1137     /**
1138      * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The
1139      * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The
1140      * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the
1141      * {@link ScopeSelector}.
1142      * <p>
1143      * <strong>Note:</strong> Implementations must be stateless.
1144      */
1145     public abstract static class VersionSelector {
1146 
1147         /**
1148          * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls
1149          * this method once per
1150          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
1151          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
1152          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
1153          * default implementation simply returns the current instance which is appropriate for implementations which do
1154          * not require auxiliary data.
1155          *
1156          * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
1157          * @param context The graph transformation context, must not be {@code null}.
1158          * @return The scope deriver to use for the given graph transformation, never {@code null}.
1159          * @throws RepositoryException If the instance could not be retrieved.
1160          */
1161         public VersionSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
1162                 throws RepositoryException {
1163             return this;
1164         }
1165 
1166         /**
1167          * Determines the winning node among conflicting dependencies. Implementations will usually iterate
1168          * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call
1169          * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a
1170          * winner will automatically fail the entire conflict resolution.
1171          *
1172          * @param context The conflict context, must not be {@code null}.
1173          * @throws RepositoryException If the version selection failed.
1174          */
1175         public abstract void selectVersion(ConflictContext context) throws RepositoryException;
1176     }
1177 
1178     /**
1179      * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a
1180      * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the
1181      * {@link VersionSelector} has picked the winning node.
1182      * <p>
1183      * <strong>Note:</strong> Implementations must be stateless.
1184      */
1185     public abstract static class ScopeSelector {
1186 
1187         /**
1188          * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls
1189          * this method once per
1190          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
1191          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
1192          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
1193          * default implementation simply returns the current instance which is appropriate for implementations which do
1194          * not require auxiliary data.
1195          *
1196          * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
1197          * @param context The graph transformation context, must not be {@code null}.
1198          * @return The scope selector to use for the given graph transformation, never {@code null}.
1199          * @throws RepositoryException If the instance could not be retrieved.
1200          */
1201         public ScopeSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
1202                 throws RepositoryException {
1203             return this;
1204         }
1205 
1206         /**
1207          * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}.
1208          * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
1209          * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the
1210          * effective scope.
1211          *
1212          * @param context The conflict context, must not be {@code null}.
1213          * @throws RepositoryException If the scope selection failed.
1214          */
1215         public abstract void selectScope(ConflictContext context) throws RepositoryException;
1216     }
1217 
1218     /**
1219      * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope
1220      * of its parent.
1221      * <p>
1222      * <strong>Note:</strong> Implementations must be stateless.
1223      */
1224     public abstract static class ScopeDeriver {
1225 
1226         /**
1227          * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls
1228          * this method once per
1229          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
1230          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
1231          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
1232          * default implementation simply returns the current instance which is appropriate for implementations which do
1233          * not require auxiliary data.
1234          *
1235          * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
1236          * @param context The graph transformation context, must not be {@code null}.
1237          * @return The scope deriver to use for the given graph transformation, never {@code null}.
1238          * @throws RepositoryException If the instance could not be retrieved.
1239          */
1240         public ScopeDeriver getInstance(DependencyNode root, DependencyGraphTransformationContext context)
1241                 throws RepositoryException {
1242             return this;
1243         }
1244 
1245         /**
1246          * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call
1247          * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is
1248          * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged.
1249          *
1250          * @param context The scope context, must not be {@code null}.
1251          * @throws RepositoryException If the scope deriviation failed.
1252          */
1253         public abstract void deriveScope(ScopeContext context) throws RepositoryException;
1254     }
1255 
1256     /**
1257      * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a
1258      * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the
1259      * {@link VersionSelector} has picked the winning node.
1260      * <p>
1261      * <strong>Note:</strong> Implementations must be stateless.
1262      */
1263     public abstract static class OptionalitySelector {
1264 
1265         /**
1266          * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver
1267          * calls this method once per
1268          * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
1269          * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
1270          * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The
1271          * default implementation simply returns the current instance which is appropriate for implementations which do
1272          * not require auxiliary data.
1273          *
1274          * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
1275          * @param context The graph transformation context, must not be {@code null}.
1276          * @return The optionality selector to use for the given graph transformation, never {@code null}.
1277          * @throws RepositoryException If the instance could not be retrieved.
1278          */
1279         public OptionalitySelector getInstance(DependencyNode root, DependencyGraphTransformationContext context)
1280                 throws RepositoryException {
1281             return this;
1282         }
1283 
1284         /**
1285          * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}.
1286          * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
1287          * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to
1288          * deliver the effective optional flag.
1289          *
1290          * @param context The conflict context, must not be {@code null}.
1291          * @throws RepositoryException If the optionality selection failed.
1292          */
1293         public abstract void selectOptionality(ConflictContext context) throws RepositoryException;
1294     }
1295 }