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 b8e9f9f8 [MRESOLVER-461] Cleanup in dep managers (#402)
b8e9f9f8 is described below

commit b8e9f9f88712ef7f25ae11618fc452d77e60591e
Author: Tamas Cservenak <[email protected]>
AuthorDate: Wed Dec 20 12:13:08 2023 +0100

    [MRESOLVER-461] Cleanup in dep managers (#402)
    
    3 of them were almost same.
    * default: derives to infinity, always applies
    * transitive: derives to infinity, applies after depth>=2
    * classic: derives until depth>=2, applies after depth>=2
    
    Note: "derives" means picks up depMgt from currently processed node. Hence, 
classic ignores all depMgt in graph coming from transitive dependencies. 
depth=1 is root (ie. project), depth=2 are first level deps (those listed in 
POM), etc.
    
    Added UT to cover all 3 of them with all aspects as well.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-461
---
 ...Manager.java => AbstractDependencyManager.java} | 105 ++++----
 .../graph/manager/ClassicDependencyManager.java    | 271 +++------------------
 .../graph/manager/DefaultDependencyManager.java    | 259 +++-----------------
 .../graph/manager/TransitiveDependencyManager.java | 263 +++-----------------
 .../manager/ClassicDependencyManagerTest.java      |  75 ------
 .../util/graph/manager/DependencyManagerTest.java  | 248 +++++++++++++++++++
 6 files changed, 421 insertions(+), 800 deletions(-)

diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java
similarity index 79%
copy from 
maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
copy to 
maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java
index 19917376..5cf7ebb3 100644
--- 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
+++ 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Objects;
 
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.artifact.ArtifactProperties;
@@ -36,59 +37,87 @@ import org.eclipse.aether.util.artifact.JavaScopes;
 import static java.util.Objects.requireNonNull;
 
 /**
- * A dependency manager that mimics the way Maven 2.x works.
+ * A dependency manager support class.
+ *
+ * @since 2.0.0
  */
-public final class ClassicDependencyManager implements DependencyManager {
+public abstract class AbstractDependencyManager implements DependencyManager {
+
+    protected final int depth;
 
-    private final int depth;
+    protected final int deriveUntil;
 
-    private final Map<Object, String> managedVersions;
+    protected final int applyFrom;
 
-    private final Map<Object, String> managedScopes;
+    protected final Map<Object, String> managedVersions;
 
-    private final Map<Object, Boolean> managedOptionals;
+    protected final Map<Object, String> managedScopes;
 
-    private final Map<Object, String> managedLocalPaths;
+    protected final Map<Object, Boolean> managedOptionals;
 
-    private final Map<Object, Collection<Exclusion>> managedExclusions;
+    protected final Map<Object, String> managedLocalPaths;
 
-    private int hashCode;
+    protected final Map<Object, Collection<Exclusion>> managedExclusions;
+
+    private final int hashCode;
 
     /**
      * Creates a new dependency manager without any management information.
      */
-    public ClassicDependencyManager() {
+    protected AbstractDependencyManager(int deriveUntil, int applyFrom) {
         this(
                 0,
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Boolean>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Collection<Exclusion>>emptyMap());
+                deriveUntil,
+                applyFrom,
+                Collections.emptyMap(),
+                Collections.emptyMap(),
+                Collections.emptyMap(),
+                Collections.emptyMap(),
+                Collections.emptyMap());
     }
 
-    private ClassicDependencyManager(
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    protected AbstractDependencyManager(
             int depth,
+            int deriveUntil,
+            int applyFrom,
             Map<Object, String> managedVersions,
             Map<Object, String> managedScopes,
             Map<Object, Boolean> managedOptionals,
             Map<Object, String> managedLocalPaths,
             Map<Object, Collection<Exclusion>> managedExclusions) {
         this.depth = depth;
+        this.deriveUntil = deriveUntil;
+        this.applyFrom = applyFrom;
         this.managedVersions = managedVersions;
         this.managedScopes = managedScopes;
         this.managedOptionals = managedOptionals;
         this.managedLocalPaths = managedLocalPaths;
         this.managedExclusions = managedExclusions;
+
+        this.hashCode = Objects.hash(
+                depth,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 
+    protected abstract DependencyManager newInstance(
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions);
+
+    @Override
     public DependencyManager deriveChildManager(DependencyCollectionContext 
context) {
         requireNonNull(context, "context cannot be null");
-        if (depth >= 2) {
+        if (depth >= deriveUntil) {
             return this;
-        } else if (depth == 1) {
-            return new ClassicDependencyManager(
-                    depth + 1, managedVersions, managedScopes, 
managedOptionals, managedLocalPaths, managedExclusions);
         }
 
         Map<Object, String> managedVersions = this.managedVersions;
@@ -99,7 +128,7 @@ public final class ClassicDependencyManager implements 
DependencyManager {
 
         for (Dependency managedDependency : context.getManagedDependencies()) {
             Artifact artifact = managedDependency.getArtifact();
-            Object key = getKey(artifact);
+            Object key = new Key(artifact);
 
             String version = artifact.getVersion();
             if (!version.isEmpty() && !managedVersions.containsKey(key)) {
@@ -143,17 +172,16 @@ public final class ClassicDependencyManager implements 
DependencyManager {
             }
         }
 
-        return new ClassicDependencyManager(
-                depth + 1, managedVersions, managedScopes, managedOptionals, 
managedLocalPaths, managedExclusions);
+        return newInstance(managedVersions, managedScopes, managedOptionals, 
managedLocalPaths, managedExclusions);
     }
 
+    @Override
     public DependencyManagement manageDependency(Dependency dependency) {
         requireNonNull(dependency, "dependency cannot be null");
         DependencyManagement management = null;
+        Object key = new Key(dependency.getArtifact());
 
-        Object key = getKey(dependency.getArtifact());
-
-        if (depth >= 2) {
+        if (depth >= applyFrom) {
             String version = managedVersions.get(key);
             if (version != null) {
                 management = new DependencyManagement();
@@ -212,10 +240,6 @@ public final class ClassicDependencyManager implements 
DependencyManager {
         return management;
     }
 
-    private Object getKey(Artifact a) {
-        return new Key(a);
-    }
-
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -224,8 +248,10 @@ public final class ClassicDependencyManager implements 
DependencyManager {
             return false;
         }
 
-        ClassicDependencyManager that = (ClassicDependencyManager) obj;
+        AbstractDependencyManager that = (AbstractDependencyManager) obj;
         return depth == that.depth
+                && deriveUntil == that.deriveUntil
+                && applyFrom == that.applyFrom
                 && managedVersions.equals(that.managedVersions)
                 && managedScopes.equals(that.managedScopes)
                 && managedOptionals.equals(that.managedOptionals)
@@ -234,19 +260,10 @@ public final class ClassicDependencyManager implements 
DependencyManager {
 
     @Override
     public int hashCode() {
-        if (hashCode == 0) {
-            int hash = 17;
-            hash = hash * 31 + depth;
-            hash = hash * 31 + managedVersions.hashCode();
-            hash = hash * 31 + managedScopes.hashCode();
-            hash = hash * 31 + managedOptionals.hashCode();
-            hash = hash * 31 + managedExclusions.hashCode();
-            hashCode = hash;
-        }
         return hashCode;
     }
 
-    static class Key {
+    protected static class Key {
 
         private final Artifact artifact;
 
@@ -254,11 +271,7 @@ public final class ClassicDependencyManager implements 
DependencyManager {
 
         Key(Artifact artifact) {
             this.artifact = artifact;
-
-            int hash = 17;
-            hash = hash * 31 + artifact.getGroupId().hashCode();
-            hash = hash * 31 + artifact.getArtifactId().hashCode();
-            hashCode = hash;
+            this.hashCode = Objects.hash(artifact.getGroupId(), 
artifact.getArtifactId());
         }
 
         @Override
diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
index 19917376..afde36ed 100644
--- 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
+++ 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManager.java
@@ -19,265 +19,72 @@
 package org.eclipse.aether.util.graph.manager;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
 
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.artifact.ArtifactProperties;
 import org.eclipse.aether.collection.DependencyCollectionContext;
-import org.eclipse.aether.collection.DependencyManagement;
 import org.eclipse.aether.collection.DependencyManager;
-import org.eclipse.aether.graph.Dependency;
 import org.eclipse.aether.graph.Exclusion;
-import org.eclipse.aether.util.artifact.JavaScopes;
-
-import static java.util.Objects.requireNonNull;
 
 /**
- * A dependency manager that mimics the way Maven 2.x works.
+ * A dependency manager that mimics the way Maven 2.x works. This manager was 
used throughout all Maven 3.x versions.
+ * <p>
+ * This manager has {@code deriveUntil=2} and {@code applyFrom=2}.
  */
-public final class ClassicDependencyManager implements DependencyManager {
-
-    private final int depth;
-
-    private final Map<Object, String> managedVersions;
-
-    private final Map<Object, String> managedScopes;
-
-    private final Map<Object, Boolean> managedOptionals;
-
-    private final Map<Object, String> managedLocalPaths;
-
-    private final Map<Object, Collection<Exclusion>> managedExclusions;
-
-    private int hashCode;
-
+public final class ClassicDependencyManager extends AbstractDependencyManager {
     /**
      * Creates a new dependency manager without any management information.
      */
     public ClassicDependencyManager() {
-        this(
-                0,
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Boolean>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Collection<Exclusion>>emptyMap());
+        super(2, 2);
     }
 
+    @SuppressWarnings("checkstyle:ParameterNumber")
     private ClassicDependencyManager(
             int depth,
+            int deriveUntil,
+            int applyFrom,
             Map<Object, String> managedVersions,
             Map<Object, String> managedScopes,
             Map<Object, Boolean> managedOptionals,
             Map<Object, String> managedLocalPaths,
             Map<Object, Collection<Exclusion>> managedExclusions) {
-        this.depth = depth;
-        this.managedVersions = managedVersions;
-        this.managedScopes = managedScopes;
-        this.managedOptionals = managedOptionals;
-        this.managedLocalPaths = managedLocalPaths;
-        this.managedExclusions = managedExclusions;
-    }
-
-    public DependencyManager deriveChildManager(DependencyCollectionContext 
context) {
-        requireNonNull(context, "context cannot be null");
-        if (depth >= 2) {
-            return this;
-        } else if (depth == 1) {
-            return new ClassicDependencyManager(
-                    depth + 1, managedVersions, managedScopes, 
managedOptionals, managedLocalPaths, managedExclusions);
-        }
-
-        Map<Object, String> managedVersions = this.managedVersions;
-        Map<Object, String> managedScopes = this.managedScopes;
-        Map<Object, Boolean> managedOptionals = this.managedOptionals;
-        Map<Object, String> managedLocalPaths = this.managedLocalPaths;
-        Map<Object, Collection<Exclusion>> managedExclusions = 
this.managedExclusions;
-
-        for (Dependency managedDependency : context.getManagedDependencies()) {
-            Artifact artifact = managedDependency.getArtifact();
-            Object key = getKey(artifact);
-
-            String version = artifact.getVersion();
-            if (!version.isEmpty() && !managedVersions.containsKey(key)) {
-                if (managedVersions == this.managedVersions) {
-                    managedVersions = new HashMap<>(this.managedVersions);
-                }
-                managedVersions.put(key, version);
-            }
-
-            String scope = managedDependency.getScope();
-            if (!scope.isEmpty() && !managedScopes.containsKey(key)) {
-                if (managedScopes == this.managedScopes) {
-                    managedScopes = new HashMap<>(this.managedScopes);
-                }
-                managedScopes.put(key, scope);
-            }
-
-            Boolean optional = managedDependency.getOptional();
-            if (optional != null && !managedOptionals.containsKey(key)) {
-                if (managedOptionals == this.managedOptionals) {
-                    managedOptionals = new HashMap<>(this.managedOptionals);
-                }
-                managedOptionals.put(key, optional);
-            }
-
-            String localPath = 
managedDependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, 
null);
-            if (localPath != null && !managedLocalPaths.containsKey(key)) {
-                if (managedLocalPaths == this.managedLocalPaths) {
-                    managedLocalPaths = new HashMap<>(this.managedLocalPaths);
-                }
-                managedLocalPaths.put(key, localPath);
-            }
-
-            Collection<Exclusion> exclusions = 
managedDependency.getExclusions();
-            if (!exclusions.isEmpty()) {
-                if (managedExclusions == this.managedExclusions) {
-                    managedExclusions = new HashMap<>(this.managedExclusions);
-                }
-                Collection<Exclusion> managed = 
managedExclusions.computeIfAbsent(key, k -> new LinkedHashSet<>());
-                managed.addAll(exclusions);
-            }
-        }
-
-        return new ClassicDependencyManager(
-                depth + 1, managedVersions, managedScopes, managedOptionals, 
managedLocalPaths, managedExclusions);
-    }
-
-    public DependencyManagement manageDependency(Dependency dependency) {
-        requireNonNull(dependency, "dependency cannot be null");
-        DependencyManagement management = null;
-
-        Object key = getKey(dependency.getArtifact());
-
-        if (depth >= 2) {
-            String version = managedVersions.get(key);
-            if (version != null) {
-                management = new DependencyManagement();
-                management.setVersion(version);
-            }
-
-            String scope = managedScopes.get(key);
-            if (scope != null) {
-                if (management == null) {
-                    management = new DependencyManagement();
-                }
-                management.setScope(scope);
-
-                if (!JavaScopes.SYSTEM.equals(scope)
-                        && 
dependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, null) != 
null) {
-                    Map<String, String> properties =
-                            new 
HashMap<>(dependency.getArtifact().getProperties());
-                    properties.remove(ArtifactProperties.LOCAL_PATH);
-                    management.setProperties(properties);
-                }
-            }
-
-            if ((JavaScopes.SYSTEM.equals(scope))
-                    || (scope == null && 
JavaScopes.SYSTEM.equals(dependency.getScope()))) {
-                String localPath = managedLocalPaths.get(key);
-                if (localPath != null) {
-                    if (management == null) {
-                        management = new DependencyManagement();
-                    }
-                    Map<String, String> properties =
-                            new 
HashMap<>(dependency.getArtifact().getProperties());
-                    properties.put(ArtifactProperties.LOCAL_PATH, localPath);
-                    management.setProperties(properties);
-                }
-            }
-
-            Boolean optional = managedOptionals.get(key);
-            if (optional != null) {
-                if (management == null) {
-                    management = new DependencyManagement();
-                }
-                management.setOptional(optional);
-            }
-        }
-
-        Collection<Exclusion> exclusions = managedExclusions.get(key);
-        if (exclusions != null) {
-            if (management == null) {
-                management = new DependencyManagement();
-            }
-            Collection<Exclusion> result = new 
LinkedHashSet<>(dependency.getExclusions());
-            result.addAll(exclusions);
-            management.setExclusions(result);
-        }
-
-        return management;
-    }
-
-    private Object getKey(Artifact a) {
-        return new Key(a);
+        super(
+                depth,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 
     @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        } else if (null == obj || !getClass().equals(obj.getClass())) {
-            return false;
+    public DependencyManager deriveChildManager(DependencyCollectionContext 
context) {
+        // MNG-4720: Maven2 backward compatibility
+        // Removing this IF makes one IT fail here (read comment above):
+        // 
https://github.com/apache/maven-integration-testing/blob/b4e8fd52b99a058336f9c7c5ec44fdbc1427759c/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4720DependencyManagementExclusionMergeTest.java#L67
+        if (depth == 1) {
+            return newInstance(managedVersions, managedScopes, 
managedOptionals, managedLocalPaths, managedExclusions);
         }
-
-        ClassicDependencyManager that = (ClassicDependencyManager) obj;
-        return depth == that.depth
-                && managedVersions.equals(that.managedVersions)
-                && managedScopes.equals(that.managedScopes)
-                && managedOptionals.equals(that.managedOptionals)
-                && managedExclusions.equals(that.managedExclusions);
+        return super.deriveChildManager(context);
     }
 
     @Override
-    public int hashCode() {
-        if (hashCode == 0) {
-            int hash = 17;
-            hash = hash * 31 + depth;
-            hash = hash * 31 + managedVersions.hashCode();
-            hash = hash * 31 + managedScopes.hashCode();
-            hash = hash * 31 + managedOptionals.hashCode();
-            hash = hash * 31 + managedExclusions.hashCode();
-            hashCode = hash;
-        }
-        return hashCode;
-    }
-
-    static class Key {
-
-        private final Artifact artifact;
-
-        private final int hashCode;
-
-        Key(Artifact artifact) {
-            this.artifact = artifact;
-
-            int hash = 17;
-            hash = hash * 31 + artifact.getGroupId().hashCode();
-            hash = hash * 31 + artifact.getArtifactId().hashCode();
-            hashCode = hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == this) {
-                return true;
-            } else if (!(obj instanceof Key)) {
-                return false;
-            }
-            Key that = (Key) obj;
-            return 
artifact.getArtifactId().equals(that.artifact.getArtifactId())
-                    && artifact.getGroupId().equals(that.artifact.getGroupId())
-                    && 
artifact.getExtension().equals(that.artifact.getExtension())
-                    && 
artifact.getClassifier().equals(that.artifact.getClassifier());
-        }
-
-        @Override
-        public int hashCode() {
-            return hashCode;
-        }
+    protected DependencyManager newInstance(
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions) {
+        return new ClassicDependencyManager(
+                depth + 1,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 }
diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DefaultDependencyManager.java
 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DefaultDependencyManager.java
index 332a3622..f76b2a44 100644
--- 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DefaultDependencyManager.java
+++ 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DefaultDependencyManager.java
@@ -19,22 +19,10 @@
 package org.eclipse.aether.util.graph.manager;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Objects;
 
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.artifact.ArtifactProperties;
-import org.eclipse.aether.collection.DependencyCollectionContext;
-import org.eclipse.aether.collection.DependencyManagement;
 import org.eclipse.aether.collection.DependencyManager;
-import org.eclipse.aether.graph.Dependency;
 import org.eclipse.aether.graph.Exclusion;
-import org.eclipse.aether.util.artifact.JavaScopes;
-
-import static java.util.Objects.requireNonNull;
 
 /**
  * A dependency manager managing dependencies on all levels supporting 
transitive dependency management.
@@ -42,229 +30,56 @@ import static java.util.Objects.requireNonNull;
  * <b>Note:</b>Unlike the {@code ClassicDependencyManager} and the {@code 
TransitiveDependencyManager} this
  * implementation applies management also on the first level. This is 
considered the resolver's default behaviour.
  * It ignores all management overrides supported by the {@code 
MavenModelBuilder}.
- * </p>
+ * <p>
+ * This manager has {@code deriveUntil=Integer.MAX_VALUE} and {@code 
applyFrom=0}.
  *
  * @author Christian Schulte
  * @since 1.4.0
  */
-public final class DefaultDependencyManager implements DependencyManager {
-
-    private final Map<Object, String> managedVersions;
-
-    private final Map<Object, String> managedScopes;
-
-    private final Map<Object, Boolean> managedOptionals;
-
-    private final Map<Object, String> managedLocalPaths;
-
-    private final Map<Object, Collection<Exclusion>> managedExclusions;
-
-    private int hashCode;
-
+public final class DefaultDependencyManager extends AbstractDependencyManager {
     /**
      * Creates a new dependency manager without any management information.
      */
     public DefaultDependencyManager() {
-        this(
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Boolean>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Collection<Exclusion>>emptyMap());
+        super(Integer.MAX_VALUE, 0);
     }
 
+    @SuppressWarnings("checkstyle:ParameterNumber")
     private DefaultDependencyManager(
-            final Map<Object, String> managedVersions,
-            final Map<Object, String> managedScopes,
-            final Map<Object, Boolean> managedOptionals,
-            final Map<Object, String> managedLocalPaths,
-            final Map<Object, Collection<Exclusion>> managedExclusions) {
-        super();
-        this.managedVersions = managedVersions;
-        this.managedScopes = managedScopes;
-        this.managedOptionals = managedOptionals;
-        this.managedLocalPaths = managedLocalPaths;
-        this.managedExclusions = managedExclusions;
-    }
-
-    public DependencyManager deriveChildManager(final 
DependencyCollectionContext context) {
-        requireNonNull(context, "context cannot be null");
-        Map<Object, String> versions = this.managedVersions;
-        Map<Object, String> scopes = this.managedScopes;
-        Map<Object, Boolean> optionals = this.managedOptionals;
-        Map<Object, String> localPaths = this.managedLocalPaths;
-        Map<Object, Collection<Exclusion>> exclusions = this.managedExclusions;
-
-        for (Dependency managedDependency : context.getManagedDependencies()) {
-            Artifact artifact = managedDependency.getArtifact();
-            Object key = getKey(artifact);
-
-            String version = artifact.getVersion();
-            if (!version.isEmpty() && !versions.containsKey(key)) {
-                if (versions == this.managedVersions) {
-                    versions = new HashMap<>(this.managedVersions);
-                }
-                versions.put(key, version);
-            }
-
-            String scope = managedDependency.getScope();
-            if (!scope.isEmpty() && !scopes.containsKey(key)) {
-                if (scopes == this.managedScopes) {
-                    scopes = new HashMap<>(this.managedScopes);
-                }
-                scopes.put(key, scope);
-            }
-
-            Boolean optional = managedDependency.getOptional();
-            if (optional != null && !optionals.containsKey(key)) {
-                if (optionals == this.managedOptionals) {
-                    optionals = new HashMap<>(this.managedOptionals);
-                }
-                optionals.put(key, optional);
-            }
-
-            String localPath = 
managedDependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, 
null);
-            if (localPath != null && !localPaths.containsKey(key)) {
-                if (localPaths == this.managedLocalPaths) {
-                    localPaths = new HashMap<>(this.managedLocalPaths);
-                }
-                localPaths.put(key, localPath);
-            }
-
-            if (!managedDependency.getExclusions().isEmpty()) {
-                if (exclusions == this.managedExclusions) {
-                    exclusions = new HashMap<>(this.managedExclusions);
-                }
-                Collection<Exclusion> managed = 
exclusions.computeIfAbsent(key, k -> new LinkedHashSet<>());
-                managed.addAll(managedDependency.getExclusions());
-            }
-        }
-
-        return new DefaultDependencyManager(versions, scopes, optionals, 
localPaths, exclusions);
-    }
-
-    public DependencyManagement manageDependency(Dependency dependency) {
-        requireNonNull(dependency, "dependency cannot be null");
-        DependencyManagement management = null;
-
-        Object key = getKey(dependency.getArtifact());
-
-        String version = managedVersions.get(key);
-        if (version != null) {
-            management = new DependencyManagement();
-            management.setVersion(version);
-        }
-
-        String scope = managedScopes.get(key);
-        if (scope != null) {
-            if (management == null) {
-                management = new DependencyManagement();
-            }
-            management.setScope(scope);
-
-            if (!JavaScopes.SYSTEM.equals(scope)
-                    && 
dependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, null) != 
null) {
-                Map<String, String> properties =
-                        new 
HashMap<>(dependency.getArtifact().getProperties());
-
-                properties.remove(ArtifactProperties.LOCAL_PATH);
-                management.setProperties(properties);
-            }
-        }
-
-        if ((scope != null && JavaScopes.SYSTEM.equals(scope))
-                || (scope == null && 
JavaScopes.SYSTEM.equals(dependency.getScope()))) {
-            String localPath = managedLocalPaths.get(key);
-            if (localPath != null) {
-                if (management == null) {
-                    management = new DependencyManagement();
-                }
-
-                Map<String, String> properties =
-                        new 
HashMap<>(dependency.getArtifact().getProperties());
-
-                properties.put(ArtifactProperties.LOCAL_PATH, localPath);
-                management.setProperties(properties);
-            }
-        }
-
-        Boolean optional = managedOptionals.get(key);
-        if (optional != null) {
-            if (management == null) {
-                management = new DependencyManagement();
-            }
-            management.setOptional(optional);
-        }
-
-        Collection<Exclusion> exclusions = managedExclusions.get(key);
-        if (exclusions != null) {
-            if (management == null) {
-                management = new DependencyManagement();
-            }
-            Collection<Exclusion> result = new 
LinkedHashSet<>(dependency.getExclusions());
-            result.addAll(exclusions);
-            management.setExclusions(result);
-        }
-
-        return management;
-    }
-
-    private Object getKey(Artifact a) {
-        return new Key(a);
+            int depth,
+            int deriveUntil,
+            int applyFrom,
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions) {
+        super(
+                depth,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        boolean equal = obj instanceof DefaultDependencyManager;
-
-        if (equal) {
-            final DefaultDependencyManager that = (DefaultDependencyManager) 
obj;
-            equal = Objects.equals(managedVersions, that.managedVersions)
-                    && Objects.equals(managedScopes, that.managedScopes)
-                    && Objects.equals(managedOptionals, that.managedOptionals)
-                    && Objects.equals(managedExclusions, 
that.managedExclusions);
-        }
-
-        return equal;
-    }
-
-    @Override
-    public int hashCode() {
-        if (hashCode == 0) {
-            hashCode = Objects.hash(managedVersions, managedScopes, 
managedOptionals, managedExclusions);
-        }
-        return hashCode;
-    }
-
-    static class Key {
-
-        private final Artifact artifact;
-
-        private final int hashCode;
-
-        Key(final Artifact artifact) {
-            this.artifact = artifact;
-            this.hashCode = Objects.hash(artifact.getGroupId(), 
artifact.getArtifactId());
-        }
-
-        @Override
-        public boolean equals(final Object obj) {
-            boolean equal = obj instanceof Key;
-
-            if (equal) {
-                final Key that = (Key) obj;
-                return Objects.equals(artifact.getArtifactId(), 
that.artifact.getArtifactId())
-                        && Objects.equals(artifact.getGroupId(), 
that.artifact.getGroupId())
-                        && Objects.equals(artifact.getExtension(), 
that.artifact.getExtension())
-                        && Objects.equals(artifact.getClassifier(), 
that.artifact.getClassifier());
-            }
-
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return this.hashCode;
-        }
+    protected DependencyManager newInstance(
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions) {
+        return new DefaultDependencyManager(
+                depth + 1,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 }
diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java
 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java
index 9e691239..e1ec3304 100644
--- 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java
+++ 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java
@@ -19,251 +19,64 @@
 package org.eclipse.aether.util.graph.manager;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Objects;
 
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.artifact.ArtifactProperties;
-import org.eclipse.aether.collection.DependencyCollectionContext;
-import org.eclipse.aether.collection.DependencyManagement;
 import org.eclipse.aether.collection.DependencyManager;
-import org.eclipse.aether.graph.Dependency;
 import org.eclipse.aether.graph.Exclusion;
-import org.eclipse.aether.util.artifact.JavaScopes;
-
-import static java.util.Objects.requireNonNull;
 
 /**
  * A dependency manager managing transitive dependencies supporting transitive 
dependency management.
+ * <p>
+ * This manager is similar to "classic", it has {@code 
deriveUntil=Integer.MAX_VALUE} (unlike 2 as in "classic") and
+ * {@code applyFrom=2}.
  *
  * @author Christian Schulte
  * @since 1.4.0
  */
-public final class TransitiveDependencyManager implements DependencyManager {
-
-    private final Map<Object, String> managedVersions;
-
-    private final Map<Object, String> managedScopes;
-
-    private final Map<Object, Boolean> managedOptionals;
-
-    private final Map<Object, String> managedLocalPaths;
-
-    private final Map<Object, Collection<Exclusion>> managedExclusions;
-
-    private final int depth;
-
-    private int hashCode;
-
+public final class TransitiveDependencyManager extends 
AbstractDependencyManager {
     /**
      * Creates a new dependency manager without any management information.
      */
     public TransitiveDependencyManager() {
-        this(
-                0,
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Boolean>emptyMap(),
-                Collections.<Object, String>emptyMap(),
-                Collections.<Object, Collection<Exclusion>>emptyMap());
+        super(Integer.MAX_VALUE, 2);
     }
 
+    @SuppressWarnings("checkstyle:ParameterNumber")
     private TransitiveDependencyManager(
-            final int depth,
-            final Map<Object, String> managedVersions,
-            final Map<Object, String> managedScopes,
-            final Map<Object, Boolean> managedOptionals,
-            final Map<Object, String> managedLocalPaths,
-            final Map<Object, Collection<Exclusion>> managedExclusions) {
-        super();
-        this.depth = depth;
-        this.managedVersions = managedVersions;
-        this.managedScopes = managedScopes;
-        this.managedOptionals = managedOptionals;
-        this.managedLocalPaths = managedLocalPaths;
-        this.managedExclusions = managedExclusions;
-    }
-
-    public DependencyManager deriveChildManager(final 
DependencyCollectionContext context) {
-        requireNonNull(context, "context cannot be null");
-        Map<Object, String> versions = managedVersions;
-        Map<Object, String> scopes = managedScopes;
-        Map<Object, Boolean> optionals = managedOptionals;
-        Map<Object, String> localPaths = managedLocalPaths;
-        Map<Object, Collection<Exclusion>> exclusions = managedExclusions;
-
-        for (Dependency managedDependency : context.getManagedDependencies()) {
-            Artifact artifact = managedDependency.getArtifact();
-            Object key = getKey(artifact);
-
-            String version = artifact.getVersion();
-            if (!version.isEmpty() && !versions.containsKey(key)) {
-                if (versions == managedVersions) {
-                    versions = new HashMap<>(managedVersions);
-                }
-                versions.put(key, version);
-            }
-
-            String scope = managedDependency.getScope();
-            if (!scope.isEmpty() && !scopes.containsKey(key)) {
-                if (scopes == this.managedScopes) {
-                    scopes = new HashMap<>(this.managedScopes);
-                }
-                scopes.put(key, scope);
-            }
-
-            Boolean optional = managedDependency.getOptional();
-            if (optional != null && !optionals.containsKey(key)) {
-                if (optionals == managedOptionals) {
-                    optionals = new HashMap<>(managedOptionals);
-                }
-                optionals.put(key, optional);
-            }
-
-            String localPath = 
managedDependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, 
null);
-            if (localPath != null && !localPaths.containsKey(key)) {
-                if (localPaths == this.managedLocalPaths) {
-                    localPaths = new HashMap<>(managedLocalPaths);
-                }
-                localPaths.put(key, localPath);
-            }
-
-            if (!managedDependency.getExclusions().isEmpty()) {
-                if (exclusions == managedExclusions) {
-                    exclusions = new HashMap<>(managedExclusions);
-                }
-                Collection<Exclusion> managed = 
exclusions.computeIfAbsent(key, k -> new LinkedHashSet<>());
-                managed.addAll(managedDependency.getExclusions());
-            }
-        }
-
-        return new TransitiveDependencyManager(depth + 1, versions, scopes, 
optionals, localPaths, exclusions);
-    }
-
-    public DependencyManagement manageDependency(Dependency dependency) {
-        requireNonNull(dependency, "dependency cannot be null");
-        DependencyManagement management = null;
-
-        Object key = getKey(dependency.getArtifact());
-
-        if (depth >= 2) {
-            String version = managedVersions.get(key);
-            if (version != null) {
-                management = new DependencyManagement();
-                management.setVersion(version);
-            }
-
-            String scope = managedScopes.get(key);
-            if (scope != null) {
-                if (management == null) {
-                    management = new DependencyManagement();
-                }
-                management.setScope(scope);
-
-                if (!JavaScopes.SYSTEM.equals(scope)
-                        && 
dependency.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, null) != 
null) {
-                    Map<String, String> properties =
-                            new 
HashMap<>(dependency.getArtifact().getProperties());
-                    properties.remove(ArtifactProperties.LOCAL_PATH);
-                    management.setProperties(properties);
-                }
-            }
-
-            if ((JavaScopes.SYSTEM.equals(scope))
-                    || (scope == null && 
JavaScopes.SYSTEM.equals(dependency.getScope()))) {
-                String localPath = managedLocalPaths.get(key);
-                if (localPath != null) {
-                    if (management == null) {
-                        management = new DependencyManagement();
-                    }
-                    Map<String, String> properties =
-                            new 
HashMap<>(dependency.getArtifact().getProperties());
-                    properties.put(ArtifactProperties.LOCAL_PATH, localPath);
-                    management.setProperties(properties);
-                }
-            }
-
-            Boolean optional = managedOptionals.get(key);
-            if (optional != null) {
-                if (management == null) {
-                    management = new DependencyManagement();
-                }
-                management.setOptional(optional);
-            }
-        }
-
-        Collection<Exclusion> exclusions = managedExclusions.get(key);
-        if (exclusions != null) {
-            if (management == null) {
-                management = new DependencyManagement();
-            }
-            Collection<Exclusion> result = new 
LinkedHashSet<>(dependency.getExclusions());
-            result.addAll(exclusions);
-            management.setExclusions(result);
-        }
-
-        return management;
-    }
-
-    private Object getKey(Artifact a) {
-        return new Key(a);
+            int depth,
+            int deriveUntil,
+            int applyFrom,
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions) {
+        super(
+                depth,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        boolean equal = obj instanceof TransitiveDependencyManager;
-
-        if (equal) {
-            final TransitiveDependencyManager that = 
(TransitiveDependencyManager) obj;
-            return depth == that.depth
-                    && Objects.equals(managedVersions, that.managedVersions)
-                    && Objects.equals(managedScopes, that.managedScopes)
-                    && Objects.equals(managedOptionals, that.managedOptionals)
-                    && Objects.equals(managedExclusions, 
that.managedExclusions);
-        }
-
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        if (hashCode == 0) {
-            hashCode = Objects.hash(depth, managedVersions, managedScopes, 
managedOptionals, managedExclusions);
-        }
-        return hashCode;
-    }
-
-    static class Key {
-        private final Artifact artifact;
-
-        private final int hashCode;
-
-        Key(final Artifact artifact) {
-            this.artifact = artifact;
-            this.hashCode = Objects.hash(artifact.getGroupId(), 
artifact.getArtifactId());
-        }
-
-        @Override
-        public boolean equals(final Object obj) {
-            boolean equal = obj instanceof Key;
-
-            if (equal) {
-                final Key that = (Key) obj;
-                return Objects.equals(artifact.getArtifactId(), 
that.artifact.getArtifactId())
-                        && Objects.equals(artifact.getGroupId(), 
that.artifact.getGroupId())
-                        && Objects.equals(artifact.getExtension(), 
that.artifact.getExtension())
-                        && Objects.equals(artifact.getClassifier(), 
that.artifact.getClassifier());
-            }
-
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return this.hashCode;
-        }
+    protected DependencyManager newInstance(
+            Map<Object, String> managedVersions,
+            Map<Object, String> managedScopes,
+            Map<Object, Boolean> managedOptionals,
+            Map<Object, String> managedLocalPaths,
+            Map<Object, Collection<Exclusion>> managedExclusions) {
+        return new TransitiveDependencyManager(
+                depth + 1,
+                deriveUntil,
+                applyFrom,
+                managedVersions,
+                managedScopes,
+                managedOptionals,
+                managedLocalPaths,
+                managedExclusions);
     }
 }
diff --git 
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManagerTest.java
 
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManagerTest.java
deleted file mode 100644
index 2db88c09..00000000
--- 
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/ClassicDependencyManagerTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.eclipse.aether.util.graph.manager;
-
-import java.util.Arrays;
-
-import org.eclipse.aether.RepositorySystemSession;
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.artifact.DefaultArtifact;
-import org.eclipse.aether.collection.DependencyCollectionContext;
-import org.eclipse.aether.collection.DependencyManagement;
-import org.eclipse.aether.collection.DependencyManager;
-import org.eclipse.aether.graph.Dependency;
-import org.eclipse.aether.internal.test.util.TestUtils;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-public class ClassicDependencyManagerTest {
-
-    private final Artifact A = new DefaultArtifact("test", "a", "", "");
-
-    private final Artifact A1 = new DefaultArtifact("test", "a", "", "1");
-
-    private final Artifact B = new DefaultArtifact("test", "b", "", "");
-
-    private final Artifact B1 = new DefaultArtifact("test", "b", "", "1");
-
-    private RepositorySystemSession session;
-
-    private DependencyCollectionContext newContext(Dependency... 
managedDependencies) {
-        return TestUtils.newCollectionContext(session, null, 
Arrays.asList(managedDependencies));
-    }
-
-    @BeforeEach
-    void setUp() {
-        session = TestUtils.newSession();
-    }
-
-    @Test
-    void testManageOptional() {
-        DependencyManager manager = new ClassicDependencyManager();
-
-        manager = manager.deriveChildManager(newContext(new Dependency(A, 
null, null), new Dependency(B, null, true)));
-        DependencyManagement mngt;
-        mngt = manager.manageDependency(new Dependency(A1, null));
-        assertNull(mngt);
-        mngt = manager.manageDependency(new Dependency(B1, null));
-        assertNull(mngt);
-
-        manager = manager.deriveChildManager(newContext());
-        mngt = manager.manageDependency(new Dependency(A1, null));
-        assertNull(mngt);
-        mngt = manager.manageDependency(new Dependency(B1, null));
-        assertNotNull(mngt);
-        assertEquals(Boolean.TRUE, mngt.getOptional());
-    }
-}
diff --git 
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/DependencyManagerTest.java
 
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/DependencyManagerTest.java
new file mode 100644
index 00000000..f2aab830
--- /dev/null
+++ 
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/manager/DependencyManagerTest.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.util.graph.manager;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencyManagement;
+import org.eclipse.aether.collection.DependencyManager;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.Exclusion;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * UT for {@link DependencyManager} implementations.
+ */
+public class DependencyManagerTest {
+
+    private final Artifact A1 = new DefaultArtifact("test", "a", "", "1");
+
+    private final Artifact A2 = new DefaultArtifact("test", "a", "", "2");
+
+    private final Artifact B1 = new DefaultArtifact("test", "b", "", "1");
+
+    private final Artifact C1 = new DefaultArtifact("test", "c", "", "1");
+
+    private final Artifact D1 = new DefaultArtifact("test", "d", "", "1");
+
+    private final Artifact E1 = new DefaultArtifact("test", "e", "", "1");
+
+    private final Artifact E2 = new DefaultArtifact("test", "e", "", "2");
+
+    private final Exclusion EXCLUSION = new Exclusion("test", "excluded", 
null, null);
+
+    private RepositorySystemSession session;
+
+    private DependencyCollectionContext newContext(Dependency... 
managedDependencies) {
+        return TestUtils.newCollectionContext(session, null, 
Arrays.asList(managedDependencies));
+    }
+
+    @BeforeEach
+    void setUp() {
+        session = TestUtils.newSession();
+    }
+
+    @Test
+    void testClassic() {
+        DependencyManager manager = new ClassicDependencyManager();
+        DependencyManagement mngt;
+
+        // depth=1: only exclusion applied, nothing more
+        manager = manager.deriveChildManager(newContext(
+                new Dependency(A2, null, null),
+                new Dependency(B1, null, true),
+                new Dependency(C1, "newscope", null),
+                new Dependency(D1, null, null, 
Collections.singleton(EXCLUSION))));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=2: all applied
+        manager = manager.deriveChildManager(newContext());
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=3: all existing applied, new depMgt ignored, carried on only 
what we have so far
+        manager = manager.deriveChildManager(newContext(new Dependency(E2, 
null, null)));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+    }
+
+    @Test
+    void testTransitive() {
+        DependencyManager manager = new TransitiveDependencyManager();
+        DependencyManagement mngt;
+
+        // depth=1: only exclusion applied, nothing more
+        manager = manager.deriveChildManager(newContext(
+                new Dependency(A2, null, null),
+                new Dependency(B1, null, true),
+                new Dependency(C1, "newscope", null),
+                new Dependency(D1, null, null, 
Collections.singleton(EXCLUSION))));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNull(mngt);
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=2: all applied
+        manager = manager.deriveChildManager(newContext());
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=3: all existing applied, new depMgt processed, carried on
+        manager = manager.deriveChildManager(newContext(new Dependency(E2, 
null, null)));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), E2.getVersion());
+    }
+
+    @Test
+    void testDefault() {
+        DependencyManager manager = new DefaultDependencyManager();
+        DependencyManagement mngt;
+
+        // depth=1: all applied
+        manager = manager.deriveChildManager(newContext(
+                new Dependency(A2, null, null),
+                new Dependency(B1, null, true),
+                new Dependency(C1, "newscope", null),
+                new Dependency(D1, null, null, 
Collections.singleton(EXCLUSION))));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=2: all applied
+        manager = manager.deriveChildManager(newContext());
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNull(mngt);
+
+        // depth=3: all existing applied, new depMgt processed, carried on
+        manager = manager.deriveChildManager(newContext(new Dependency(E2, 
null, null)));
+        mngt = manager.manageDependency(new Dependency(A1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), A2.getVersion());
+        mngt = manager.manageDependency(new Dependency(B1, null));
+        assertNotNull(mngt);
+        assertEquals(Boolean.TRUE, mngt.getOptional());
+        mngt = manager.manageDependency(new Dependency(C1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getScope(), "newscope");
+        mngt = manager.manageDependency(new Dependency(D1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getExclusions(), Collections.singleton(EXCLUSION));
+        mngt = manager.manageDependency(new Dependency(E1, null));
+        assertNotNull(mngt);
+        assertEquals(mngt.getVersion(), E2.getVersion());
+    }
+}

Reply via email to