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 }