1818 */
1919package org .eclipse .aether .util .graph .transformer ;
2020
21- import java .util .ArrayDeque ;
2221import java .util .Arrays ;
2322import java .util .Collection ;
2423
4342 * <p>
4443 * <strong>Available Implementations:</strong>
4544 * <ul>
46- * <li><strong>{@link PathConflictResolver }</strong> - Recommended high-performance implementation with O(N) complexity </li>
47- * <li><strong>{@link ClassicConflictResolver }</strong> - Legacy implementation for backward compatibility ( O(N²) worst-case) </li>
45+ * <li><strong>{@link ClassicConflictResolver }</strong> - Original implementation ( O(N²) worst-case) </li>
46+ * <li><strong>{@link PathConflictResolver }</strong> - Not yet recommended for production; high-performance implementation with O(N) complexity </li>
4847 * </ul>
4948 * <p>
5049 * <strong>Implementation Selection Guide:</strong>
5150 * <ul>
52- * <li><strong>New Projects:</strong> Use {@link PathConflictResolver} for optimal performance</li>
53- * <li><strong>Large Multi-Module Projects:</strong> Use {@link PathConflictResolver} to avoid performance bottlenecks</li>
54- * <li><strong>Maven 4+ Environments:</strong> Use {@link PathConflictResolver} for best build performance</li>
55- * <li><strong>Legacy Compatibility:</strong> Use {@link ClassicConflictResolver} only when exact Maven 3.x behavior is required</li>
51+ * <li><strong>All projects:</strong> Use {@link ClassicConflictResolver} for optimal correctness and Maven 3.x behavior</li>
52+ * <li><strong>Experimenters:</strong> Use {@link PathConflictResolver} but no guarantees it will work</li>
5653 * </ul>
5754 * <p>
5855 * <strong>Usage Example:</strong>
5956 * <pre>{@code
60- * // Recommended: High-performance path-based resolver
61- * DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer(
62- * new PathConflictResolver(
63- * new NearestVersionSelector(),
64- * new JavaScopeSelector(),
65- * new SimpleOptionalitySelector(),
66- * new JavaScopeDeriver()),
67- * // other transformers...
68- * );
69- *
70- * // Legacy: Classic resolver for backward compatibility
57+ * // Classic resolver
7158 * DependencyGraphTransformer legacyTransformer = new ChainedDependencyGraphTransformer(
7259 * new ClassicConflictResolver(
7360 * new NearestVersionSelector(),
11299 * existing information about conflict ids. In absence of this information, it will automatically invoke the
113100 * {@link ConflictIdSorter} to calculate it.
114101 *
115- * @see PathConflictResolver
116102 * @see ClassicConflictResolver
103+ * @see PathConflictResolver
117104 */
118105public class ConflictResolver implements DependencyGraphTransformer {
119106
@@ -132,9 +119,8 @@ public class ConflictResolver implements DependencyGraphTransformer {
132119 /**
133120 * The name of the conflict resolver implementation to use: "auto" (default), "path", or "classic" (same as Maven 3).
134121 * <p>
135- * When set to "auto", the resolver estimates whether the Path tree would fit in available heap memory.
136- * If it would consume more than 25% of available heap, the classic (in-place) resolver is used instead
137- * to avoid OutOfMemoryErrors on very large dependency graphs.
122+ * When set to "auto", the resolver will currently just use "classic". The idea here, is that this value will
123+ * always select the best (most robust, most performant) one, which currently is "classic".
138124 *
139125 * @since 2.0.11
140126 * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
@@ -264,79 +250,22 @@ public ConflictResolver(
264250 }
265251
266252 @ Override
267- @ SuppressWarnings ("unchecked" )
268253 public DependencyNode transformGraph (DependencyNode node , DependencyGraphTransformationContext context )
269254 throws RepositoryException {
270255 String cf = ConfigUtils .getString (
271256 context .getSession (), DEFAULT_CONFLICT_RESOLVER_IMPL , CONFIG_PROP_CONFLICT_RESOLVER_IMPL );
272257 ConflictResolver delegate ;
273- if (AUTO_CONFLICT_RESOLVER .equals (cf )) {
274- delegate = selectConflictResolver ( node , context );
258+ if (AUTO_CONFLICT_RESOLVER .equals (cf ) || CLASSIC_CONFLICT_RESOLVER . equals ( cf ) ) {
259+ delegate = new ClassicConflictResolver ( versionSelector , scopeSelector , optionalitySelector , scopeDeriver );
275260 } else if (PATH_CONFLICT_RESOLVER .equals (cf )) {
276261 delegate = new PathConflictResolver (versionSelector , scopeSelector , optionalitySelector , scopeDeriver );
277- } else if (CLASSIC_CONFLICT_RESOLVER .equals (cf )) {
278- delegate = new ClassicConflictResolver (versionSelector , scopeSelector , optionalitySelector , scopeDeriver );
279262 } else {
280263 throw new IllegalArgumentException ("Unknown conflict resolver: " + cf + "; known are "
281264 + Arrays .asList (AUTO_CONFLICT_RESOLVER , PATH_CONFLICT_RESOLVER , CLASSIC_CONFLICT_RESOLVER ));
282265 }
283266 return delegate .transformGraph (node , context );
284267 }
285268
286- /**
287- * Selects the most appropriate conflict resolver based on graph size and available memory.
288- * <p>
289- * PathConflictResolver builds a parallel tree of Path objects that costs ~200 bytes per node.
290- * For very large dependency graphs (millions of nodes), this can exhaust the heap.
291- * In such cases, ClassicConflictResolver is used instead — it works in-place with no parallel
292- * structure, trading O(N²) worst-case time for O(1) extra space.
293- */
294- private ConflictResolver selectConflictResolver (DependencyNode node , DependencyGraphTransformationContext context )
295- throws RepositoryException {
296- // Ensure conflict IDs are computed — both implementations need this anyway
297- if (context .get (TransformationContextKeys .SORTED_CONFLICT_IDS ) == null ) {
298- new ConflictIdSorter ().transformGraph (node , context );
299- }
300-
301- Runtime rt = Runtime .getRuntime ();
302- long available = rt .maxMemory () - (rt .totalMemory () - rt .freeMemory ());
303-
304- // Estimate the maximum number of Path tree nodes that would fit in 25% of available heap.
305- // Each Path object costs ~200 bytes (object header + fields + children list entry).
306- int maxPathNodes = (int ) Math .min (available / (4L * 200 ), Integer .MAX_VALUE );
307-
308- // Walk the dependency tree to count total nodes (including diamond-expanded duplicates).
309- // The Path tree mirrors this structure, so the count directly reflects Path tree size.
310- // Use early-exit: stop counting once we exceed the threshold — no need to measure the
311- // full tree if we already know it's too large.
312- if (treeExceedsThreshold (node , maxPathNodes )) {
313- return new ClassicConflictResolver (versionSelector , scopeSelector , optionalitySelector , scopeDeriver );
314- } else {
315- return new PathConflictResolver (versionSelector , scopeSelector , optionalitySelector , scopeDeriver );
316- }
317- }
318-
319- /**
320- * Checks whether the total number of nodes in the dependency tree (including diamond-expanded
321- * duplicates) exceeds the given threshold. Uses an iterative walk with early exit to avoid
322- * measuring the full tree when it's clearly too large.
323- */
324- private boolean treeExceedsThreshold (DependencyNode root , int threshold ) {
325- int count = 0 ;
326- ArrayDeque <DependencyNode > stack = new ArrayDeque <>();
327- stack .push (root );
328- while (!stack .isEmpty ()) {
329- DependencyNode n = stack .pop ();
330- if (++count > threshold ) {
331- return true ;
332- }
333- for (DependencyNode child : n .getChildren ()) {
334- stack .push (child );
335- }
336- }
337- return false ;
338- }
339-
340269 /**
341270 * A context used to hold information that is relevant for deriving the scope of a child dependency.
342271 *
0 commit comments