blob: 3d3d15b55fa02eb53ef32c4eddb9d4c4de670c85 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.eclipse.aether.util.graph.visitor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
import static java.util.Objects.requireNonNull;
/**
* A dependency visitor that dumps the graph to any {@link Consumer}{@code <String>}. Meant for diagnostic and testing, as
* it may output the graph to standard output, error or even some logging interface.
*
* @since 1.9.8
*/
public class DependencyGraphDumper implements DependencyVisitor {
private final Consumer<String> consumer;
private final Collection<String> properties;
private final Deque<DependencyNode> nodes = new ArrayDeque<>();
/**
* Creates instance with given consumer.
*/
public DependencyGraphDumper(Consumer<String> consumer) {
this(consumer, Collections.emptyList());
}
/**
* Creates instance with given consumer and properties (to print out).
*
* @since 2.0.0
*/
public DependencyGraphDumper(Consumer<String> consumer, Collection<String> properties) {
this.consumer = requireNonNull(consumer);
this.properties = new ArrayList<>(properties);
}
@Override
public boolean visitEnter(DependencyNode node) {
nodes.push(node);
consumer.accept(formatLine(nodes));
return true;
}
@Override
public boolean visitLeave(DependencyNode node) {
if (!nodes.isEmpty()) {
nodes.pop();
}
return true;
}
protected String formatLine(Deque<DependencyNode> nodes) {
return formatIndentation(nodes) + formatNode(nodes);
}
protected String formatIndentation(Deque<DependencyNode> nodes) {
StringBuilder buffer = new StringBuilder(128);
Iterator<DependencyNode> iter = nodes.descendingIterator();
DependencyNode parent = iter.hasNext() ? iter.next() : null;
DependencyNode child = iter.hasNext() ? iter.next() : null;
while (parent != null && child != null) {
boolean lastChild = parent.getChildren().get(parent.getChildren().size() - 1) == child;
boolean end = child == nodes.peekFirst();
String indent;
if (end) {
indent = lastChild ? "\\- " : "+- ";
} else {
indent = lastChild ? " " : "| ";
}
buffer.append(indent);
parent = child;
child = iter.hasNext() ? iter.next() : null;
}
return buffer.toString();
}
protected String formatNode(Deque<DependencyNode> nodes) {
DependencyNode node = requireNonNull(nodes.peek(), "bug: should not happen");
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(")");
}
}
return buffer.toString();
}
private 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) {
return m1 != null
&& m2 != null
&& m1.size() == m2.size()
&& m1.entrySet().stream().allMatch(entry -> Objects.equals(m2.get(entry.getKey()), entry.getValue()));
}
}