[MRESOLVER-535][MRESOLVER-538] Add decorator ability to graph dumper; show ranges (#464)
Ability to pass in any function that is able to "decorate" the print out of the graph. This should have a follow up Pr that enable/disable (or simply move all into decorators). Also, verbose tree should show use of version ranges, for cases when someone wants to detect them in whole transitive hull.
---
https://issues.apache.org/jira/browse/MRESOLVER-535
https://issues.apache.org/jira/browse/MRESOLVER-538
diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java
index 6b39fc3..28f6c67 100644
--- a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java
+++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java
@@ -18,7 +18,7 @@
*/
package org.apache.maven.resolver.examples;
-import java.util.Arrays;
+import java.util.Collections;
import org.apache.maven.resolver.examples.util.Booter;
import org.eclipse.aether.RepositorySystem;
@@ -34,6 +34,9 @@
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
+import static org.eclipse.aether.util.graph.visitor.DependencyGraphDumper.artifactProperties;
+import static org.eclipse.aether.util.graph.visitor.DependencyGraphDumper.defaultsWith;
+
/**
* Visualizes the transitive dependencies of an artifact similar to m2e's dependency hierarchy view.
*/
@@ -68,7 +71,12 @@
CollectResult collectResult = system.collectDependencies(session, collectRequest);
- collectResult.getRoot().accept(new DependencyGraphDumper(System.out::println, Arrays.asList("color")));
+ collectResult
+ .getRoot()
+ .accept(new DependencyGraphDumper(
+ System.out::println,
+ defaultsWith(
+ Collections.singleton(artifactProperties(Collections.singleton("color"))))));
}
}
}
diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java
index 3d3d15b..2f37957 100644
--- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java
+++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java
@@ -20,13 +20,16 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.aether.artifact.Artifact;
@@ -37,6 +40,7 @@
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.version.VersionConstraint;
import static java.util.Objects.requireNonNull;
@@ -47,28 +51,216 @@
* @since 1.9.8
*/
public class DependencyGraphDumper implements DependencyVisitor {
+ /**
+ * Decorator of "effective dependency": shows effective scope and optionality.
+ */
+ public static Function<DependencyNode, String> effectiveDependency() {
+ return dependencyNode -> {
+ Dependency d = dependencyNode.getDependency();
+ if (d != null) {
+ if (!d.getScope().isEmpty()) {
+ String result = d.getScope();
+ if (d.isOptional()) {
+ result += ", optional";
+ }
+ return "[" + result + "]";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "managed version": explains on nodes what was managed.
+ */
+ public static Function<DependencyNode, String> premanagedVersion() {
+ return dependencyNode -> {
+ if (dependencyNode.getArtifact() != null) {
+ String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(dependencyNode);
+ if (premanagedVersion != null
+ && !premanagedVersion.equals(
+ dependencyNode.getArtifact().getBaseVersion())) {
+ return "(version managed from " + premanagedVersion + ")";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "managed scope": explains on nodes what was managed.
+ */
+ public static Function<DependencyNode, String> premanagedScope() {
+ return dependencyNode -> {
+ Dependency d = dependencyNode.getDependency();
+ if (d != null) {
+ String premanagedScope = DependencyManagerUtils.getPremanagedScope(dependencyNode);
+ if (premanagedScope != null && !premanagedScope.equals(d.getScope())) {
+ return "(scope managed from " + premanagedScope + ")";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "managed optionality": explains on nodes what was managed.
+ */
+ public static Function<DependencyNode, String> premanagedOptional() {
+ return dependencyNode -> {
+ Dependency d = dependencyNode.getDependency();
+ if (d != null) {
+ Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(dependencyNode);
+ if (premanagedOptional != null && !premanagedOptional.equals(d.getOptional())) {
+ return "(optionality managed from " + premanagedOptional + ")";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "managed exclusions": explains on nodes what was managed.
+ */
+ public static Function<DependencyNode, String> premanagedExclusions() {
+ return dependencyNode -> {
+ Dependency d = dependencyNode.getDependency();
+ if (d != null) {
+ Collection<Exclusion> premanagedExclusions =
+ DependencyManagerUtils.getPremanagedExclusions(dependencyNode);
+ if (premanagedExclusions != null && !equals(premanagedExclusions, d.getExclusions())) {
+ return "(exclusions managed from " + premanagedExclusions + ")";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "managed properties": explains on nodes what was managed.
+ */
+ public static Function<DependencyNode, String> premanagedProperties() {
+ return dependencyNode -> {
+ if (dependencyNode.getArtifact() != null) {
+ Map<String, String> premanagedProperties =
+ DependencyManagerUtils.getPremanagedProperties(dependencyNode);
+ if (premanagedProperties != null
+ && !equals(
+ premanagedProperties,
+ dependencyNode.getArtifact().getProperties())) {
+ return "(properties managed from " + premanagedProperties + ")";
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "range member": explains on nodes what range it participates in.
+ */
+ public static Function<DependencyNode, String> rangeMember() {
+ return dependencyNode -> {
+ VersionConstraint constraint = dependencyNode.getVersionConstraint();
+ if (constraint != null && constraint.getRange() != null) {
+ return "(range '" + constraint.getRange() + "')";
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "winner node": explains on losers why lost.
+ */
+ public static Function<DependencyNode, String> winnerNode() {
+ return dependencyNode -> {
+ if (dependencyNode.getArtifact() != null) {
+ DependencyNode winner =
+ (DependencyNode) dependencyNode.getData().get(ConflictResolver.NODE_DATA_WINNER);
+ if (winner != null) {
+ if (ArtifactIdUtils.equalsId(dependencyNode.getArtifact(), winner.getArtifact())) {
+ return "(nearer exists)";
+ } else {
+ Artifact w = winner.getArtifact();
+ String result = "conflicts with ";
+ if (ArtifactIdUtils.toVersionlessId(dependencyNode.getArtifact())
+ .equals(ArtifactIdUtils.toVersionlessId(w))) {
+ result += w.getVersion();
+ } else {
+ result += w;
+ }
+ return "(" + result + ")";
+ }
+ }
+ }
+ return null;
+ };
+ }
+ /**
+ * Decorator of "artifact properties": prints out asked properties, if present.
+ */
+ public static Function<DependencyNode, String> artifactProperties(Collection<String> properties) {
+ requireNonNull(properties, "properties");
+ return dependencyNode -> {
+ if (!properties.isEmpty() && dependencyNode.getDependency() != null) {
+ String props = properties.stream()
+ .map(p -> p + "="
+ + dependencyNode.getDependency().getArtifact().getProperty(p, "n/a"))
+ .collect(Collectors.joining(","));
+ if (!props.isEmpty()) {
+ return "(" + props + ")";
+ }
+ }
+ return null;
+ };
+ }
+
+ /**
+ * The standard "default" decorators.
+ *
+ * @since 2.0.0
+ */
+ private static final List<Function<DependencyNode, String>> DEFAULT_DECORATORS =
+ Collections.unmodifiableList(Arrays.asList(
+ effectiveDependency(),
+ premanagedVersion(),
+ premanagedScope(),
+ premanagedOptional(),
+ premanagedExclusions(),
+ premanagedProperties(),
+ rangeMember(),
+ winnerNode()));
+
+ /**
+ * Extends {@link #DEFAULT_DECORATORS} decorators with passed in ones.
+ *
+ * @since 2.0.0
+ */
+ public static List<Function<DependencyNode, String>> defaultsWith(
+ Collection<Function<DependencyNode, String>> extras) {
+ requireNonNull(extras, "extras");
+ ArrayList<Function<DependencyNode, String>> result = new ArrayList<>(DEFAULT_DECORATORS);
+ result.addAll(extras);
+ return result;
+ }
private final Consumer<String> consumer;
- private final Collection<String> properties;
+ private final List<Function<DependencyNode, String>> decorators;
private final Deque<DependencyNode> nodes = new ArrayDeque<>();
/**
* Creates instance with given consumer.
+ *
+ * @param consumer The string consumer, must not be {@code null}.
*/
public DependencyGraphDumper(Consumer<String> consumer) {
- this(consumer, Collections.emptyList());
+ this(consumer, DEFAULT_DECORATORS);
}
/**
- * Creates instance with given consumer and properties (to print out).
+ * Creates instance with given consumer and decorators.
*
+ * @param consumer The string consumer, must not be {@code null}.
+ * @param decorators The decorators to apply, must not be {@code null}.
* @since 2.0.0
*/
- public DependencyGraphDumper(Consumer<String> consumer, Collection<String> properties) {
+ public DependencyGraphDumper(Consumer<String> consumer, Collection<Function<DependencyNode, String>> decorators) {
this.consumer = requireNonNull(consumer);
- this.properties = new ArrayList<>(properties);
+ this.decorators = new ArrayList<>(decorators);
}
@Override
@@ -116,77 +308,20 @@
StringBuilder buffer = new StringBuilder(128);
Artifact a = node.getArtifact();
buffer.append(a);
- Dependency d = node.getDependency();
- if (d != null && !d.getScope().isEmpty()) {
- buffer.append(" [").append(d.getScope());
- if (d.isOptional()) {
- buffer.append(", optional");
- }
- buffer.append("]");
- }
- String premanaged = DependencyManagerUtils.getPremanagedVersion(node);
- if (premanaged != null && !premanaged.equals(a.getBaseVersion())) {
- buffer.append(" (version managed from ").append(premanaged).append(")");
- }
-
- premanaged = DependencyManagerUtils.getPremanagedScope(node);
- if (premanaged != null && d != null && !premanaged.equals(d.getScope())) {
- buffer.append(" (scope managed from ").append(premanaged).append(")");
- }
-
- Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node);
- if (premanagedOptional != null && d != null && !premanagedOptional.equals(d.getOptional())) {
- buffer.append(" (optionality managed from ")
- .append(premanagedOptional)
- .append(")");
- }
-
- Collection<Exclusion> premanagedExclusions = DependencyManagerUtils.getPremanagedExclusions(node);
- if (premanagedExclusions != null && d != null && !equals(premanagedExclusions, d.getExclusions())) {
- buffer.append(" (exclusions managed from ")
- .append(premanagedExclusions)
- .append(")");
- }
-
- Map<String, String> premanagedProperties = DependencyManagerUtils.getPremanagedProperties(node);
- if (premanagedProperties != null && !equals(premanagedProperties, a.getProperties())) {
- buffer.append(" (properties managed from ")
- .append(premanagedProperties)
- .append(")");
- }
-
- DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER);
- if (winner != null) {
- if (ArtifactIdUtils.equalsId(a, winner.getArtifact())) {
- buffer.append(" (nearer exists)");
- } else {
- Artifact w = winner.getArtifact();
- buffer.append(" (conflicts with ");
- if (ArtifactIdUtils.toVersionlessId(a).equals(ArtifactIdUtils.toVersionlessId(w))) {
- buffer.append(w.getVersion());
- } else {
- buffer.append(w);
- }
- buffer.append(")");
- }
- }
-
- if (!properties.isEmpty() && node.getDependency() != null) {
- String props = properties.stream()
- .map(p -> p + "=" + node.getDependency().getArtifact().getProperty(p, "n/a"))
- .collect(Collectors.joining(","));
- if (!props.isEmpty()) {
- buffer.append(" (").append(props).append(")");
+ for (Function<DependencyNode, String> decorator : decorators) {
+ String decoration = decorator.apply(node);
+ if (decoration != null) {
+ buffer.append(" ").append(decoration);
}
}
return buffer.toString();
}
- private boolean equals(Collection<Exclusion> c1, Collection<Exclusion> c2) {
+ private static boolean equals(Collection<Exclusion> c1, Collection<Exclusion> c2) {
return c1 != null && c2 != null && c1.size() == c2.size() && c1.containsAll(c2);
}
- private boolean equals(Map<String, String> m1, Map<String, String> m2) {
+ private static boolean equals(Map<String, String> m1, Map<String, String> m2) {
return m1 != null
&& m2 != null
&& m1.size() == m2.size()