001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.graph.visitor;
020
021import java.util.ArrayDeque;
022import java.util.Deque;
023import java.util.Iterator;
024import java.util.function.Consumer;
025
026import org.eclipse.aether.artifact.Artifact;
027import org.eclipse.aether.graph.Dependency;
028import org.eclipse.aether.graph.DependencyNode;
029import org.eclipse.aether.graph.DependencyVisitor;
030import org.eclipse.aether.util.artifact.ArtifactIdUtils;
031import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
032import org.eclipse.aether.util.graph.transformer.ConflictResolver;
033
034import static java.util.Objects.requireNonNull;
035
036/**
037 * A dependency visitor that dumps the graph to any {@link Consumer}{@code <String>}. Meant for diagnostic and testing, as
038 * it may output the graph to standard output, error or even some logging interface.
039 *
040 * @since 1.9.8
041 */
042public class DependencyGraphDumper implements DependencyVisitor {
043
044    private final Consumer<String> consumer;
045
046    private final Deque<DependencyNode> nodes = new ArrayDeque<>();
047
048    public DependencyGraphDumper(Consumer<String> consumer) {
049        this.consumer = requireNonNull(consumer);
050    }
051
052    @Override
053    public boolean visitEnter(DependencyNode node) {
054        nodes.push(node);
055        consumer.accept(formatLine(nodes));
056        return true;
057    }
058
059    @Override
060    public boolean visitLeave(DependencyNode node) {
061        if (!nodes.isEmpty()) {
062            nodes.pop();
063        }
064        return true;
065    }
066
067    protected String formatLine(Deque<DependencyNode> nodes) {
068        return formatIndentation(nodes) + formatNode(nodes);
069    }
070
071    protected String formatIndentation(Deque<DependencyNode> nodes) {
072        StringBuilder buffer = new StringBuilder(128);
073        Iterator<DependencyNode> iter = nodes.descendingIterator();
074        DependencyNode parent = iter.hasNext() ? iter.next() : null;
075        DependencyNode child = iter.hasNext() ? iter.next() : null;
076        while (parent != null && child != null) {
077            boolean lastChild = parent.getChildren().get(parent.getChildren().size() - 1) == child;
078            boolean end = child == nodes.peekFirst();
079            String indent;
080            if (end) {
081                indent = lastChild ? "\\- " : "+- ";
082            } else {
083                indent = lastChild ? "   " : "|  ";
084            }
085            buffer.append(indent);
086            parent = child;
087            child = iter.hasNext() ? iter.next() : null;
088        }
089        return buffer.toString();
090    }
091
092    protected String formatNode(Deque<DependencyNode> nodes) {
093        DependencyNode node = requireNonNull(nodes.peek(), "bug: should not happen");
094        StringBuilder buffer = new StringBuilder(128);
095        Artifact a = node.getArtifact();
096        buffer.append(a);
097        Dependency d = node.getDependency();
098        if (d != null && !d.getScope().isEmpty()) {
099            buffer.append(" [").append(d.getScope());
100            if (d.isOptional()) {
101                buffer.append(", optional");
102            }
103            buffer.append("]");
104        }
105        String premanaged = DependencyManagerUtils.getPremanagedVersion(node);
106        if (premanaged != null && !premanaged.equals(a.getBaseVersion())) {
107            buffer.append(" (version managed from ").append(premanaged).append(")");
108        }
109
110        premanaged = DependencyManagerUtils.getPremanagedScope(node);
111        if (premanaged != null && d != null && !premanaged.equals(d.getScope())) {
112            buffer.append(" (scope managed from ").append(premanaged).append(")");
113        }
114        DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER);
115        if (winner != null) {
116            if (ArtifactIdUtils.equalsId(a, winner.getArtifact())) {
117                buffer.append(" (nearer exists)");
118            } else {
119                Artifact w = winner.getArtifact();
120                buffer.append(" (conflicts with ");
121                if (ArtifactIdUtils.toVersionlessId(a).equals(ArtifactIdUtils.toVersionlessId(w))) {
122                    buffer.append(w.getVersion());
123                } else {
124                    buffer.append(w);
125                }
126                buffer.append(")");
127            }
128        }
129        return buffer.toString();
130    }
131}