This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new cf2eaeff7 Put PCR aside for now (#1942)
cf2eaeff7 is described below

commit cf2eaeff7dab4f967538c21470de138fe11198b4
Author: Tamas Cservenak <[email protected]>
AuthorDate: Mon Jun 29 22:33:16 2026 +0200

    Put PCR aside for now (#1942)
    
    It's proven a bit problematic, and memory hungry,
    while the promise of O(N) over O(N^2) is cool.
---
 .../util/graph/transformer/ConflictResolver.java   | 91 +++-------------------
 1 file changed, 10 insertions(+), 81 deletions(-)

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 fc8bf87ab..9fb6eb31f 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 @@ import static java.util.Objects.requireNonNull;
  * <p>
  * <strong>Available Implementations:</strong>
  * <ul>
- * <li><strong>{@link PathConflictResolver}</strong> - Recommended 
high-performance implementation with O(N) complexity</li>
- * <li><strong>{@link ClassicConflictResolver}</strong> - Legacy 
implementation for backward compatibility (O(N²) worst-case)</li>
+ * <li><strong>{@link ClassicConflictResolver}</strong> - Original 
implementation (O(N²) worst-case)</li>
+ * <li><strong>{@link PathConflictResolver}</strong> - Not yet recommended for 
production; high-performance implementation with O(N) complexity</li>
  * </ul>
  * <p>
  * <strong>Implementation Selection Guide:</strong>
  * <ul>
- * <li><strong>New Projects:</strong> Use {@link PathConflictResolver} for 
optimal performance</li>
- * <li><strong>Large Multi-Module Projects:</strong> Use {@link 
PathConflictResolver} to avoid performance bottlenecks</li>
- * <li><strong>Maven 4+ Environments:</strong> Use {@link 
PathConflictResolver} for best build performance</li>
- * <li><strong>Legacy Compatibility:</strong> Use {@link 
ClassicConflictResolver} only when exact Maven 3.x behavior is required</li>
+ * <li><strong>All projects:</strong> Use {@link ClassicConflictResolver} for 
optimal correctness and Maven 3.x behavior</li>
+ * <li><strong>Experimenters:</strong> Use {@link PathConflictResolver} but no 
guarantees it will work</li>
  * </ul>
  * <p>
  * <strong>Usage Example:</strong>
  * <pre>{@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 @@ import static java.util.Objects.requireNonNull;
  * 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).
      * <p>
-     * 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 class ConflictResolver implements 
DependencyGraphTransformer {
     }
 
     @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 class ConflictResolver implements 
DependencyGraphTransformer {
         return delegate.transformGraph(node, context);
     }
 
-    /**
-     * Selects the most appropriate conflict resolver based on graph size and 
available memory.
-     * <p>
-     * 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<DependencyNode> 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.
      *

Reply via email to