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.ArrayList;
022import java.util.Iterator;
023import java.util.List;
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<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 List<ChildInfo> childInfos = new ArrayList<>();
047
048    public DependencyGraphDumper(Consumer<String> consumer) {
049        this.consumer = requireNonNull(consumer);
050    }
051
052    @Override
053    public boolean visitEnter(DependencyNode node) {
054        consumer.accept(formatIndentation() + formatNode(node));
055        childInfos.add(new ChildInfo(node.getChildren().size()));
056        return true;
057    }
058
059    private String formatIndentation() {
060        StringBuilder buffer = new StringBuilder(128);
061        for (Iterator<ChildInfo> it = childInfos.iterator(); it.hasNext(); ) {
062            buffer.append(it.next().formatIndentation(!it.hasNext()));
063        }
064        return buffer.toString();
065    }
066
067    private String formatNode(DependencyNode node) {
068        StringBuilder buffer = new StringBuilder(128);
069        Artifact a = node.getArtifact();
070        Dependency d = node.getDependency();
071        buffer.append(a);
072        if (d != null && d.getScope().length() > 0) {
073            buffer.append(" [").append(d.getScope());
074            if (d.isOptional()) {
075                buffer.append(", optional");
076            }
077            buffer.append("]");
078        }
079        String premanaged = DependencyManagerUtils.getPremanagedVersion(node);
080        if (premanaged != null && !premanaged.equals(a.getBaseVersion())) {
081            buffer.append(" (version managed from ").append(premanaged).append(")");
082        }
083
084        premanaged = DependencyManagerUtils.getPremanagedScope(node);
085        if (premanaged != null && !premanaged.equals(d.getScope())) {
086            buffer.append(" (scope managed from ").append(premanaged).append(")");
087        }
088        DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER);
089        if (winner != null) {
090            if (ArtifactIdUtils.equalsId(a, winner.getArtifact())) {
091                buffer.append(" (nearer exists)");
092            } else {
093                Artifact w = winner.getArtifact();
094                buffer.append(" (conflicts with ");
095                if (ArtifactIdUtils.toVersionlessId(a).equals(ArtifactIdUtils.toVersionlessId(w))) {
096                    buffer.append(w.getVersion());
097                } else {
098                    buffer.append(w);
099                }
100                buffer.append(")");
101            }
102        }
103        return buffer.toString();
104    }
105
106    @Override
107    public boolean visitLeave(DependencyNode node) {
108        if (!childInfos.isEmpty()) {
109            childInfos.remove(childInfos.size() - 1);
110        }
111        if (!childInfos.isEmpty()) {
112            childInfos.get(childInfos.size() - 1).index++;
113        }
114        return true;
115    }
116
117    private static class ChildInfo {
118
119        final int count;
120
121        int index;
122
123        ChildInfo(int count) {
124            this.count = count;
125        }
126
127        public String formatIndentation(boolean end) {
128            boolean last = index + 1 >= count;
129            if (end) {
130                return last ? "\\- " : "+- ";
131            }
132            return last ? "   " : "|  ";
133        }
134    }
135}