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.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.Deque; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.function.Consumer; 032import java.util.function.Function; 033import java.util.stream.Collectors; 034 035import org.eclipse.aether.artifact.Artifact; 036import org.eclipse.aether.graph.Dependency; 037import org.eclipse.aether.graph.DependencyNode; 038import org.eclipse.aether.graph.DependencyVisitor; 039import org.eclipse.aether.graph.Exclusion; 040import org.eclipse.aether.util.artifact.ArtifactIdUtils; 041import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; 042import org.eclipse.aether.util.graph.transformer.ConflictResolver; 043import org.eclipse.aether.version.VersionConstraint; 044 045import static java.util.Objects.requireNonNull; 046 047/** 048 * A dependency visitor that dumps the graph to any {@link Consumer}{@code <String>}. Meant for diagnostic and testing, as 049 * it may output the graph to standard output, error or even some logging interface. 050 * 051 * @since 1.9.8 052 */ 053public class DependencyGraphDumper implements DependencyVisitor { 054 /** 055 * Decorator of "effective dependency": shows effective scope and optionality. 056 */ 057 public static Function<DependencyNode, String> effectiveDependency() { 058 return dependencyNode -> { 059 Dependency d = dependencyNode.getDependency(); 060 if (d != null) { 061 if (!d.getScope().isEmpty()) { 062 String result = d.getScope(); 063 if (d.isOptional()) { 064 result += ", optional"; 065 } 066 return "[" + result + "]"; 067 } 068 } 069 return null; 070 }; 071 } 072 /** 073 * Decorator of "managed version": explains on nodes what was managed. 074 */ 075 public static Function<DependencyNode, String> premanagedVersion() { 076 return dependencyNode -> { 077 if (dependencyNode.getArtifact() != null) { 078 String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(dependencyNode); 079 if (premanagedVersion != null 080 && !premanagedVersion.equals( 081 dependencyNode.getArtifact().getBaseVersion())) { 082 return "(version managed from " + premanagedVersion + ")"; 083 } 084 } 085 return null; 086 }; 087 } 088 /** 089 * Decorator of "managed scope": explains on nodes what was managed. 090 */ 091 public static Function<DependencyNode, String> premanagedScope() { 092 return dependencyNode -> { 093 Dependency d = dependencyNode.getDependency(); 094 if (d != null) { 095 String premanagedScope = DependencyManagerUtils.getPremanagedScope(dependencyNode); 096 if (premanagedScope != null && !premanagedScope.equals(d.getScope())) { 097 return "(scope managed from " + premanagedScope + ")"; 098 } 099 } 100 return null; 101 }; 102 } 103 /** 104 * Decorator of "managed optionality": explains on nodes what was managed. 105 */ 106 public static Function<DependencyNode, String> premanagedOptional() { 107 return dependencyNode -> { 108 Dependency d = dependencyNode.getDependency(); 109 if (d != null) { 110 Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(dependencyNode); 111 if (premanagedOptional != null && !premanagedOptional.equals(d.getOptional())) { 112 return "(optionality managed from " + premanagedOptional + ")"; 113 } 114 } 115 return null; 116 }; 117 } 118 /** 119 * Decorator of "managed exclusions": explains on nodes what was managed. 120 */ 121 public static Function<DependencyNode, String> premanagedExclusions() { 122 return dependencyNode -> { 123 Dependency d = dependencyNode.getDependency(); 124 if (d != null) { 125 Collection<Exclusion> premanagedExclusions = 126 DependencyManagerUtils.getPremanagedExclusions(dependencyNode); 127 if (premanagedExclusions != null && !equals(premanagedExclusions, d.getExclusions())) { 128 return "(exclusions managed from " + premanagedExclusions + ")"; 129 } 130 } 131 return null; 132 }; 133 } 134 /** 135 * Decorator of "managed properties": explains on nodes what was managed. 136 */ 137 public static Function<DependencyNode, String> premanagedProperties() { 138 return dependencyNode -> { 139 if (dependencyNode.getArtifact() != null) { 140 Map<String, String> premanagedProperties = 141 DependencyManagerUtils.getPremanagedProperties(dependencyNode); 142 if (premanagedProperties != null 143 && !equals( 144 premanagedProperties, 145 dependencyNode.getArtifact().getProperties())) { 146 return "(properties managed from " + premanagedProperties + ")"; 147 } 148 } 149 return null; 150 }; 151 } 152 /** 153 * Decorator of "range member": explains on nodes what range it participates in. 154 */ 155 public static Function<DependencyNode, String> rangeMember() { 156 return dependencyNode -> { 157 VersionConstraint constraint = dependencyNode.getVersionConstraint(); 158 if (constraint != null && constraint.getRange() != null) { 159 return "(range '" + constraint.getRange() + "')"; 160 } 161 return null; 162 }; 163 } 164 /** 165 * Decorator of "winner node": explains on losers why lost. 166 */ 167 public static Function<DependencyNode, String> winnerNode() { 168 return dependencyNode -> { 169 if (dependencyNode.getArtifact() != null) { 170 DependencyNode winner = 171 (DependencyNode) dependencyNode.getData().get(ConflictResolver.NODE_DATA_WINNER); 172 if (winner != null) { 173 if (ArtifactIdUtils.equalsId(dependencyNode.getArtifact(), winner.getArtifact())) { 174 return "(nearer exists)"; 175 } else { 176 Artifact w = winner.getArtifact(); 177 String result = "conflicts with "; 178 if (ArtifactIdUtils.toVersionlessId(dependencyNode.getArtifact()) 179 .equals(ArtifactIdUtils.toVersionlessId(w))) { 180 result += w.getVersion(); 181 } else { 182 result += w; 183 } 184 return "(" + result + ")"; 185 } 186 } 187 } 188 return null; 189 }; 190 } 191 /** 192 * Decorator of "artifact properties": prints out asked properties, if present. 193 */ 194 public static Function<DependencyNode, String> artifactProperties(Collection<String> properties) { 195 requireNonNull(properties, "properties"); 196 return dependencyNode -> { 197 if (!properties.isEmpty() && dependencyNode.getDependency() != null) { 198 String props = properties.stream() 199 .map(p -> p + "=" 200 + dependencyNode.getDependency().getArtifact().getProperty(p, "n/a")) 201 .collect(Collectors.joining(",")); 202 if (!props.isEmpty()) { 203 return "(" + props + ")"; 204 } 205 } 206 return null; 207 }; 208 } 209 210 /** 211 * The standard "default" decorators. 212 * 213 * @since 2.0.0 214 */ 215 private static final List<Function<DependencyNode, String>> DEFAULT_DECORATORS = 216 Collections.unmodifiableList(Arrays.asList( 217 effectiveDependency(), 218 premanagedVersion(), 219 premanagedScope(), 220 premanagedOptional(), 221 premanagedExclusions(), 222 premanagedProperties(), 223 rangeMember(), 224 winnerNode())); 225 226 /** 227 * Extends {@link #DEFAULT_DECORATORS} decorators with passed in ones. 228 * 229 * @since 2.0.0 230 */ 231 public static List<Function<DependencyNode, String>> defaultsWith( 232 Collection<Function<DependencyNode, String>> extras) { 233 requireNonNull(extras, "extras"); 234 ArrayList<Function<DependencyNode, String>> result = new ArrayList<>(DEFAULT_DECORATORS); 235 result.addAll(extras); 236 return result; 237 } 238 239 private final Consumer<String> consumer; 240 241 private final List<Function<DependencyNode, String>> decorators; 242 243 private final Deque<DependencyNode> nodes = new ArrayDeque<>(); 244 245 /** 246 * Creates instance with given consumer. 247 * 248 * @param consumer The string consumer, must not be {@code null}. 249 */ 250 public DependencyGraphDumper(Consumer<String> consumer) { 251 this(consumer, DEFAULT_DECORATORS); 252 } 253 254 /** 255 * Creates instance with given consumer and decorators. 256 * 257 * @param consumer The string consumer, must not be {@code null}. 258 * @param decorators The decorators to apply, must not be {@code null}. 259 * @since 2.0.0 260 */ 261 public DependencyGraphDumper(Consumer<String> consumer, Collection<Function<DependencyNode, String>> decorators) { 262 this.consumer = requireNonNull(consumer); 263 this.decorators = new ArrayList<>(decorators); 264 } 265 266 @Override 267 public boolean visitEnter(DependencyNode node) { 268 nodes.push(node); 269 consumer.accept(formatLine(nodes)); 270 return true; 271 } 272 273 @Override 274 public boolean visitLeave(DependencyNode node) { 275 if (!nodes.isEmpty()) { 276 nodes.pop(); 277 } 278 return true; 279 } 280 281 protected String formatLine(Deque<DependencyNode> nodes) { 282 return formatIndentation(nodes) + formatNode(nodes); 283 } 284 285 protected String formatIndentation(Deque<DependencyNode> nodes) { 286 StringBuilder buffer = new StringBuilder(128); 287 Iterator<DependencyNode> iter = nodes.descendingIterator(); 288 DependencyNode parent = iter.hasNext() ? iter.next() : null; 289 DependencyNode child = iter.hasNext() ? iter.next() : null; 290 while (parent != null && child != null) { 291 boolean lastChild = parent.getChildren().get(parent.getChildren().size() - 1) == child; 292 boolean end = child == nodes.peekFirst(); 293 String indent; 294 if (end) { 295 indent = lastChild ? "\\- " : "+- "; 296 } else { 297 indent = lastChild ? " " : "| "; 298 } 299 buffer.append(indent); 300 parent = child; 301 child = iter.hasNext() ? iter.next() : null; 302 } 303 return buffer.toString(); 304 } 305 306 protected String formatNode(Deque<DependencyNode> nodes) { 307 DependencyNode node = requireNonNull(nodes.peek(), "bug: should not happen"); 308 StringBuilder buffer = new StringBuilder(128); 309 Artifact a = node.getArtifact(); 310 buffer.append(a); 311 for (Function<DependencyNode, String> decorator : decorators) { 312 String decoration = decorator.apply(node); 313 if (decoration != null) { 314 buffer.append(" ").append(decoration); 315 } 316 } 317 return buffer.toString(); 318 } 319 320 private static boolean equals(Collection<Exclusion> c1, Collection<Exclusion> c2) { 321 return c1 != null && c2 != null && c1.size() == c2.size() && c1.containsAll(c2); 322 } 323 324 private static boolean equals(Map<String, String> m1, Map<String, String> m2) { 325 return m1 != null 326 && m2 != null 327 && m1.size() == m2.size() 328 && m1.entrySet().stream().allMatch(entry -> Objects.equals(m2.get(entry.getKey()), entry.getValue())); 329 } 330}