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    }