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