Skip to content

Commit

Permalink
[MRESOLVER-535][MRESOLVER-538] Add decorator ability to graph dumper;…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
cstamas authored Apr 17, 2024
1 parent b64984a commit 99c9c06
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*/
Expand Down Expand Up @@ -68,7 +71,12 @@ public static void main(String[] args) throws Exception {

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"))))));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -116,77 +308,20 @@ protected String formatNode(Deque<DependencyNode> nodes) {
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()
Expand Down

0 comments on commit 99c9c06

Please sign in to comment.