View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.graph.visitor;
20  
21  import java.util.ArrayDeque;
22  import java.util.Deque;
23  import java.util.Iterator;
24  import java.util.function.Consumer;
25  
26  import org.eclipse.aether.artifact.Artifact;
27  import org.eclipse.aether.graph.Dependency;
28  import org.eclipse.aether.graph.DependencyNode;
29  import org.eclipse.aether.graph.DependencyVisitor;
30  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
31  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
32  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
33  
34  import static java.util.Objects.requireNonNull;
35  
36  /**
37   * A dependency visitor that dumps the graph to any {@link Consumer}{@code <String>}. Meant for diagnostic and testing, as
38   * it may output the graph to standard output, error or even some logging interface.
39   *
40   * @since 1.9.8
41   */
42  public class DependencyGraphDumper implements DependencyVisitor {
43  
44      private final Consumer<String> consumer;
45  
46      private final Deque<DependencyNode> nodes = new ArrayDeque<>();
47  
48      public DependencyGraphDumper(Consumer<String> consumer) {
49          this.consumer = requireNonNull(consumer);
50      }
51  
52      @Override
53      public boolean visitEnter(DependencyNode node) {
54          nodes.push(node);
55          consumer.accept(formatLine(nodes));
56          return true;
57      }
58  
59      @Override
60      public boolean visitLeave(DependencyNode node) {
61          if (!nodes.isEmpty()) {
62              nodes.pop();
63          }
64          return true;
65      }
66  
67      protected String formatLine(Deque<DependencyNode> nodes) {
68          return formatIndentation(nodes) + formatNode(nodes);
69      }
70  
71      protected String formatIndentation(Deque<DependencyNode> nodes) {
72          StringBuilder buffer = new StringBuilder(128);
73          Iterator<DependencyNode> iter = nodes.descendingIterator();
74          DependencyNode parent = iter.hasNext() ? iter.next() : null;
75          DependencyNode child = iter.hasNext() ? iter.next() : null;
76          while (parent != null && child != null) {
77              boolean lastChild = parent.getChildren().get(parent.getChildren().size() - 1) == child;
78              boolean end = child == nodes.peekFirst();
79              String indent;
80              if (end) {
81                  indent = lastChild ? "\\- " : "+- ";
82              } else {
83                  indent = lastChild ? "   " : "|  ";
84              }
85              buffer.append(indent);
86              parent = child;
87              child = iter.hasNext() ? iter.next() : null;
88          }
89          return buffer.toString();
90      }
91  
92      protected String formatNode(Deque<DependencyNode> nodes) {
93          DependencyNode node = requireNonNull(nodes.peek(), "bug: should not happen");
94          StringBuilder buffer = new StringBuilder(128);
95          Artifact a = node.getArtifact();
96          buffer.append(a);
97          Dependency d = node.getDependency();
98          if (d != null && !d.getScope().isEmpty()) {
99              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 }