diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyManagement.java b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyManagement.java index 71bfd51029..59a0e75546 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyManagement.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyManagement.java @@ -19,6 +19,8 @@ package org.eclipse.aether.collection; import java.util.Collection; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.Map; import org.eclipse.aether.graph.Exclusion; @@ -29,22 +31,46 @@ * @see DependencyManager#manageDependency(org.eclipse.aether.graph.Dependency) */ public final class DependencyManagement { + /** + * Enumeration of manageable attributes, attributes that can be subjected to dependency management. + * + * @since 2.0.19 + */ + public enum Subject { + VERSION, + SCOPE, + OPTIONAL, + EXCLUSIONS, + PROPERTIES + } - private String version; - - private String scope; - - private Boolean optional; - - private Collection exclusions; - - private Map properties; + private final EnumMap managedValues; + private final EnumSet managedEnforced; /** * Creates an empty management update. */ public DependencyManagement() { - // enables default constructor + this.managedValues = new EnumMap<>(Subject.class); + this.managedEnforced = EnumSet.noneOf(Subject.class); + } + + /** + * Returns {@code true} if passed in subject is managed. + * + * @since 2.0.19 + */ + public boolean isManagedSubject(Subject subject) { + return managedValues.containsKey(subject); + } + + /** + * Returns {@code true} if passed in subject is managed and is enforced. + * + * @since 2.0.19 + */ + public boolean isManagedSubjectEnforced(Subject subject) { + return isManagedSubject(subject) && managedEnforced.contains(subject); } /** @@ -54,7 +80,7 @@ public DependencyManagement() { * remain unchanged. */ public String getVersion() { - return version; + return (String) managedValues.get(Subject.VERSION); } /** @@ -62,10 +88,23 @@ public String getVersion() { * * @param version The new version, may be {@code null} if the version is not managed. * @return This management update for chaining, never {@code null}. + * @deprecated Use {@link #setVersion(String, boolean)} instead. */ + @Deprecated public DependencyManagement setVersion(String version) { - this.version = version; - return this; + return setVersion(version, true); + } + + /** + * Sets the new version to apply to the dependency. + * + * @param version The new version, may be {@code null} if the version is not managed. + * @param enforced The enforcement of new value. + * @return This management update for chaining, never {@code null}. + * @since 2.0.19 + */ + public DependencyManagement setVersion(String version, boolean enforced) { + return set(Subject.VERSION, version, enforced); } /** @@ -75,7 +114,7 @@ public DependencyManagement setVersion(String version) { * unchanged. */ public String getScope() { - return scope; + return (String) managedValues.get(Subject.SCOPE); } /** @@ -83,10 +122,23 @@ public String getScope() { * * @param scope The new scope, may be {@code null} if the scope is not managed. * @return This management update for chaining, never {@code null}. + * @deprecated Use {@link #setScope(String, boolean)} instead. */ + @Deprecated public DependencyManagement setScope(String scope) { - this.scope = scope; - return this; + return setScope(scope, true); + } + + /** + * Sets the new scope to apply to the dependency. + * + * @param scope The new scope, may be {@code null} if the scope is not managed. + * @param enforced The enforcement of new value. + * @return This management update for chaining, never {@code null}. + * @since 2.0.19 + */ + public DependencyManagement setScope(String scope, boolean enforced) { + return set(Subject.SCOPE, scope, enforced); } /** @@ -96,7 +148,7 @@ public DependencyManagement setScope(String scope) { * dependency should remain unchanged. */ public Boolean getOptional() { - return optional; + return (Boolean) managedValues.get(Subject.OPTIONAL); } /** @@ -104,10 +156,23 @@ public Boolean getOptional() { * * @param optional The optional flag, may be {@code null} if the flag is not managed. * @return This management update for chaining, never {@code null}. + * @deprecated Use {@link #setOptional(Boolean, boolean)} instead. */ + @Deprecated public DependencyManagement setOptional(Boolean optional) { - this.optional = optional; - return this; + return setOptional(optional, true); + } + + /** + * Sets the new optional flag to apply to the dependency. + * + * @param optional The optional flag, may be {@code null} if the flag is not managed. + * @param enforced The enforcement of new value. + * @return This management update for chaining, never {@code null}. + * @since 2.0.19 + */ + public DependencyManagement setOptional(Boolean optional, boolean enforced) { + return set(Subject.OPTIONAL, optional, enforced); } /** @@ -118,8 +183,9 @@ public DependencyManagement setOptional(Boolean optional) { * @return The new exclusions or {@code null} if the exclusions are not managed and the existing dependency * exclusions should remain unchanged. */ + @SuppressWarnings("unchecked") public Collection getExclusions() { - return exclusions; + return (Collection) managedValues.get(Subject.EXCLUSIONS); } /** @@ -129,10 +195,25 @@ public Collection getExclusions() { * * @param exclusions The new exclusions, may be {@code null} if the exclusions are not managed. * @return This management update for chaining, never {@code null}. + * @deprecated Use {@link #setExclusions(Collection, boolean)} instead. */ + @Deprecated public DependencyManagement setExclusions(Collection exclusions) { - this.exclusions = exclusions; - return this; + return setExclusions(exclusions, true); + } + + /** + * Sets the new exclusions to apply to the dependency. Note that this collection denotes the complete set of + * exclusions for the dependency, i.e. the dependency manager controls whether any existing exclusions get merged + * with information from dependency management or overridden by it. + * + * @param exclusions The new exclusions, may be {@code null} if the exclusions are not managed. + * @param enforced The enforcement of new value. + * @return This management update for chaining, never {@code null}. + * @since 2.0.19 + */ + public DependencyManagement setExclusions(Collection exclusions, boolean enforced) { + return set(Subject.EXCLUSIONS, exclusions, enforced); } /** @@ -143,8 +224,9 @@ public DependencyManagement setExclusions(Collection exclusions) { * @return The new artifact properties or {@code null} if the properties are not managed and the existing properties * should remain unchanged. */ + @SuppressWarnings("unchecked") public Map getProperties() { - return properties; + return (Map) managedValues.get(Subject.PROPERTIES); } /** @@ -154,9 +236,42 @@ public Map getProperties() { * * @param properties The new artifact properties, may be {@code null} if the properties are not managed. * @return This management update for chaining, never {@code null}. + * @deprecated Use {@link #setProperties(Map, boolean)} instead. */ + @Deprecated public DependencyManagement setProperties(Map properties) { - this.properties = properties; + return setProperties(properties, true); + } + + /** + * Sets the new properties to apply to the dependency. Note that this map denotes the complete set of properties, + * i.e. the dependency manager controls whether any existing properties get merged with the information from + * dependency management or overridden by it. + * + * @param properties The new artifact properties, may be {@code null} if the properties are not managed. + * @param enforced The enforcement of new value. + * @return This management update for chaining, never {@code null}. + * @since 2.0.19 + */ + public DependencyManagement setProperties(Map properties, boolean enforced) { + return set(Subject.PROPERTIES, properties, enforced); + } + + /** + * Generic but private setter that applies common logic. + */ + private DependencyManagement set(Subject subject, Object value, boolean enforced) { + if (value == null) { + this.managedValues.remove(subject); + this.managedEnforced.remove(subject); + } else { + this.managedValues.put(subject, value); + if (enforced) { + this.managedEnforced.add(subject); + } else { + this.managedEnforced.remove(subject); + } + } return this; } } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java index 3d7c4fde0b..2117b089ef 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java @@ -21,11 +21,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.version.Version; import org.eclipse.aether.version.VersionConstraint; @@ -36,6 +38,7 @@ * A node within a dependency graph. */ public final class DefaultDependencyNode implements DependencyNode { + private static final DependencyManagement.Subject[] SUBJECTS = DependencyManagement.Subject.values(); private List children; @@ -51,7 +54,7 @@ public final class DefaultDependencyNode implements DependencyNode { private Version version; - private byte managedBits; + private Map managedSubjects; private List repositories; @@ -66,13 +69,14 @@ public final class DefaultDependencyNode implements DependencyNode { */ public DefaultDependencyNode(Dependency dependency) { this.dependency = dependency; - artifact = (dependency != null) ? dependency.getArtifact() : null; - children = new ArrayList<>(0); - aliases = Collections.emptyList(); - relocations = Collections.emptyList(); - repositories = Collections.emptyList(); - context = ""; - data = Collections.emptyMap(); + this.artifact = (dependency != null) ? dependency.getArtifact() : null; + this.children = new ArrayList<>(0); + this.aliases = Collections.emptyList(); + this.relocations = Collections.emptyList(); + this.managedSubjects = null; + this.repositories = Collections.emptyList(); + this.context = ""; + this.data = Collections.emptyMap(); } /** @@ -84,12 +88,13 @@ public DefaultDependencyNode(Dependency dependency) { */ public DefaultDependencyNode(Artifact artifact) { this.artifact = artifact; - children = new ArrayList<>(0); - aliases = Collections.emptyList(); - relocations = Collections.emptyList(); - repositories = Collections.emptyList(); - context = ""; - data = Collections.emptyMap(); + this.children = new ArrayList<>(0); + this.aliases = Collections.emptyList(); + this.relocations = Collections.emptyList(); + this.managedSubjects = null; + this.repositories = Collections.emptyList(); + this.context = ""; + this.data = Collections.emptyMap(); } /** @@ -99,12 +104,23 @@ public DefaultDependencyNode(Artifact artifact) { * @param node The node to copy, must not be {@code null}. */ public DefaultDependencyNode(DependencyNode node) { - dependency = node.getDependency(); - artifact = node.getArtifact(); - children = new ArrayList<>(0); + this.dependency = node.getDependency(); + this.artifact = node.getArtifact(); + this.children = new ArrayList<>(0); setAliases(node.getAliases()); setRequestContext(node.getRequestContext()); - setManagedBits(node.getManagedBits()); + + EnumMap managedSubjects = null; + for (DependencyManagement.Subject subject : SUBJECTS) { + if (node.isManagedSubject(subject)) { + if (managedSubjects == null) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); + } + managedSubjects.put(subject, node.isManagedSubjectEnforced(subject)); + } + } + setManagedSubjects(managedSubjects); + setRelocations(node.getRelocations()); setRepositories(node.getRepositories()); setVersion(node.getVersion()); @@ -113,10 +129,12 @@ public DefaultDependencyNode(DependencyNode node) { setData(data.isEmpty() ? null : new HashMap<>(data)); } + @Override public List getChildren() { return children; } + @Override public void setChildren(List children) { if (children == null) { this.children = new ArrayList<>(0); @@ -125,14 +143,17 @@ public void setChildren(List children) { } } + @Override public Dependency getDependency() { return dependency; } + @Override public Artifact getArtifact() { return artifact; } + @Override public void setArtifact(Artifact artifact) { if (dependency == null) { throw new IllegalStateException("node does not have a dependency"); @@ -141,6 +162,7 @@ public void setArtifact(Artifact artifact) { this.artifact = dependency.getArtifact(); } + @Override public List getRelocations() { return relocations; } @@ -158,6 +180,7 @@ public void setRelocations(List relocations) { } } + @Override public Collection getAliases() { return aliases; } @@ -175,6 +198,7 @@ public void setAliases(Collection aliases) { } } + @Override public VersionConstraint getVersionConstraint() { return versionConstraint; } @@ -188,6 +212,7 @@ public void setVersionConstraint(VersionConstraint versionConstraint) { this.versionConstraint = versionConstraint; } + @Override public Version getVersion() { return version; } @@ -201,6 +226,7 @@ public void setVersion(Version version) { this.version = version; } + @Override public void setScope(String scope) { if (dependency == null) { throw new IllegalStateException("node does not have a dependency"); @@ -208,6 +234,7 @@ public void setScope(String scope) { dependency = dependency.setScope(scope); } + @Override public void setOptional(Boolean optional) { if (dependency == null) { throw new IllegalStateException("node does not have a dependency"); @@ -215,20 +242,71 @@ public void setOptional(Boolean optional) { dependency = dependency.setOptional(optional); } + @Override public int getManagedBits() { - return managedBits; + byte res = 0; + if (isManagedSubject(DependencyManagement.Subject.VERSION)) { + res |= DependencyNode.MANAGED_VERSION; + } + if (isManagedSubject(DependencyManagement.Subject.SCOPE)) { + res |= DependencyNode.MANAGED_SCOPE; + } + if (isManagedSubject(DependencyManagement.Subject.OPTIONAL)) { + res |= DependencyNode.MANAGED_OPTIONAL; + } + if (isManagedSubject(DependencyManagement.Subject.PROPERTIES)) { + res |= DependencyNode.MANAGED_PROPERTIES; + } + if (isManagedSubject(DependencyManagement.Subject.EXCLUSIONS)) { + res |= DependencyNode.MANAGED_EXCLUSIONS; + } + return res; } - /** - * Sets a bit field indicating which attributes of this node were subject to dependency management. - * - * @param managedBits The bit field indicating the managed attributes or {@code 0} if dependency management wasn't - * applied. - */ + @Deprecated public void setManagedBits(int managedBits) { - this.managedBits = (byte) (managedBits & 0x1F); + if (managedBits == 0) { + setManagedSubjects(null); + return; + } + EnumMap subjects = new EnumMap<>(DependencyManagement.Subject.class); + if ((managedBits & DependencyNode.MANAGED_VERSION) != 0) { + subjects.put(DependencyManagement.Subject.VERSION, true); + } + if ((managedBits & DependencyNode.MANAGED_SCOPE) != 0) { + subjects.put(DependencyManagement.Subject.SCOPE, true); + } + if ((managedBits & DependencyNode.MANAGED_OPTIONAL) != 0) { + subjects.put(DependencyManagement.Subject.OPTIONAL, true); + } + if ((managedBits & DependencyNode.MANAGED_PROPERTIES) != 0) { + subjects.put(DependencyManagement.Subject.PROPERTIES, true); + } + if ((managedBits & DependencyNode.MANAGED_EXCLUSIONS) != 0) { + subjects.put(DependencyManagement.Subject.EXCLUSIONS, true); + } + setManagedSubjects(subjects.isEmpty() ? null : subjects); + } + + public void setManagedSubjects(Map managedSubjects) { + if (managedSubjects == null || managedSubjects.isEmpty()) { + this.managedSubjects = null; + } else { + this.managedSubjects = managedSubjects; + } + } + + @Override + public boolean isManagedSubject(DependencyManagement.Subject subject) { + return managedSubjects != null && managedSubjects.containsKey(subject); + } + + @Override + public boolean isManagedSubjectEnforced(DependencyManagement.Subject subject) { + return isManagedSubject(subject) && managedSubjects.getOrDefault(subject, false); } + @Override public List getRepositories() { return repositories; } @@ -246,18 +324,22 @@ public void setRepositories(List repositories) { } } + @Override public String getRequestContext() { return context; } + @Override public void setRequestContext(String context) { this.context = (context != null) ? context.intern() : ""; } + @Override public Map getData() { return data; } + @Override public void setData(Map data) { if (data == null) { this.data = Collections.emptyMap(); @@ -266,6 +348,7 @@ public void setData(Map data) { } } + @Override public void setData(Object key, Object value) { requireNonNull(key, "key cannot be null"); @@ -285,6 +368,7 @@ public void setData(Object key, Object value) { } } + @Override public boolean accept(DependencyVisitor visitor) { if (Thread.currentThread().isInterrupted()) { throw new RuntimeException(new InterruptedException("Thread interrupted")); diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java index 7471639852..d0dbfc7c5c 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java @@ -23,6 +23,7 @@ import java.util.Map; import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.version.Version; import org.eclipse.aether.version.VersionConstraint; @@ -38,40 +39,49 @@ * @noextend This interface is not intended to be extended by clients. */ public interface DependencyNode { - /** * A bit flag indicating the dependency version was subject to dependency management * * @see #getManagedBits() + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int MANAGED_VERSION = 0x01; /** * A bit flag indicating the dependency scope was subject to dependency management * * @see #getManagedBits() + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int MANAGED_SCOPE = 0x02; /** * A bit flag indicating the optional flag was subject to dependency management * * @see #getManagedBits() + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int MANAGED_OPTIONAL = 0x04; /** * A bit flag indicating the artifact properties were subject to dependency management * * @see #getManagedBits() + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int MANAGED_PROPERTIES = 0x08; /** * A bit flag indicating the exclusions were subject to dependency management * * @see #getManagedBits() + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int MANAGED_EXCLUSIONS = 0x10; /** @@ -170,9 +180,46 @@ public interface DependencyNode { * @return A bit field containing any of the bits {@link #MANAGED_VERSION}, {@link #MANAGED_SCOPE}, * {@link #MANAGED_OPTIONAL}, {@link #MANAGED_PROPERTIES} and {@link #MANAGED_EXCLUSIONS} if the * corresponding attribute was set via dependency management. + * @deprecated Use {@link #isManagedSubject(DependencyManagement.Subject)} and {@link #isManagedSubjectEnforced(DependencyManagement.Subject)} instead. */ + @Deprecated int getManagedBits(); + /** + * Returns {@code true} if given subject is managed. + * + * @param subject the {@link org.eclipse.aether.collection.DependencyManagement.Subject}, must not be {@code null}. + * @see org.eclipse.aether.collection.DependencyManagement.Subject + * @since 2.0.19 + */ + default boolean isManagedSubject(DependencyManagement.Subject subject) { + switch (subject) { + case VERSION: + return (getManagedBits() & MANAGED_VERSION) != 0; + case SCOPE: + return (getManagedBits() & MANAGED_SCOPE) != 0; + case OPTIONAL: + return (getManagedBits() & MANAGED_OPTIONAL) != 0; + case PROPERTIES: + return (getManagedBits() & MANAGED_PROPERTIES) != 0; + case EXCLUSIONS: + return (getManagedBits() & MANAGED_EXCLUSIONS) != 0; + default: + throw new IllegalArgumentException("Unknown subject: " + subject.name()); + } + } + + /** + * Returns {@code true} if given subject is managed with enforcing modality on this node. + * + * @param subject the {@link org.eclipse.aether.collection.DependencyManagement.Subject}, must not be {@code null}. + * @see org.eclipse.aether.collection.DependencyManagement.Subject + * @since 2.0.19 + */ + default boolean isManagedSubjectEnforced(DependencyManagement.Subject subject) { + return isManagedSubject(subject); + } + /** * Gets the remote repositories from which this node's artifact shall be resolved. * diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java index d7cf77b23e..55fea8bdab 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java @@ -37,6 +37,7 @@ import org.eclipse.aether.collection.DependencyCollectionChecker; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.collection.DependencyManager; import org.eclipse.aether.collection.DependencyTraverser; import org.eclipse.aether.collection.VersionFilter; import org.eclipse.aether.graph.DefaultDependencyNode; @@ -387,8 +388,16 @@ protected static String getId(Artifact a) { return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); } + protected PremanagedDependency createPremanagedDependency( + DependencyManager depManager, + Dependency dependency, + boolean disableVersionManagement, + boolean premanagedState) { + return PremanagedDependency.create(depManager, dependency, disableVersionManagement, premanagedState); + } + @SuppressWarnings("checkstyle:parameternumber") - protected static DefaultDependencyNode createDependencyNode( + protected DefaultDependencyNode createDependencyNode( List relocations, PremanagedDependency preManaged, VersionRangeResult rangeResult, @@ -408,7 +417,7 @@ protected static DefaultDependencyNode createDependencyNode( return child; } - protected static DefaultDependencyNode createDependencyNode( + protected DefaultDependencyNode createDependencyNodeCycle( List relocations, PremanagedDependency preManaged, VersionRangeResult rangeResult, diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/PremanagedDependency.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/PremanagedDependency.java index c0a6ef3bb6..10e6d4725c 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/PremanagedDependency.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/PremanagedDependency.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.Map; @@ -29,7 +30,6 @@ import org.eclipse.aether.collection.DependencyManager; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; @@ -38,27 +38,30 @@ */ public class PremanagedDependency { - final String premanagedVersion; + private final String premanagedVersion; - final String premanagedScope; + private final String premanagedScope; - final Boolean premanagedOptional; + private final Boolean premanagedOptional; /** * @since 1.1.0 */ - final Collection premanagedExclusions; + private final Collection premanagedExclusions; /** * @since 1.1.0 */ - final Map premanagedProperties; + private final Map premanagedProperties; - final int managedBits; + /** + * @since 2.0.19 + */ + private final EnumMap managedSubjects; - final Dependency managedDependency; + private final Dependency managedDependency; - final boolean premanagedState; + private final boolean premanagedState; @SuppressWarnings("checkstyle:parameternumber") PremanagedDependency( @@ -67,7 +70,7 @@ public class PremanagedDependency { Boolean premanagedOptional, Collection premanagedExclusions, Map premanagedProperties, - int managedBits, + EnumMap managedSubjects, Dependency managedDependency, boolean premanagedState) { this.premanagedVersion = premanagedVersion; @@ -80,7 +83,7 @@ public class PremanagedDependency { this.premanagedProperties = premanagedProperties != null ? Collections.unmodifiableMap(new HashMap<>(premanagedProperties)) : null; - this.managedBits = managedBits; + this.managedSubjects = managedSubjects; this.managedDependency = managedDependency; this.premanagedState = premanagedState; } @@ -92,7 +95,7 @@ public static PremanagedDependency create( boolean premanagedState) { DependencyManagement depMngt = depManager != null ? depManager.manageDependency(dependency) : null; - int managedBits = 0; + EnumMap managedSubjects = null; String premanagedVersion = null; String premanagedScope = null; Boolean premanagedOptional = null; @@ -101,40 +104,64 @@ public static PremanagedDependency create( if (depMngt != null) { if (depMngt.getVersion() != null && !disableVersionManagement) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); Artifact artifact = dependency.getArtifact(); premanagedVersion = artifact.getVersion(); dependency = dependency.setArtifact(artifact.setVersion(depMngt.getVersion())); - managedBits |= DependencyNode.MANAGED_VERSION; + managedSubjects.put( + DependencyManagement.Subject.VERSION, + depMngt.isManagedSubjectEnforced(DependencyManagement.Subject.VERSION)); } if (depMngt.getProperties() != null) { + if (managedSubjects == null) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); + } Artifact artifact = dependency.getArtifact(); premanagedProperties = artifact.getProperties(); dependency = dependency.setArtifact(artifact.setProperties(depMngt.getProperties())); - managedBits |= DependencyNode.MANAGED_PROPERTIES; + managedSubjects.put( + DependencyManagement.Subject.PROPERTIES, + depMngt.isManagedSubjectEnforced(DependencyManagement.Subject.PROPERTIES)); } if (depMngt.getScope() != null) { + if (managedSubjects == null) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); + } premanagedScope = dependency.getScope(); dependency = dependency.setScope(depMngt.getScope()); - managedBits |= DependencyNode.MANAGED_SCOPE; + managedSubjects.put( + DependencyManagement.Subject.SCOPE, + depMngt.isManagedSubjectEnforced(DependencyManagement.Subject.SCOPE)); } if (depMngt.getOptional() != null) { + if (managedSubjects == null) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); + } premanagedOptional = dependency.isOptional(); dependency = dependency.setOptional(depMngt.getOptional()); - managedBits |= DependencyNode.MANAGED_OPTIONAL; + managedSubjects.put( + DependencyManagement.Subject.OPTIONAL, + depMngt.isManagedSubjectEnforced(DependencyManagement.Subject.OPTIONAL)); } if (depMngt.getExclusions() != null) { + if (managedSubjects == null) { + managedSubjects = new EnumMap<>(DependencyManagement.Subject.class); + } premanagedExclusions = dependency.getExclusions(); dependency = dependency.setExclusions(depMngt.getExclusions()); - managedBits |= DependencyNode.MANAGED_EXCLUSIONS; + managedSubjects.put( + DependencyManagement.Subject.EXCLUSIONS, + depMngt.isManagedSubjectEnforced(DependencyManagement.Subject.EXCLUSIONS)); } } + return new PremanagedDependency( premanagedVersion, premanagedScope, premanagedOptional, premanagedExclusions, premanagedProperties, - managedBits, + managedSubjects, dependency, premanagedState); } @@ -144,7 +171,7 @@ public Dependency getManagedDependency() { } public void applyTo(DefaultDependencyNode child) { - child.setManagedBits(managedBits); + child.setManagedSubjects(managedSubjects); if (premanagedState) { child.setData(DependencyManagerUtils.NODE_DATA_PREMANAGED_VERSION, premanagedVersion); child.setData(DependencyManagerUtils.NODE_DATA_PREMANAGED_SCOPE, premanagedScope); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java index c7c3edecb1..26002ee974 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java @@ -212,7 +212,7 @@ protected void doCollectDependencies( managedDependencies, parents, dependency, - PremanagedDependency.create(rootDepManager, dependency, false, args.premanagedState)); + createPremanagedDependency(rootDepManager, dependency, false, args.premanagedState)); processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency()); resolveArtifactDescriptorAsync(args, processingContext, results); args.dependencyProcessingQueue.add(processingContext); @@ -275,7 +275,7 @@ private void processDependency( results.addCycle(context.parents, cycleEntry, d); DependencyNode cycleNode = context.parents.get(cycleEntry); if (cycleNode.getDependency() != null) { - DefaultDependencyNode child = createDependencyNode( + DefaultDependencyNode child = createDependencyNodeCycle( relocations, preManaged, rangeResult, version, d, descriptorResult, cycleNode); context.getParent().getChildren().add(child); continue; @@ -291,7 +291,7 @@ private void processDependency( .getArtifactId() .equals(d.getArtifact().getArtifactId()); - PremanagedDependency premanagedDependency = PremanagedDependency.create( + PremanagedDependency premanagedDependency = createPremanagedDependency( context.depManager, d, disableVersionManagementSubsequently, args.premanagedState); DependencyProcessingContext relocatedContext = new DependencyProcessingContext( context.depSelector, @@ -410,7 +410,7 @@ private void doRecurse( } RequestTrace childTrace = collectStepTrace( parentContext.trace, args.request.getRequestContext(), parents, dependency); - PremanagedDependency premanagedDependency = PremanagedDependency.create( + PremanagedDependency premanagedDependency = createPremanagedDependency( childManager, dependency, disableVersionManagement, args.premanagedState); DependencyProcessingContext processingContext = new DependencyProcessingContext( childSelector, diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java index 034d6691e2..44a9b223f0 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java @@ -191,7 +191,7 @@ private void processDependency( RequestTrace trace = collectStepTrace(parent, args.request.getRequestContext(), args.nodes.nodes, dependency); PremanagedDependency preManaged = - PremanagedDependency.create(depManager, dependency, disableVersionManagement, args.premanagedState); + createPremanagedDependency(depManager, dependency, disableVersionManagement, args.premanagedState); dependency = preManaged.getManagedDependency(); boolean noDescriptor = isLackingDescriptor(args.session, dependency.getArtifact()); @@ -231,7 +231,7 @@ private void processDependency( results.addCycle(args.nodes.nodes, cycleEntry, d); DependencyNode cycleNode = args.nodes.get(cycleEntry); if (cycleNode.getDependency() != null) { - DefaultDependencyNode child = createDependencyNode( + DefaultDependencyNode child = createDependencyNodeCycle( relocations, preManaged, rangeResult, version, d, descriptorResult, cycleNode); node.getChildren().add(child); continue; diff --git a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java index 18da45bb70..98d66fbfdb 100644 --- a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java +++ b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -36,6 +37,7 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; @@ -291,16 +293,17 @@ private DependencyNode build(DependencyNode parent, LineContext ctx, boolean isR DefaultArtifact artifact = new DefaultArtifact(def.coords, def.properties); Dependency dependency = new Dependency(artifact, def.scope, def.optional); node = new DefaultDependencyNode(dependency); - int managedBits = 0; + EnumMap managedSubjects = + new EnumMap<>(DependencyManagement.Subject.class); if (def.premanagedScope != null) { - managedBits |= DependencyNode.MANAGED_SCOPE; + managedSubjects.put(DependencyManagement.Subject.SCOPE, true); node.setData("premanaged.scope", def.premanagedScope); } if (def.premanagedVersion != null) { - managedBits |= DependencyNode.MANAGED_VERSION; + managedSubjects.put(DependencyManagement.Subject.VERSION, true); node.setData("premanaged.version", def.premanagedVersion); } - node.setManagedBits(managedBits); + node.setManagedSubjects(managedSubjects); if (def.relocations != null) { List relocations = new ArrayList<>(); for (String relocation : def.relocations) { diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index 2b2eeab358..1c67889b97 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -86,9 +86,10 @@ *

