diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java index fc8bf87ab3..9fb6eb31f9 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java @@ -18,7 +18,6 @@ */ package org.eclipse.aether.util.graph.transformer; -import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -43,31 +42,19 @@ *
* Available Implementations: *
* Implementation Selection Guide: *
* Usage Example: *
{@code
- * // Recommended: High-performance path-based resolver
- * DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer(
- * new PathConflictResolver(
- * new NearestVersionSelector(),
- * new JavaScopeSelector(),
- * new SimpleOptionalitySelector(),
- * new JavaScopeDeriver()),
- * // other transformers...
- * );
- *
- * // Legacy: Classic resolver for backward compatibility
+ * // Classic resolver
* DependencyGraphTransformer legacyTransformer = new ChainedDependencyGraphTransformer(
* new ClassicConflictResolver(
* new NearestVersionSelector(),
@@ -112,8 +99,8 @@
* existing information about conflict ids. In absence of this information, it will automatically invoke the
* {@link ConflictIdSorter} to calculate it.
*
- * @see PathConflictResolver
* @see ClassicConflictResolver
+ * @see PathConflictResolver
*/
public class ConflictResolver implements DependencyGraphTransformer {
@@ -132,9 +119,8 @@ public class ConflictResolver implements DependencyGraphTransformer {
/**
* The name of the conflict resolver implementation to use: "auto" (default), "path", or "classic" (same as Maven 3).
*
- * When set to "auto", the resolver estimates whether the Path tree would fit in available heap memory.
- * If it would consume more than 25% of available heap, the classic (in-place) resolver is used instead
- * to avoid OutOfMemoryErrors on very large dependency graphs.
+ * When set to "auto", the resolver will currently just use "classic". The idea here, is that this value will
+ * always select the best (most robust, most performant) one, which currently is "classic".
*
* @since 2.0.11
* @configurationSource {@link RepositorySystemSession#getConfigProperties()}
@@ -264,18 +250,15 @@ public ConflictResolver(
}
@Override
- @SuppressWarnings("unchecked")
public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
throws RepositoryException {
String cf = ConfigUtils.getString(
context.getSession(), DEFAULT_CONFLICT_RESOLVER_IMPL, CONFIG_PROP_CONFLICT_RESOLVER_IMPL);
ConflictResolver delegate;
- if (AUTO_CONFLICT_RESOLVER.equals(cf)) {
- delegate = selectConflictResolver(node, context);
+ if (AUTO_CONFLICT_RESOLVER.equals(cf) || CLASSIC_CONFLICT_RESOLVER.equals(cf)) {
+ delegate = new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
} else if (PATH_CONFLICT_RESOLVER.equals(cf)) {
delegate = new PathConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
- } else if (CLASSIC_CONFLICT_RESOLVER.equals(cf)) {
- delegate = new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
} else {
throw new IllegalArgumentException("Unknown conflict resolver: " + cf + "; known are "
+ Arrays.asList(AUTO_CONFLICT_RESOLVER, PATH_CONFLICT_RESOLVER, CLASSIC_CONFLICT_RESOLVER));
@@ -283,60 +266,6 @@ public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransfo
return delegate.transformGraph(node, context);
}
- /**
- * Selects the most appropriate conflict resolver based on graph size and available memory.
- *
- * PathConflictResolver builds a parallel tree of Path objects that costs ~200 bytes per node.
- * For very large dependency graphs (millions of nodes), this can exhaust the heap.
- * In such cases, ClassicConflictResolver is used instead — it works in-place with no parallel
- * structure, trading O(N²) worst-case time for O(1) extra space.
- */
- private ConflictResolver selectConflictResolver(DependencyNode node, DependencyGraphTransformationContext context)
- throws RepositoryException {
- // Ensure conflict IDs are computed — both implementations need this anyway
- if (context.get(TransformationContextKeys.SORTED_CONFLICT_IDS) == null) {
- new ConflictIdSorter().transformGraph(node, context);
- }
-
- Runtime rt = Runtime.getRuntime();
- long available = rt.maxMemory() - (rt.totalMemory() - rt.freeMemory());
-
- // Estimate the maximum number of Path tree nodes that would fit in 25% of available heap.
- // Each Path object costs ~200 bytes (object header + fields + children list entry).
- int maxPathNodes = (int) Math.min(available / (4L * 200), Integer.MAX_VALUE);
-
- // Walk the dependency tree to count total nodes (including diamond-expanded duplicates).
- // The Path tree mirrors this structure, so the count directly reflects Path tree size.
- // Use early-exit: stop counting once we exceed the threshold — no need to measure the
- // full tree if we already know it's too large.
- if (treeExceedsThreshold(node, maxPathNodes)) {
- return new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
- } else {
- return new PathConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver);
- }
- }
-
- /**
- * Checks whether the total number of nodes in the dependency tree (including diamond-expanded
- * duplicates) exceeds the given threshold. Uses an iterative walk with early exit to avoid
- * measuring the full tree when it's clearly too large.
- */
- private boolean treeExceedsThreshold(DependencyNode root, int threshold) {
- int count = 0;
- ArrayDeque stack = new ArrayDeque<>();
- stack.push(root);
- while (!stack.isEmpty()) {
- DependencyNode n = stack.pop();
- if (++count > threshold) {
- return true;
- }
- for (DependencyNode child : n.getChildren()) {
- stack.push(child);
- }
- }
- return false;
- }
-
/**
* A context used to hold information that is relevant for deriving the scope of a child dependency.
*