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}