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.Collection; 023import java.util.Deque; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.Objects; 027import java.util.function.Consumer; 028 029import org.eclipse.aether.artifact.Artifact; 030import org.eclipse.aether.graph.Dependency; 031import org.eclipse.aether.graph.DependencyNode; 032import org.eclipse.aether.graph.DependencyVisitor; 033import org.eclipse.aether.graph.Exclusion; 034import org.eclipse.aether.util.artifact.ArtifactIdUtils; 035import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; 036import org.eclipse.aether.util.graph.transformer.ConflictResolver; 037 038import static java.util.Objects.requireNonNull; 039 040/** 041 * A dependency visitor that dumps the graph to any {@link Consumer}{@code <String>}. Meant for diagnostic and testing, as 042 * it may output the graph to standard output, error or even some logging interface. 043 * 044 * @since 1.9.8 045 */ 046public class DependencyGraphDumper implements DependencyVisitor { 047 048 private final Consumer<String> consumer; 049 050 private final Deque<DependencyNode> nodes = new ArrayDeque<>(); 051 052 public DependencyGraphDumper(Consumer<String> consumer) { 053 this.consumer = requireNonNull(consumer); 054 } 055 056 @Override 057 public boolean visitEnter(DependencyNode node) { 058 nodes.push(node); 059 consumer.accept(formatLine(nodes)); 060 return true; 061 } 062 063 @Override 064 public boolean visitLeave(DependencyNode node) { 065 if (!nodes.isEmpty()) { 066 nodes.pop(); 067 } 068 return true; 069 } 070 071 protected String formatLine(Deque<DependencyNode> nodes) { 072 return formatIndentation(nodes) + formatNode(nodes); 073 } 074 075 protected String formatIndentation(Deque<DependencyNode> nodes) { 076 StringBuilder buffer = new StringBuilder(128); 077 Iterator<DependencyNode> iter = nodes.descendingIterator(); 078 DependencyNode parent = iter.hasNext() ? iter.next() : null; 079 DependencyNode child = iter.hasNext() ? iter.next() : null; 080 while (parent != null && child != null) { 081 boolean lastChild = parent.getChildren().get(parent.getChildren().size() - 1) == child; 082 boolean end = child == nodes.peekFirst(); 083 String indent; 084 if (end) { 085 indent = lastChild ? "\\- " : "+- "; 086 } else { 087 indent = lastChild ? " " : "| "; 088 } 089 buffer.append(indent); 090 parent = child; 091 child = iter.hasNext() ? iter.next() : null; 092 } 093 return buffer.toString(); 094 } 095 096 protected String formatNode(Deque<DependencyNode> nodes) { 097 DependencyNode node = requireNonNull(nodes.peek(), "bug: should not happen"); 098 StringBuilder buffer = new StringBuilder(128); 099 Artifact a = node.getArtifact(); 100 buffer.append(a); 101 Dependency d = node.getDependency(); 102 if (d != null && !d.getScope().isEmpty()) { 103 buffer.append(" [").append(d.getScope()); 104 if (d.isOptional()) { 105 buffer.append(", optional"); 106 } 107 buffer.append("]"); 108 } 109 String premanaged = DependencyManagerUtils.getPremanagedVersion(node); 110 if (premanaged != null && !premanaged.equals(a.getBaseVersion())) { 111 buffer.append(" (version managed from ").append(premanaged).append(")"); 112 } 113 114 premanaged = DependencyManagerUtils.getPremanagedScope(node); 115 if (premanaged != null && d != null && !premanaged.equals(d.getScope())) { 116 buffer.append(" (scope managed from ").append(premanaged).append(")"); 117 } 118 119 Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node); 120 if (premanagedOptional != null && d != null && !premanagedOptional.equals(d.getOptional())) { 121 buffer.append(" (optionality managed from ") 122 .append(premanagedOptional) 123 .append(")"); 124 } 125 126 Collection<Exclusion> premanagedExclusions = DependencyManagerUtils.getPremanagedExclusions(node); 127 if (premanagedExclusions != null && d != null && !equals(premanagedExclusions, d.getExclusions())) { 128 buffer.append(" (exclusions managed from ") 129 .append(premanagedExclusions) 130 .append(")"); 131 } 132 133 Map<String, String> premanagedProperties = DependencyManagerUtils.getPremanagedProperties(node); 134 if (premanagedProperties != null && !equals(premanagedProperties, a.getProperties())) { 135 buffer.append(" (properties managed from ") 136 .append(premanagedProperties) 137 .append(")"); 138 } 139 140 DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER); 141 if (winner != null) { 142 if (ArtifactIdUtils.equalsId(a, winner.getArtifact())) { 143 buffer.append(" (nearer exists)"); 144 } else { 145 Artifact w = winner.getArtifact(); 146 buffer.append(" (conflicts with "); 147 if (ArtifactIdUtils.toVersionlessId(a).equals(ArtifactIdUtils.toVersionlessId(w))) { 148 buffer.append(w.getVersion()); 149 } else { 150 buffer.append(w); 151 } 152 buffer.append(")"); 153 } 154 } 155 return buffer.toString(); 156 } 157 158 private boolean equals(Collection<Exclusion> c1, Collection<Exclusion> c2) { 159 return c1 != null && c2 != null && c1.size() == c2.size() && c1.containsAll(c2); 160 } 161 162 private boolean equals(Map<String, String> m1, Map<String, String> m2) { 163 return m1 != null 164 && m2 != null 165 && m1.size() == m2.size() 166 && m1.entrySet().stream().allMatch(entry -> Objects.equals(m2.get(entry.getKey()), entry.getValue())); 167 } 168}