001 package org.kuali.maven.plugins.graph.tree; 002 003 import java.io.IOException; 004 import java.io.InputStream; 005 import java.util.ArrayList; 006 import java.util.Collection; 007 import java.util.Collections; 008 import java.util.HashMap; 009 import java.util.List; 010 import java.util.Map; 011 import java.util.Properties; 012 013 import org.apache.commons.beanutils.BeanUtils; 014 import org.apache.commons.io.IOUtils; 015 import org.apache.maven.artifact.Artifact; 016 import org.apache.maven.shared.dependency.tree.DependencyNode; 017 import org.kuali.maven.plugins.graph.collector.ArtifactIdTokenCollector; 018 import org.kuali.maven.plugins.graph.collector.TokenCollector; 019 import org.kuali.maven.plugins.graph.collector.VersionFreeArtifactTokenCollector; 020 import org.kuali.maven.plugins.graph.dot.GraphException; 021 import org.kuali.maven.plugins.graph.dot.NodeGenerator; 022 import org.kuali.maven.plugins.graph.dot.edge.EdgeHandler; 023 import org.kuali.maven.plugins.graph.filter.NodeFilter; 024 import org.kuali.maven.plugins.graph.pojo.Edge; 025 import org.kuali.maven.plugins.graph.pojo.GraphNode; 026 import org.kuali.maven.plugins.graph.pojo.LabelCount; 027 import org.kuali.maven.plugins.graph.pojo.MavenContext; 028 import org.kuali.maven.plugins.graph.pojo.Scope; 029 import org.kuali.maven.plugins.graph.pojo.State; 030 import org.kuali.maven.plugins.graph.pojo.Style; 031 import org.kuali.maven.plugins.graph.sanitize.ConflictSanitizer; 032 import org.kuali.maven.plugins.graph.sanitize.CyclicSanitizer; 033 import org.kuali.maven.plugins.graph.sanitize.DuplicateSanitizer; 034 import org.kuali.maven.plugins.graph.sanitize.MavenContextSanitizer; 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 import org.springframework.core.io.DefaultResourceLoader; 038 import org.springframework.core.io.Resource; 039 import org.springframework.core.io.ResourceLoader; 040 import org.springframework.util.Assert; 041 042 public class TreeHelper { 043 private static final Logger logger = LoggerFactory.getLogger(TreeHelper.class); 044 public static final String ROOT_FILL_COLOR = "#dddddd"; 045 public static final String OPTIONAL = "optional"; 046 public static final String REQUIRED = "required"; 047 Counter counter = new Counter(); 048 NodeGenerator ng = new NodeGenerator(); 049 Properties properties = getProperties(); 050 051 public void include(Node<MavenContext> node, NodeFilter<MavenContext> filter) { 052 if (!filter.isMatch(node) && !node.isRoot()) { 053 hide(node); 054 } else { 055 showPath(node); 056 } 057 for (Node<MavenContext> child : node.getChildren()) { 058 include(child, filter); 059 } 060 } 061 062 public void showPath(Node<MavenContext> node) { 063 Node<MavenContext>[] path = node.getPath(); 064 for (Node<MavenContext> pathNode : path) { 065 show(pathNode); 066 } 067 } 068 069 public void exclude(Node<MavenContext> node, NodeFilter<MavenContext> filter) { 070 if (filter.isMatch(node) && !node.isRoot()) { 071 logger.debug("hiding tree at level=" + node.getLevel()); 072 hideTree(node); 073 } 074 for (Node<MavenContext> child : node.getChildren()) { 075 exclude(child, filter); 076 } 077 } 078 079 public void hideTree(Node<MavenContext> node) { 080 hide(node); 081 for (Node<MavenContext> child : node.getChildren()) { 082 hideTree(child); 083 } 084 } 085 086 public void show(Node<MavenContext> node) { 087 node.getObject().getGraphNode().setHidden(false); 088 } 089 090 public void hide(Node<MavenContext> node) { 091 node.getObject().getGraphNode().setHidden(true); 092 } 093 094 public <T> void prune(Node<T> node, NodeFilter<T> filter) { 095 if (!filter.isMatch(node)) { 096 if (node.isRoot()) { 097 logger.debug("removing all children from root"); 098 node.removeAllChildren(); 099 } else { 100 logger.debug("removing node at level={}", node.getLevel()); 101 node.removeFromParent(); 102 } 103 } else { 104 for (Node<T> child : node.getChildren()) { 105 prune(child, filter); 106 } 107 } 108 } 109 110 protected MavenContext getMavenContext(GraphNode gn, DependencyNode dn) { 111 int id = gn.getId(); 112 String artifactIdentifier = getArtifactId(dn.getArtifact()); 113 MavenContext context = new MavenContext(); 114 context.setId(id); 115 context.setArtifactIdentifier(artifactIdentifier); 116 context.setGraphNode(gn); 117 context.setDependencyNode(dn); 118 context.setState(State.getState(dn.getState())); 119 return context; 120 } 121 122 public Node<MavenContext> getTree(DependencyNode dependencyNode) { 123 GraphNode gn = getGraphNode(dependencyNode); 124 MavenContext context = getMavenContext(gn, dependencyNode); 125 Node<MavenContext> node = new Node<MavenContext>(context); 126 @SuppressWarnings("unchecked") 127 List<DependencyNode> children = dependencyNode.getChildren(); 128 for (DependencyNode child : children) { 129 node.add(getTree(child)); 130 } 131 return node; 132 } 133 134 public void sanitize(Node<MavenContext> node) { 135 List<Node<MavenContext>> nodes = node.getBreadthFirstList(); 136 logger.info("Sanitizing metadata for " + nodes.size() + " dependency nodes"); 137 Map<String, MavenContext> included = getMap(node, nodes, State.INCLUDED); 138 int includedCount = getStateCount(nodes, State.INCLUDED); 139 140 Assert.isTrue(includedCount == included.size(), "Unique included artifact id counts don't match. size=" 141 + included.size() + " count=" + includedCount); 142 143 List<MavenContextSanitizer> sanitizers = getSanitizers(included); 144 for (MavenContextSanitizer sanitizer : sanitizers) { 145 sanitizer.sanitize(node); 146 } 147 for (Node<MavenContext> element : nodes) { 148 updateGraphNodeStyle(element.getObject()); 149 } 150 } 151 152 protected List<MavenContextSanitizer> getSanitizers(Map<String, MavenContext> included) { 153 List<MavenContextSanitizer> sanitizers = new ArrayList<MavenContextSanitizer>(); 154 sanitizers.add(new DuplicateSanitizer(included)); 155 sanitizers.add(new ConflictSanitizer(included)); 156 sanitizers.add(new CyclicSanitizer(included)); 157 return sanitizers; 158 } 159 160 protected boolean replacementFound(MavenContext context, Map<String, MavenContext> included) { 161 if (included.get(context.getArtifactIdentifier()) != null) { 162 return true; 163 } 164 Artifact related = context.getDependencyNode().getRelatedArtifact(); 165 if (related == null) { 166 return false; 167 } else { 168 String artifactId = getArtifactId(related); 169 return (included.get(artifactId) != null); 170 } 171 } 172 173 protected int getStateCount(List<Node<MavenContext>> nodes, State state) { 174 int count = 0; 175 for (Node<MavenContext> node : nodes) { 176 MavenContext context = node.getObject(); 177 DependencyNode dn = context.getDependencyNode(); 178 State nodeState = State.getState(dn.getState()); 179 count = (state == nodeState) ? ++count : count; 180 } 181 return count; 182 } 183 184 public List<MavenContext> getList(Node<MavenContext> node, State state) { 185 Assert.notNull(state, "state is required"); 186 List<Node<MavenContext>> list = node.getBreadthFirstList(); 187 List<MavenContext> newList = new ArrayList<MavenContext>(); 188 for (Node<MavenContext> element : list) { 189 MavenContext context = element.getObject(); 190 DependencyNode dn = context.getDependencyNode(); 191 State elementState = State.getState(dn.getState()); 192 if (!state.equals(elementState)) { 193 continue; 194 } 195 newList.add(context); 196 } 197 return newList; 198 } 199 200 public Map<String, MavenContext> getMap(Node<MavenContext> node, List<Node<MavenContext>> list, State state) { 201 Map<String, MavenContext> map = new HashMap<String, MavenContext>(); 202 for (Node<MavenContext> element : list) { 203 MavenContext context = element.getObject(); 204 DependencyNode dn = context.getDependencyNode(); 205 State elementState = State.getState(dn.getState()); 206 if (!state.equals(elementState)) { 207 continue; 208 } else { 209 map.put(context.getArtifactIdentifier(), context); 210 } 211 } 212 return map; 213 } 214 215 public void show(TreeMetaData md) { 216 logger.info("Metadata for " + md.getSize() + " dependency nodes"); 217 logger.info("states -" + toString(md.getStates())); 218 logger.info("requiredness -" + toString(md.getRequiredness())); 219 logger.info("scopes -" + toString(md.getScopes())); 220 logger.info("types -" + toString(md.getTypes())); 221 logger.info("classifiers -" + toString(md.getClassifiers())); 222 int groups = md.getGroupIds().size(); 223 int artifacts = md.getArtifactIds().size(); 224 int versions = md.getVersions().size(); 225 logger.info("unique gav info - groups:" + groups + " artifacts:" + artifacts + " versions:" + versions); 226 logger.info("unique artifacts (including version): " + md.getArtifactIdentifiers().size()); 227 logger.info("unique artifacts (ignoring version): " + md.getPartialArtifactIdentifiers().size()); 228 } 229 230 protected List<String> getArtifactIds(Collection<String> ids) { 231 List<String> strings = new ArrayList<String>(ids); 232 Collections.sort(strings); 233 return strings; 234 } 235 236 protected String toString(Tracker tracker) { 237 List<LabelCount> labels = new ArrayList<LabelCount>(); 238 for (String key : tracker.keySet()) { 239 labels.add(new LabelCount(key, tracker.get(key))); 240 } 241 Collections.sort(labels); 242 Collections.reverse(labels); 243 StringBuilder sb = new StringBuilder(); 244 for (LabelCount label : labels) { 245 sb.append(" " + label.getLabel() + ":" + label.getCount()); 246 } 247 return sb.toString(); 248 } 249 250 protected <T> void show(String label, List<T> list) { 251 logger.info(label); 252 for (T element : list) { 253 logger.info(element.toString()); 254 } 255 } 256 257 public TreeMetaData getMetaData(Node<MavenContext> node) { 258 List<Node<MavenContext>> list = node.getBreadthFirstList(); 259 TreeMetaData metaData = new TreeMetaData(); 260 metaData.setSize(list.size()); 261 for (Node<MavenContext> element : list) { 262 updateMetaData(metaData, element.getObject()); 263 } 264 return metaData; 265 } 266 267 protected void updateMetaData(TreeMetaData md, MavenContext context) { 268 DependencyNode dn = context.getDependencyNode(); 269 updateMetaData(md, dn.getArtifact()); 270 if (dn.getParent() != null) { 271 md.getStates().increment(context.getState().getValue()); 272 } 273 } 274 275 protected void updateMetaData(TreeMetaData md, Artifact a) { 276 md.getGroupIds().increment(a.getGroupId()); 277 md.getArtifactIds().increment(a.getArtifactId()); 278 md.getTypes().increment(a.getType()); 279 String classifier = a.getClassifier(); 280 if (!Helper.isBlank(classifier)) { 281 md.getClassifiers().increment(classifier); 282 } 283 md.getVersions().increment(a.getVersion()); 284 Scope scope = Scope.getScope(a.getScope()); 285 if (scope != null) { 286 md.getScopes().increment(scope.toString()); 287 } 288 md.getRequiredness().increment(a.isOptional() ? OPTIONAL : REQUIRED); 289 md.getArtifactIdentifiers().add(getArtifactId(a)); 290 md.getPartialArtifactIdentifiers().add(getPartialArtifactId(a)); 291 } 292 293 protected void updateGraphNodeStyle(MavenContext context) { 294 DependencyNode dn = context.getDependencyNode(); 295 boolean optional = dn.getArtifact().isOptional(); 296 State state = context.getState(); 297 Scope scope = Scope.getScope(dn.getArtifact().getScope()); 298 Style style = getStyle(scope, optional, state); 299 copyStyleProperties(context.getGraphNode(), style); 300 } 301 302 protected GraphNode getGraphNode(DependencyNode dn) { 303 Artifact a = dn.getArtifact(); 304 GraphNode n = new GraphNode(); 305 n.setId(counter.increment()); 306 n.setLabel(ng.getLabel(a)); 307 String fillcolor = dn.getParent() == null ? ROOT_FILL_COLOR : n.getFillcolor(); 308 n.setFillcolor(fillcolor); 309 return n; 310 } 311 312 public List<GraphNode> getGraphNodes(Node<MavenContext> node) { 313 List<GraphNode> nodes = new ArrayList<GraphNode>(); 314 if (node.isRoot()) { 315 nodes.add(node.getObject().getGraphNode()); 316 } 317 List<Node<MavenContext>> children = node.getChildren(); 318 for (Node<MavenContext> child : children) { 319 nodes.add(child.getObject().getGraphNode()); 320 nodes.addAll(getGraphNodes(child)); 321 } 322 return nodes; 323 } 324 325 public List<Edge> getEdges(Node<MavenContext> node, EdgeHandler handler) { 326 List<Edge> edges = new ArrayList<Edge>(); 327 List<Node<MavenContext>> children = node.getChildren(); 328 for (Node<MavenContext> child : children) { 329 edges.addAll(handler.getEdges(child)); 330 edges.addAll(getEdges(child, handler)); 331 } 332 return edges; 333 } 334 335 // Omit normal info from the label 336 public static String getRelationshipLabel(Scope scope, boolean optional, State state) { 337 List<String> labelTokens = new ArrayList<String>(); 338 if (!Scope.DEFAULT_SCOPE.equals(scope)) { 339 labelTokens.add(scope.name().toLowerCase()); 340 } 341 if (optional) { 342 labelTokens.add(OPTIONAL); 343 } 344 if (!State.INCLUDED.equals(state)) { 345 labelTokens.add(state.getValue()); 346 } 347 return toIdString(labelTokens); 348 } 349 350 public static String toIdString(List<String> strings) { 351 return toIdString(Helper.toArray(strings)); 352 } 353 354 public static String toIdString(String... strings) { 355 StringBuilder sb = new StringBuilder(); 356 for (int i = 0; i < strings.length; i++) { 357 if (i != 0) { 358 sb.append(":"); 359 } 360 sb.append(Helper.toEmpty(strings[i])); 361 } 362 return sb.toString(); 363 } 364 365 public void copyStyleProperties(Object dest, Style style) { 366 List<String> names = getStyleProperties(); 367 for (String name : names) { 368 String value = getProperty(style, name); 369 if (!Helper.isBlank(value)) { 370 copyProperty(dest, name, value); 371 } 372 } 373 } 374 375 protected List<String> getStyleProperties() { 376 try { 377 @SuppressWarnings("unchecked") 378 Map<String, ?> map = BeanUtils.describe(Style.DEFAULT_STYLE); 379 map.remove("class"); 380 return new ArrayList<String>(map.keySet()); 381 } catch (Exception e) { 382 throw new GraphException(e); 383 } 384 } 385 386 protected String getStyle(String property, Scope scope, boolean optional, State state) { 387 // State styling overrides everything 388 String key1 = "state." + state.getValue() + "." + property; 389 // Scope styling overrides "optional" properties 390 String key2 = "scope." + scope.getValue() + "." + property; 391 // Fall through to styling for the "optional" attribute on a dependency 392 String key3 = "optional." + property; 393 394 String value1 = properties.getProperty(key1); 395 String value2 = properties.getProperty(key2); 396 String value3 = properties.getProperty(key3); 397 398 if (!Helper.isBlank(value1)) { 399 return value1; 400 } else if (!Helper.isBlank(value2)) { 401 return value2; 402 } else if (!Helper.isBlank(value3) && optional) { 403 return value3; 404 } else { 405 return null; 406 } 407 408 } 409 410 public Style getStyle(Scope scope, boolean optional, State state) { 411 // This happens for the root node 412 scope = (scope == null) ? Scope.DEFAULT_SCOPE : scope; 413 state = (state == null) ? State.INCLUDED : state; 414 415 List<String> properties = getStyleProperties(); 416 Style style = new Style(); 417 418 for (String property : properties) { 419 String value = getStyle(property, scope, optional, state); 420 if (Helper.isBlank(value)) { 421 continue; 422 } 423 copyProperty(style, property, value); 424 } 425 return style; 426 } 427 428 protected Properties getProperties() { 429 String location = "classpath:dot.properties"; 430 ResourceLoader loader = new DefaultResourceLoader(); 431 Resource resource = loader.getResource(location); 432 InputStream in = null; 433 try { 434 Properties properties = new Properties(); 435 in = resource.getInputStream(); 436 properties.load(in); 437 return properties; 438 } catch (IOException e) { 439 throw new GraphException(e); 440 } finally { 441 IOUtils.closeQuietly(in); 442 } 443 } 444 445 protected String getProperty(Object bean, String name) { 446 try { 447 return BeanUtils.getProperty(bean, name); 448 } catch (Exception e) { 449 throw new GraphException(e); 450 } 451 } 452 453 protected void copyProperty(Object bean, String name, Object value) { 454 try { 455 BeanUtils.copyProperty(bean, name, value); 456 } catch (Exception e) { 457 throw new GraphException(e); 458 } 459 } 460 461 public void show(List<GraphNode> nodes, List<Edge> edges) { 462 int nodeCount = nodes.size(); 463 int edgeCount = edges.size(); 464 int nodeCountShown = getNodeCount(nodes); 465 int edgeCountShown = getEdgeCount(edges); 466 int nodeCountHidden = nodeCount - nodeCountShown; 467 int edgeCountHidden = edgeCount - edgeCountShown; 468 logger.info("Generated " + nodes.size() + " graph nodes and " + edges.size() + " edges"); 469 logger.info("Showing " + nodeCountShown + " nodes and " + edgeCountShown + " edges"); 470 logger.info("Hiding " + nodeCountHidden + " nodes and " + edgeCountHidden + " edges"); 471 } 472 473 protected int getEdgeCount(List<Edge> edges) { 474 int count = 0; 475 for (Edge edge : edges) { 476 GraphNode parent = edge.getParent(); 477 GraphNode child = edge.getChild(); 478 boolean hidden = parent.isHidden() || child.isHidden(); 479 count = hidden ? count : ++count; 480 } 481 return count; 482 } 483 484 protected int getNodeCount(List<GraphNode> nodes) { 485 int count = 0; 486 for (GraphNode node : nodes) { 487 count = node.isHidden() ? count : ++count; 488 } 489 return count; 490 } 491 492 /** 493 * [groupId]:[artifactId]:[type]:[classifier] 494 */ 495 public static String getPartialArtifactId(Artifact a) { 496 TokenCollector<Artifact> collector = new VersionFreeArtifactTokenCollector(); 497 return toIdString(collector.getTokens(a)); 498 } 499 500 /** 501 * [groupId]:[artifactId]:[type]:[classifier]:[version] 502 */ 503 public static String getArtifactId(Artifact a) { 504 TokenCollector<Artifact> collector = new ArtifactIdTokenCollector(); 505 return toIdString(collector.getTokens(a)); 506 } 507 508 }