Managed Bits and Graph Transformations

*

* When a {@link org.eclipse.aether.graph.DependencyNode} becomes "managed" by any property - * provided from this manager, {@link org.eclipse.aether.graph.DependencyNode#getManagedBits()} + * provided from this manager, {@link org.eclipse.aether.graph.DependencyNode#isManagedSubject(DependencyManagement.Subject)} + * and {@link org.eclipse.aether.graph.DependencyNode#isManagedSubjectEnforced(DependencyManagement.Subject)} * will carry this information for the given property. Later graph transformations will abstain - * from modifying these properties of marked nodes (assuming the node already has the property + * from modifying these properties of marked enforced nodes (assuming the node already has the property * set to what it should have). Sometimes this is unwanted, especially for properties that need * to be inherited in the graph (values derived from parent-child context of the actual node, * like "scope" or "optional"). @@ -206,14 +207,23 @@ private boolean containsManagedVersion(Key key, MMap managedVersion return managedVersions != null && managedVersions.containsKey(key); } - private String getManagedVersion(Key key) { + /** + * Returns {@code true} if this manager represents the root level (factory or root POM level). + * Per the depth model: 0 = factory (seed), 1 = root (project POM), 2+ = descendants. + * Management entries from root level should be enforced, while those from descendants are advised. + */ + private boolean isRootManager() { + return depth <= 1; + } + + private AbstractDependencyManager getManagedVersion(Key key) { for (AbstractDependencyManager ancestor : path) { if (ancestor.managedVersions != null && ancestor.managedVersions.containsKey(key)) { - return ancestor.managedVersions.get(key); + return ancestor; } } if (depth == 1 && managedVersions != null && managedVersions.containsKey(key)) { - return managedVersions.get(key); + return this; } return null; } @@ -227,14 +237,14 @@ private boolean containsManagedScope(Key key, MMap managedScopes) { return managedScopes != null && managedScopes.containsKey(key); } - private String getManagedScope(Key key) { + private AbstractDependencyManager getManagedScope(Key key) { for (AbstractDependencyManager ancestor : path) { if (ancestor.managedScopes != null && ancestor.managedScopes.containsKey(key)) { - return ancestor.managedScopes.get(key); + return ancestor; } } if (depth == 1 && managedScopes != null && managedScopes.containsKey(key)) { - return managedScopes.get(key); + return this; } return null; } @@ -248,14 +258,14 @@ private boolean containsManagedOptional(Key key, MMap managedOptio return managedOptionals != null && managedOptionals.containsKey(key); } - private Boolean getManagedOptional(Key key) { + private AbstractDependencyManager getManagedOptional(Key key) { for (AbstractDependencyManager ancestor : path) { if (ancestor.managedOptionals != null && ancestor.managedOptionals.containsKey(key)) { - return ancestor.managedOptionals.get(key); + return ancestor; } } if (depth == 1 && managedOptionals != null && managedOptionals.containsKey(key)) { - return managedOptionals.get(key); + return this; } return null; } @@ -276,14 +286,14 @@ private boolean containsManagedLocalPath(Key key, MMap managedLocal * @param key the dependency key * @return the managed local path, or null if not managed */ - private String getManagedLocalPath(Key key) { + private AbstractDependencyManager getManagedLocalPath(Key key) { for (AbstractDependencyManager ancestor : path) { if (ancestor.managedLocalPaths != null && ancestor.managedLocalPaths.containsKey(key)) { - return ancestor.managedLocalPaths.get(key); + return ancestor; } } if (managedLocalPaths != null && managedLocalPaths.containsKey(key)) { - return managedLocalPaths.get(key); + return this; } return null; } @@ -334,22 +344,20 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) managedVersions.put(key, version); } - if (isInheritedDerived()) { - String scope = managedDependency.getScope(); - if (!scope.isEmpty() && !containsManagedScope(key, managedScopes)) { - if (managedScopes == null) { - managedScopes = MMap.emptyNotDone(); - } - managedScopes.put(key, scope); + String scope = managedDependency.getScope(); + if (!scope.isEmpty() && !containsManagedScope(key, managedScopes)) { + if (managedScopes == null) { + managedScopes = MMap.emptyNotDone(); } + managedScopes.put(key, scope); + } - Boolean optional = managedDependency.getOptional(); - if (optional != null && !containsManagedOptional(key, managedOptionals)) { - if (managedOptionals == null) { - managedOptionals = MMap.emptyNotDone(); - } - managedOptionals.put(key, optional); + Boolean optional = managedDependency.getOptional(); + if (optional != null && !containsManagedOptional(key, managedOptionals)) { + if (managedOptionals == null) { + managedOptionals = MMap.emptyNotDone(); } + managedOptionals.put(key, optional); } String localPath = systemDependencyScope == null @@ -394,58 +402,59 @@ public DependencyManagement manageDependency(Dependency dependency) { Key key = new Key(dependency.getArtifact()); if (isApplied()) { - String version = getManagedVersion(key); + AbstractDependencyManager versionOwner = getManagedVersion(key); // is managed locally by model builder // apply only rules coming from "higher" levels - if (version != null) { + if (versionOwner != null) { management = new DependencyManagement(); - management.setVersion(version); + management.setVersion(versionOwner.managedVersions.get(key), versionOwner.isRootManager()); } - String scope = getManagedScope(key); + AbstractDependencyManager scopeOwner = getManagedScope(key); // is managed locally by model builder // apply only rules coming from "higher" levels - if (scope != null) { + if (scopeOwner != null) { if (management == null) { management = new DependencyManagement(); } - management.setScope(scope); + String managedScope = scopeOwner.managedScopes.get(key); + management.setScope(managedScope, scopeOwner.isRootManager()); if (systemDependencyScope != null - && !systemDependencyScope.is(scope) + && !systemDependencyScope.is(managedScope) && systemDependencyScope.getSystemPath(dependency.getArtifact()) != null) { HashMap properties = new HashMap<>(dependency.getArtifact().getProperties()); systemDependencyScope.setSystemPath(properties, null); - management.setProperties(properties); + management.setProperties(properties, false); } } // system scope paths always applied to have them aligned // (same artifact == same path) in whole graph if (systemDependencyScope != null - && (scope != null && systemDependencyScope.is(scope) - || (scope == null && systemDependencyScope.is(dependency.getScope())))) { - String localPath = getManagedLocalPath(key); - if (localPath != null) { + && (scopeOwner != null && systemDependencyScope.is(scopeOwner.managedScopes.get(key)) + || (scopeOwner == null && systemDependencyScope.is(dependency.getScope())))) { + AbstractDependencyManager localPathOwner = getManagedLocalPath(key); + if (localPathOwner != null) { if (management == null) { management = new DependencyManagement(); } HashMap properties = new HashMap<>(dependency.getArtifact().getProperties()); - systemDependencyScope.setSystemPath(properties, localPath); - management.setProperties(properties); + systemDependencyScope.setSystemPath(properties, localPathOwner.managedLocalPaths.get(key)); + management.setProperties(properties, false); } } // optional is not managed by model builder // apply only rules coming from "higher" levels - Boolean optional = getManagedOptional(key); - if (optional != null) { + AbstractDependencyManager optionalOwner = getManagedOptional(key); + if (optionalOwner != null) { if (management == null) { management = new DependencyManagement(); } - management.setOptional(optional); + management.setOptional(optionalOwner.managedOptionals.get(key), optionalOwner.isRootManager()); } } @@ -461,7 +470,7 @@ public DependencyManagement manageDependency(Dependency dependency) { } Collection result = new LinkedHashSet<>(dependency.getExclusions()); result.addAll(exclusions); - management.setExclusions(result); + management.setExclusions(result, true); } return management; @@ -474,34 +483,6 @@ protected boolean isDerived() { return depth < deriveUntil; } - /** - * Manages dependency properties including "version", "scope", "optional", "local path", and "exclusions". - *

- * Property management behavior: - *

    - *
  • Version: Follows {@link #isDerived()} pattern. Management is applied only at higher - * levels to avoid interference with the model builder.
  • - *
  • Scope: Derived from root only due to inheritance in dependency graphs. Special handling - * for "system" scope to align artifact paths.
  • - *
  • Optional: Derived from root only due to inheritance in dependency graphs.
  • - *
  • Local path: Managed only when scope is or was set to "system" to ensure consistent - * artifact path alignment.
  • - *
  • Exclusions: Accumulated additively from root to current level throughout the entire - * dependency path.
  • - *
- *

- * Inheritance handling: Since "scope" and "optional" properties inherit through dependency - * graphs (beyond model builder scope), they are derived only from the root node. The actual manager - * implementation determines specific handling behavior. - *

- * Default behavior: Defaults to {@link #isDerived()} to maintain compatibility with - * "classic" behavior (equivalent to {@code deriveUntil=2}). For custom transitivity management, override - * this method or ensure inherited managed properties are handled during graph transformation. - */ - protected boolean isInheritedDerived() { - return isDerived(); - } - /** * Returns {@code true} if current dependency should be managed according to so far collected/derived rules. */ diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DependencyManagerUtils.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DependencyManagerUtils.java index e83fd06698..685d005f44 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DependencyManagerUtils.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/DependencyManagerUtils.java @@ -23,6 +23,7 @@ import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.Exclusion; @@ -87,7 +88,7 @@ public final class DependencyManagerUtils { * or if {@link #CONFIG_PROP_VERBOSE} was not enabled */ public static String getPremanagedVersion(DependencyNode node) { - if ((node.getManagedBits() & DependencyNode.MANAGED_VERSION) == 0) { + if (!node.isManagedSubject(DependencyManagement.Subject.VERSION)) { return null; } return cast(node.getData().get(NODE_DATA_PREMANAGED_VERSION), String.class); @@ -101,7 +102,7 @@ public static String getPremanagedVersion(DependencyNode node) { * if {@link #CONFIG_PROP_VERBOSE} was not enabled */ public static String getPremanagedScope(DependencyNode node) { - if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) == 0) { + if (!node.isManagedSubject(DependencyManagement.Subject.SCOPE)) { return null; } return cast(node.getData().get(NODE_DATA_PREMANAGED_SCOPE), String.class); @@ -115,7 +116,7 @@ public static String getPremanagedScope(DependencyNode node) { * {@link #CONFIG_PROP_VERBOSE} was not enabled */ public static Boolean getPremanagedOptional(DependencyNode node) { - if ((node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == 0) { + if (!node.isManagedSubject(DependencyManagement.Subject.OPTIONAL)) { return null; } return cast(node.getData().get(NODE_DATA_PREMANAGED_OPTIONAL), Boolean.class); @@ -131,7 +132,7 @@ public static Boolean getPremanagedOptional(DependencyNode node) { */ @SuppressWarnings("unchecked") public static Collection getPremanagedExclusions(DependencyNode node) { - if ((node.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) == 0) { + if (!node.isManagedSubject(DependencyManagement.Subject.EXCLUSIONS)) { return null; } return cast(node.getData().get(NODE_DATA_PREMANAGED_EXCLUSIONS), Collection.class); @@ -147,7 +148,7 @@ public static Collection getPremanagedExclusions(DependencyNode node) */ @SuppressWarnings("unchecked") public static Map getPremanagedProperties(DependencyNode node) { - if ((node.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) == 0) { + if (!node.isManagedSubject(DependencyManagement.Subject.PROPERTIES)) { return null; } return cast(node.getData().get(NODE_DATA_PREMANAGED_PROPERTIES), Map.class); 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 c45b64bc7e..9ad6938475 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 @@ -140,30 +140,4 @@ protected DependencyManager newInstance( managedExclusions, systemDependencyScope); } - - /** - * Controls inheritance-based property derivation for scope and optional properties. - *

- * Why scope and optional are special: In dependency graphs, these two properties - * are subject to inheritance during graph transformation (which is outside ModelBuilder's scope). - * Therefore, scope and optional are derived only from the root to prevent interference with - * inheritance logic. - *

- *

- * The inheritance problem: If we managed scope/optional from sources below the root, - * we would mark nodes as "managed" in the dependency graph. The "managed" flag means "do not touch it, - * it is as it should be", which would prevent proper inheritance application during later graph - * transformation, causing nodes to end up with incorrect scope or optional states. - *

- *

- * Special case: The "system" scope has special handling due to its unique path requirements. - *

- * - * @return true only at depth 0 (root level) to ensure inheritance-based properties are only - * derived from the root, false otherwise - */ - @Override - protected boolean isInheritedDerived() { - return depth == 0; - } } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ClassicConflictResolver.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ClassicConflictResolver.java index 4ecd53b2eb..4825e433e5 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ClassicConflictResolver.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ClassicConflictResolver.java @@ -33,6 +33,7 @@ import org.eclipse.aether.RepositoryException; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.collection.DependencyGraphTransformationContext; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; @@ -675,7 +676,7 @@ private DependencyNode parent() { } private String deriveScope(DependencyNode node, String conflictId) throws RepositoryException { - if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) != 0 + if (node.isManagedSubjectEnforced(DependencyManagement.Subject.SCOPE) || (conflictId != null && resolvedIds.containsKey(conflictId))) { return scope(node.getDependency()); } @@ -702,7 +703,7 @@ private boolean deriveOptional(DependencyNode node, String conflictId) { Dependency dep = node.getDependency(); boolean optional = (dep != null) && dep.isOptional(); if (optional - || (node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) != 0 + || node.isManagedSubjectEnforced(DependencyManagement.Subject.OPTIONAL) || (conflictId != null && resolvedIds.containsKey(conflictId))) { return optional; } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/PathConflictResolver.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/PathConflictResolver.java index 92e3bd4957..0f81c668c4 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/PathConflictResolver.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/PathConflictResolver.java @@ -34,6 +34,7 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.collection.DependencyGraphTransformationContext; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; @@ -437,12 +438,12 @@ private void pull(int levels) { private void derive(int levels, boolean winner) throws RepositoryException { if (!winner) { if (this.parent != null) { - if ((dn.getManagedBits() & DependencyNode.MANAGED_SCOPE) == 0) { + if (!dn.isManagedSubjectEnforced(DependencyManagement.Subject.SCOPE)) { ScopeContext context = new ScopeContext(this.parent.scope, this.scope); state.scopeDeriver.deriveScope(context); this.scope = context.derivedScope; } - if ((dn.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == 0) { + if (!dn.isManagedSubjectEnforced(DependencyManagement.Subject.OPTIONAL)) { if (!this.optional && this.parent.optional) { this.optional = true; } 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 index db5c4afe17..0524587bc4 100644 --- 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 @@ -20,14 +20,18 @@ import java.util.Arrays; import java.util.Collections; +import java.util.EnumMap; 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.DependencyManagement.Subject; import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.internal.test.util.TestUtils; import org.junit.jupiter.api.BeforeEach; @@ -268,4 +272,175 @@ void testDefault() { // DO NOT APPLY ONTO ITSELF assertNull(mngt); } + + /** + * Tests that root-level management produces enforced results for version, scope, and optional. + */ + @Test + void testTransitiveEnforcementFromRoot() { + DependencyManager manager = new TransitiveDependencyManager(null); + + // depth=1: derive from root with managed dependencies + manager = manager.deriveChildManager(newContext( + new Dependency(A2, null, null), new Dependency(B, null, true), new Dependency(C1, "newscope", null))); + + // depth=2: management is applied — check enforcement flags + manager = manager.deriveChildManager(newContext()); + DependencyManagement mngt = manager.manageDependency(new Dependency(A1, null)); + assertNotNull(mngt); + assertEquals(A2.getVersion(), mngt.getVersion()); + // version management from root should be enforced + assertTrue(mngt.isManagedSubject(Subject.VERSION)); + assertTrue(mngt.isManagedSubjectEnforced(Subject.VERSION)); + + mngt = manager.manageDependency(new Dependency(C1, null)); + assertNotNull(mngt); + assertEquals("newscope", mngt.getScope()); + // scope management from root should be enforced + assertTrue(mngt.isManagedSubject(Subject.SCOPE)); + assertTrue(mngt.isManagedSubjectEnforced(Subject.SCOPE)); + + mngt = manager.manageDependency(new Dependency(B1, null)); + assertNotNull(mngt); + assertEquals(Boolean.TRUE, mngt.getOptional()); + // optional management from root should be enforced + assertTrue(mngt.isManagedSubject(Subject.OPTIONAL)); + assertTrue(mngt.isManagedSubjectEnforced(Subject.OPTIONAL)); + } + + /** + * Tests that transitive (non-root) management produces advised (not enforced) results + * for scope and optional. + */ + @Test + void testTransitiveEnforcementFromNonRoot() { + DependencyManager manager = new TransitiveDependencyManager(null); + + // depth=1: no managed dependencies at root level + manager = manager.deriveChildManager(newContext()); + + // depth=2: managed dependencies introduced at transitive level + manager = manager.deriveChildManager(newContext( + new Dependency(A2, null, null), new Dependency(B, null, true), new Dependency(C1, "newscope", null))); + + // depth=3: management is applied — check enforcement flags + manager = manager.deriveChildManager(newContext()); + DependencyManagement mngt = manager.manageDependency(new Dependency(A1, null)); + assertNotNull(mngt); + assertEquals(A2.getVersion(), mngt.getVersion()); + // version management from non-root should NOT be enforced (advised) + assertTrue(mngt.isManagedSubject(Subject.VERSION)); + assertFalse(mngt.isManagedSubjectEnforced(Subject.VERSION)); + + mngt = manager.manageDependency(new Dependency(C1, null)); + assertNotNull(mngt); + assertEquals("newscope", mngt.getScope()); + // scope management from non-root should NOT be enforced (advised) + assertTrue(mngt.isManagedSubject(Subject.SCOPE)); + assertFalse(mngt.isManagedSubjectEnforced(Subject.SCOPE)); + + mngt = manager.manageDependency(new Dependency(B1, null)); + assertNotNull(mngt); + assertEquals(Boolean.TRUE, mngt.getOptional()); + // optional management from non-root should NOT be enforced (advised) + assertTrue(mngt.isManagedSubject(Subject.OPTIONAL)); + assertFalse(mngt.isManagedSubjectEnforced(Subject.OPTIONAL)); + } + + /** + * Tests that classic dependency manager also produces enforced results from root. + */ + @Test + void testClassicEnforcementFromRoot() { + DependencyManager manager = new ClassicDependencyManager(null); + + // depth=1: derive from root + manager = manager.deriveChildManager( + newContext(new Dependency(A2, null, null), new Dependency(C1, "newscope", null))); + + // depth=2: management is applied + manager = manager.deriveChildManager(newContext()); + DependencyManagement mngt = manager.manageDependency(new Dependency(A1, null)); + assertNotNull(mngt); + assertTrue(mngt.isManagedSubjectEnforced(Subject.VERSION)); + + mngt = manager.manageDependency(new Dependency(C1, null)); + assertNotNull(mngt); + assertTrue(mngt.isManagedSubjectEnforced(Subject.SCOPE)); + } + + /** + * Tests backwards compatibility: setManagedBits maps to isManagedSubject/isManagedSubjectEnforced. + */ + @Test + void testSetManagedBitsBackwardsCompat() { + DefaultDependencyNode node = new DefaultDependencyNode(new Dependency(A1, "compile")); + + // Set via deprecated API + node.setManagedBits(DependencyNode.MANAGED_VERSION | DependencyNode.MANAGED_SCOPE); + + // Check via new API — all mapped as enforced + assertTrue(node.isManagedSubject(Subject.VERSION)); + assertTrue(node.isManagedSubjectEnforced(Subject.VERSION)); + assertTrue(node.isManagedSubject(Subject.SCOPE)); + assertTrue(node.isManagedSubjectEnforced(Subject.SCOPE)); + + // Unset subjects + assertFalse(node.isManagedSubject(Subject.OPTIONAL)); + assertFalse(node.isManagedSubjectEnforced(Subject.OPTIONAL)); + assertFalse(node.isManagedSubject(Subject.PROPERTIES)); + assertFalse(node.isManagedSubject(Subject.EXCLUSIONS)); + + // Round-trip: getManagedBits still works + assertEquals(DependencyNode.MANAGED_VERSION | DependencyNode.MANAGED_SCOPE, node.getManagedBits()); + } + + /** + * Tests that setManagedSubjects with explicit enforcement flags works correctly. + */ + @Test + void testSetManagedSubjectsWithEnforcement() { + DefaultDependencyNode node = new DefaultDependencyNode(new Dependency(A1, "compile")); + + EnumMap subjects = new EnumMap<>(Subject.class); + subjects.put(Subject.VERSION, true); // enforced + subjects.put(Subject.SCOPE, false); // advised + subjects.put(Subject.OPTIONAL, false); // advised + node.setManagedSubjects(subjects); + + // All are managed + assertTrue(node.isManagedSubject(Subject.VERSION)); + assertTrue(node.isManagedSubject(Subject.SCOPE)); + assertTrue(node.isManagedSubject(Subject.OPTIONAL)); + assertFalse(node.isManagedSubject(Subject.PROPERTIES)); + + // Only version is enforced + assertTrue(node.isManagedSubjectEnforced(Subject.VERSION)); + assertFalse(node.isManagedSubjectEnforced(Subject.SCOPE)); + assertFalse(node.isManagedSubjectEnforced(Subject.OPTIONAL)); + assertFalse(node.isManagedSubjectEnforced(Subject.PROPERTIES)); + + // getManagedBits still reports all managed subjects (regardless of enforcement) + assertEquals( + DependencyNode.MANAGED_VERSION | DependencyNode.MANAGED_SCOPE | DependencyNode.MANAGED_OPTIONAL, + node.getManagedBits()); + } + + /** + * Tests that setManagedSubjects with null clears all managed subjects. + */ + @Test + void testSetManagedSubjectsNull() { + DefaultDependencyNode node = new DefaultDependencyNode(new Dependency(A1, "compile")); + + EnumMap subjects = new EnumMap<>(Subject.class); + subjects.put(Subject.VERSION, true); + node.setManagedSubjects(subjects); + assertTrue(node.isManagedSubject(Subject.VERSION)); + + // Clear + node.setManagedSubjects(null); + assertFalse(node.isManagedSubject(Subject.VERSION)); + assertEquals(0, node.getManagedBits()); + } } diff --git a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java index c8bb21954a..103f8c45cd 100644 --- a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java +++ b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java @@ -20,12 +20,14 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.eclipse.aether.RepositoryException; import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.DependencyManagement; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; @@ -527,6 +529,27 @@ void winnerCycleRemoved(ConflictResolver conflictResolver) throws RepositoryExce } } + @ParameterizedTest + @MethodSource("conflictResolverSource") + void enforcedScopeNotDerived(ConflictResolver conflictResolver) throws RepositoryException { + // Root -> Parent (test) -> Child (compile, scope enforced) + // Scope derivation would normally narrow Child's scope to "test" (from parent), + // but enforced scope should prevent that. + DependencyNode root = makeDependencyNode("some-group", "root", "1.0"); + DependencyNode parent = makeDependencyNode("some-group", "parent", "1.0", "test"); + DefaultDependencyNode child = + (DefaultDependencyNode) makeDependencyNode("some-group", "child", "1.0", "compile"); + EnumMap enforcedScope = + new EnumMap<>(DependencyManagement.Subject.class); + enforcedScope.put(DependencyManagement.Subject.SCOPE, true); + child.setManagedSubjects(enforcedScope); + root.setChildren(mutableList(parent)); + parent.setChildren(mutableList(child)); + + transform(conflictResolver, root); + assertEquals("compile", child.getDependency().getScope(), "Enforced scope should not be derived from parent"); + } + @ParameterizedTest @MethodSource("conflictResolverSource") void subtreeRepeatedSameInstance(ConflictResolver conflictResolver) throws RepositoryException { @@ -577,6 +600,25 @@ public boolean visitLeave(DependencyNode node) { assertEquals(1, occurrenceOfSt3.get()); // st3 must be present only once in tree } + @ParameterizedTest + @MethodSource("conflictResolverSource") + void advisedScopeIsDerived(ConflictResolver conflictResolver) throws RepositoryException { + // Root -> Parent (test) -> Child (compile, scope advised) + // Scope derivation should narrow Child's scope to "test" because it's only advised. + DependencyNode root = makeDependencyNode("some-group", "root", "1.0"); + DependencyNode parent = makeDependencyNode("some-group", "parent", "1.0", "test"); + DefaultDependencyNode child = + (DefaultDependencyNode) makeDependencyNode("some-group", "child", "1.0", "compile"); + EnumMap advisedScope = new EnumMap<>(DependencyManagement.Subject.class); + advisedScope.put(DependencyManagement.Subject.SCOPE, false); + child.setManagedSubjects(advisedScope); + root.setChildren(mutableList(parent)); + parent.setChildren(mutableList(child)); + + transform(conflictResolver, root); + assertEquals("test", child.getDependency().getScope(), "Advised scope should be derived from parent"); + } + @ParameterizedTest @MethodSource("conflictResolverSource") void subtreeRepeatedSameInstanceWithRealCycle(ConflictResolver conflictResolver) throws RepositoryException { @@ -630,6 +672,28 @@ public boolean visitLeave(DependencyNode node) { assertEquals(1, occurrenceOfSt3.get()); // st3 must be present only once in tree } + @ParameterizedTest + @MethodSource("conflictResolverSource") + void enforcedOptionalNotDerived(ConflictResolver conflictResolver) throws RepositoryException { + // Root -> Parent (optional=true) -> Child (optional=false, optional enforced) + // Optional derivation would normally set Child to optional (from parent), + // but enforced optional should prevent that. + DependencyNode root = makeDependencyNode("some-group", "root", "1.0"); + DependencyNode parent = makeDependencyNode("some-group", "parent", "1.0"); + parent.setOptional(true); + DefaultDependencyNode child = (DefaultDependencyNode) makeDependencyNode("some-group", "child", "1.0"); + child.setOptional(false); + EnumMap enforcedOptional = + new EnumMap<>(DependencyManagement.Subject.class); + enforcedOptional.put(DependencyManagement.Subject.OPTIONAL, true); + child.setManagedSubjects(enforcedOptional); + root.setChildren(mutableList(parent)); + parent.setChildren(mutableList(child)); + + transform(conflictResolver, root); + assertFalse(child.getDependency().isOptional(), "Enforced optional should not be derived from parent"); + } + @ParameterizedTest @MethodSource("conflictResolverSource") void nettyBoringSslExample(ConflictResolver conflictResolver) throws RepositoryException { @@ -673,6 +737,22 @@ public boolean visitLeave(DependencyNode node) { assertEquals(2, occurrence.get()); // st3 must be present only once in tree } + @ParameterizedTest + @MethodSource("conflictResolverSource") + void unmanagedScopeIsDerivedAsUsual(ConflictResolver conflictResolver) throws RepositoryException { + // Root -> Parent (test) -> Child (compile, no management) + // Scope derivation should narrow Child's scope to "test" as usual (backwards compat). + DependencyNode root = makeDependencyNode("some-group", "root", "1.0"); + DependencyNode parent = makeDependencyNode("some-group", "parent", "1.0", "test"); + DependencyNode child = makeDependencyNode("some-group", "child", "1.0", "compile"); + root.setChildren(mutableList(parent)); + parent.setChildren(mutableList(child)); + + transform(conflictResolver, root); + assertEquals( + "test", child.getDependency().getScope(), "Unmanaged scope should be derived from parent as usual"); + } + @ParameterizedTest @MethodSource("conflictResolverSource") void issue1903(ConflictResolver conflictResolver) throws RepositoryException { @@ -725,7 +805,7 @@ private static DependencyNode makeDependencyNode(String groupId, String artifact } private static DependencyNode makeDependencyNode(String groupId, String artifactId, String version, String scope) { - return makeDependencyNode(groupId, artifactId, version, null, "compile"); + return makeDependencyNode(groupId, artifactId, version, null, scope); } private static DependencyNode makeDependencyNode(