001    package org.kuali.maven.plugins.graph.dot.edge;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.List;
006    import java.util.Map;
007    
008    import org.apache.maven.shared.dependency.tree.DependencyNode;
009    import org.kuali.maven.plugins.graph.dot.GraphException;
010    import org.kuali.maven.plugins.graph.pojo.Edge;
011    import org.kuali.maven.plugins.graph.pojo.GraphNode;
012    import org.kuali.maven.plugins.graph.pojo.MavenContext;
013    import org.kuali.maven.plugins.graph.pojo.Scope;
014    import org.kuali.maven.plugins.graph.pojo.State;
015    import org.kuali.maven.plugins.graph.tree.Node;
016    import org.kuali.maven.plugins.graph.tree.TreeHelper;
017    import org.slf4j.Logger;
018    import org.slf4j.LoggerFactory;
019    
020    public class SmartEdgeHandler extends FlatEdgeHandler {
021        private static final Logger logger = LoggerFactory.getLogger(SmartEdgeHandler.class);
022        public static final String REPLACEMENT_LABEL = "replacement";
023        TreeHelper helper = new TreeHelper();
024    
025        Map<String, MavenContext> conflictsMap = new HashMap<String, MavenContext>();
026    
027        @Override
028        public List<Edge> getEdges(Node<MavenContext> node) {
029            List<Edge> edges = new ArrayList<Edge>();
030            handleNode(node, edges);
031            return edges;
032        }
033    
034        protected void handleNode(Node<MavenContext> node, List<Edge> edges) {
035            logger.debug("handling node {}", node.getObject().getId());
036            switch (node.getObject().getState()) {
037            case INCLUDED:
038            case CYCLIC:
039            case UNKNOWN:
040                // Just draw a line from parent to child
041                // Styling draws attention to CYCLIC and UNKNOWN nodes
042                edges.addAll(super.getEdges(node));
043                return;
044            case DUPLICATE:
045                // Draw a line from our parent to the included node containing our same artifact
046                handleDuplicate(node, edges);
047                return;
048            case CONFLICT:
049                /**
050                 * Draw two lines. One from our parent to the node that has been conflicted out (might be us, might be
051                 * another node if the same artifact has been conflicted out twice). Draw a second line from that node to
052                 * the node containing the artifact Maven replaced it with.
053                 */
054                handleConflict(node, edges);
055                return;
056            default:
057                throw new GraphException("Unknown state " + node.getObject().getState());
058            }
059        }
060    
061        protected void handleConflict(Node<MavenContext> node, List<Edge> edges) {
062            MavenContext context = node.getObject();
063            String artifactIdentifier = TreeHelper.getArtifactId(context.getDependencyNode().getRelatedArtifact());
064    
065            // Find the node containing the replacement artifact Maven is actually going to use
066            Node<MavenContext> replacement = findIncludedNode(node.getRoot(), artifactIdentifier);
067    
068            // Check to see if we've encountered this same conflict before
069            MavenContext contextToUse = conflictsMap.get(artifactIdentifier);
070            if (contextToUse == null) {
071                contextToUse = context;
072                conflictsMap.put(artifactIdentifier, contextToUse);
073    
074                // Draw an edge from contextToUse to the replacement
075                GraphNode parent = contextToUse.getGraphNode();
076                GraphNode child = replacement.getObject().getGraphNode();
077                Edge edge = getEdge(parent, child, false, Scope.DEFAULT_SCOPE, State.CONFLICT);
078                edge.setLabel(REPLACEMENT_LABEL);
079                edges.add(edge);
080            } else {
081                // Hide ourself since contextToUse represents the same artifact
082                context.getGraphNode().setHidden(true);
083            }
084    
085            // Draw an edge from our parent to contextToUse
086            GraphNode parent = node.getParent().getObject().getGraphNode();
087            GraphNode child = contextToUse.getGraphNode();
088            Edge edge = getEdge(parent, child, false, Scope.DEFAULT_SCOPE, State.CONFLICT);
089    
090            edges.add(edge);
091    
092        }
093    
094        protected void handleDuplicate(Node<MavenContext> node, List<Edge> edges) {
095            MavenContext context = node.getObject();
096            DependencyNode dn = context.getDependencyNode();
097    
098            // Find the node that replaces us
099            Node<MavenContext> replacement = findIncludedNode(node.getRoot(), context.getArtifactIdentifier());
100            // This is our parent in the tree
101            GraphNode parent = node.getParent().getObject().getGraphNode();
102            // This is the node that is being used instead of us
103            GraphNode child = replacement.getObject().getGraphNode();
104            // Use our optional/scope settings
105            boolean optional = dn.getArtifact().isOptional();
106            Scope scope = Scope.getScope(dn.getArtifact().getScope());
107    
108            // Draw an edge from our parent to the node that replaced us
109            Edge edge = getEdge(parent, child, optional, scope, State.INCLUDED);
110    
111            // Add this new edge to the list
112            edges.add(edge);
113    
114            // Hide ourself
115            context.getGraphNode().setHidden(true);
116        }
117    
118        protected Node<MavenContext> findIncludedNode(Node<MavenContext> root, String artifactId) {
119            List<Node<MavenContext>> nodes = root.getBreadthFirstList();
120            for (Node<MavenContext> node : nodes) {
121                MavenContext context = node.getObject();
122                State state = context.getState();
123                String artifactIdentifier = context.getArtifactIdentifier();
124                if (state == State.INCLUDED && artifactIdentifier.equals(artifactId)) {
125                    return node;
126                }
127            }
128            throw new GraphException("Inconsistent tree.  Can't locate " + artifactId);
129        }
130    }