All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fusesource.mvnplugins.graph.DependencyVisualizer Maven / Gradle / Ivy

There is a newer version: 1.45
Show newest version
/**
 * Copyright (C) 2009 Progress Software, Inc.
 * http://fusesource.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fusesource.mvnplugins.graph;

import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.artifact.Artifact;
import org.codehaus.plexus.util.cli.*;
import org.codehaus.plexus.util.FileUtils;

import java.util.*;
import java.io.File;
import java.io.PrintStream;

/**
 * @author Hiram Chirino
 */
public class DependencyVisualizer {

    LinkedHashMap nodes = new LinkedHashMap();
    LinkedHashSet edges = new LinkedHashSet();
    HashSet hideScopes = new HashSet();
    boolean hideOptional;
    boolean hidePoms;
    boolean hideOmitted;
    boolean hideExternal;
    boolean hideVersion;
    boolean hideGroupId;
    boolean hideType;
    boolean keepDot;
    String label;
    boolean hideTransitive;
    Log log;
    boolean cascade;
    String direction="TB";

    private class Node {
        private final String id;
        private final ArrayList children = new ArrayList();
        private final ArrayList parents = new ArrayList();
        private final Artifact artifact;
        private int roots;

        public Node(String id, Artifact artifact) {
            this.id = id;
            this.artifact = artifact;
        }

        @Override
        public boolean equals(Object obj) {
            return id.equals(((Node) obj).id);
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }

        @Override
        public String toString() {
            return id;
        }

        public boolean isHidden() {
            if ( hidePoms && isExclusivelyType("pom") ) {
                return true;
            }
            if( hideExternal && roots == 0 ) {
                return true;
            }
            return false;
        }

        public String getId() {
            return id;
        }

        public String getLabel() {
            final Artifact a = artifact;
            StringBuilder sb = new StringBuilder();
            if( !hideGroupId ) {
                sb.append( a.getGroupId() + "\\n");
            }
            sb.append( a.getArtifactId());
            if( !hideType ) {
                if (!isExclusivelyType("jar")) {
                    sb.append("\\n");
                    boolean first=true;
                    for (String type : getTypes()) {
                        if( !first  ) {
                            sb.append(" | ");
                        }
                        first=false;
                        sb.append(type);
                    }
                }
            }
            if( !hideVersion ) {
                sb.append("\\n" + a.getVersion());
            }
            return sb.toString();
        }

        public String getColor() {
            if (isScope("test")) {
                return "cornflowerblue";
            }
            if (isOptional()) {
                return "green";
            }
            if (isScope("provided")) {
                return "darkgrey";
            }
            return "black";
        }

        private boolean isScope(String scope) {
            return roots==0 && !parents.isEmpty() && allMatchScope(parents, scope);
        }

        public String getFillColor() {
            if( roots > 0 ) {
                return "#dddddd"; 
            }
            return "white";
        }
        public String getFontColor() {
            return getColor();
        }

        public String getLineStyle() {
            String rc = isOptional() ? "dotted" : "solid";
            rc += ",filled";
            return rc;
        }

        public double getFontSize() {
            if( roots > 0 ) {
                return 14;
            }
            return 8;
        }

        public boolean isOptional() {
            return roots==0 && !parents.isEmpty() && allMatchOptional(parents, true);
        }


        private boolean allMatchScope(ArrayList edges, String scope) {
            for (Edge e : edges) {
                if (!e.isScope(scope)) {
                    return false;
                }
            }
            return true;
        }
        private boolean allMatchOptional(ArrayList edges, boolean value) {
            for (Edge e : edges) {
                if (e.optional != value) {
                    return false;
                }
            }
            return true;
        }

        private Set getTypes() {
            LinkedHashSet rc = new LinkedHashSet();
            rc.add(artifact.getType() + (artifact.getClassifier()==null? "" : (":" + artifact.getClassifier())));
            for (Edge e : parents) {
                Artifact artifact = e.dependencyNode.getArtifact();
                rc.add(artifact.getType() + (artifact.getClassifier()==null? "" : (":" + artifact.getClassifier())));
            }
            return rc;
        }

        private boolean isExclusivelyType(String value) {
            Set types = getTypes();
            return types.size()==1 && types.contains(value); 
        }

        public int getRecursiveChildCount() {
            int rc = children.size();
            for (Edge child : children) {
                int t = child.getRecursiveChildCount();
                if( t > rc ) {
                    rc = t;
                }
            }
            return rc;
        }

    }

    private class Edge {
        private Node parent;
        private Node child;
        private String scope;
        private boolean optional;
        private DependencyNode dependencyNode;

        public Edge(Node parent, Node child, DependencyNode dependencyNode) {
            this.parent = parent;
            this.child = child;
            this.dependencyNode = dependencyNode;
            this.scope = dependencyNode.getArtifact().getScope();
            this.optional = dependencyNode.getArtifact().isOptional();
        }
        public Edge(Edge edge) {
            this.parent = edge.parent;
            this.child = edge.child;
            this.dependencyNode = edge.dependencyNode;
            this.scope = edge.scope;
            this.optional = edge.optional;
        }

        public Edge optional(boolean optional) {
            if ( this.optional == optional) {
                return this;
            }
            Edge rc =  new Edge(this);
            rc.optional = optional;
            return rc;
        }
        
        public Edge scope(String scope) {
            if ( this.scope.equals(scope) ) {
                return this;
            }
            Edge rc =  new Edge(this);
            rc.scope = scope;
            return rc;
        }

        public boolean isHidden() {
            if( hideTransitive && dependencyNode.getParent().getParent()!=null ) {
                return true;
            }
            if(hideOptional && optional)
                return true;
            if(hideScopes.contains(scope) )
                return true;
            
            final int state = dependencyNode.getState();
            if(hideOmitted && (state==DependencyNode.OMITTED_FOR_CONFLICT || state==DependencyNode.OMITTED_FOR_CYCLE) ) {
                return true;
            }
            return false;
        }

        public boolean isScope(String s) {
            return scope.equals(s);
        }

        public String getLineStyle() {
            if( optional ) {
                return "dotted";
            }
            return "solid";
        }

        public String getLabel() {
            StringBuilder sb = new StringBuilder();
            if ( !isScope("compile")) {
                sb.append(scope);
            }
            if ( optional ) {
                if( sb.length()!=0 ) {
                    sb.append(",");
                }
                sb.append("optional");
            }
            return sb.toString();
        }

        public String getColor() {
            if (isScope("test")) {
                return "cornflowerblue";
            }
            return "black";
        }

        double getWeight() {
            double rc = 1 + getRecursiveChildCount();
            if ( isScope("compile")) {
                rc *= 2;
            }
            if ( !optional ) {
                rc *= 2;
            }
            return rc;
        }

        private int getRecursiveChildCount() {
            return child.getRecursiveChildCount();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Edge edge = (Edge) o;

            if (parent != null ? !parent.equals(edge.parent) : edge.parent != null) return false;
            if (child != null ? !child.equals(edge.child) : edge.child != null) return false;
            if (scope != null ? !scope.equals(edge.scope) : edge.scope != null) return false;
            if (optional != edge.optional) return false;
            return true;
        }

        @Override
        public int hashCode() {
            int result = parent != null ? parent.hashCode() : 0;
            result = 31 * result + (child != null ? child.hashCode() : 0);
            result = 31 * result + (scope != null ? scope.hashCode() : 0);
            result = 31 * result + (optional ? 1 : 0);
            return result;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "parent=" + parent +
                    ", child=" + child +
                    ", scope='" + scope + '\'' +
                    ", optional=" + optional +
                    '}';
        }
    }

    public void add(DependencyNode dn) {
        add(dn, true);
    }

    private Node add(DependencyNode dn, boolean root) {
        Node parent = getNode(dn);
        if( root ) {
            parent.roots++;
        }
        if (dn.hasChildren()) {
            for (DependencyNode c : (List) dn.getChildren()) {
                Node child = add(c, false);
                Edge edge = new Edge(parent, child, c);
                add(edge);
            }
        }
        return parent;
    }

    private Node getNode(DependencyNode dn) {
        Artifact artifact = dn.getArtifact();
        String id = artifact.getGroupId()+":"+artifact.getArtifactId()+":"+artifact.getVersion();
        if( artifact.getClassifier()!=null ) {
            id += ":"+artifact.getClassifier();
        }
        Node node = nodes.get(id);
        if (node == null) {
            node = new Node(id, dn.getArtifact());
            nodes.put(id, node);
        }
        return node;
    }

    private void add(Edge edge) {
        if (edges.add(edge)) {
            edge.child.parents.add(edge);
            edge.parent.children.add(edge);
        }
    }

    private void remove(Node node) {
        nodes.remove(node.getId());

        // Remove the edges attached to this node...
        for (Edge edge : new ArrayList(node.parents)) {
            remove(edge);
        }
        for (Edge edge : new ArrayList(node.children)) {
            remove(edge);
        }
    }

    private void remove(Edge edge) {
        edge.parent.children.remove(edge);
        edge.child.parents.remove(edge);
        edges.remove(edge);
    }

    public void export(File target) throws MojoExecutionException {

        // Drop nodes and edges which are hidden...
        for (Node node : new ArrayList(nodes.values()) ) {
            if (node.isHidden()) {
                log.debug("Dropping hidden node: "+node);
                remove(node);
            }
        }
        for (Edge edge : new ArrayList(edges) ) {
            if (edge.isHidden()) {
                log.debug("Dropping hidden edge: "+edge);
                remove(edge);
            }

        }

        if ( cascade ) {
            // Propagate the attributes down to the children.

            LinkedList ll = new LinkedList(nodes.values());
            while( !ll.isEmpty() ) {
                // Optional propagates...
                Node node = ll.removeFirst();
                if( node.isOptional() )  {
                    for (Edge edge : new ArrayList(node.children)) {
                        if( !edge.optional ) {
                            remove(edge);
                            add(edge.optional(true));

                            // If a child filpped to optional.. then we need
                            // to enqueue so we process it's children
                            if( edge.child.isOptional() ) {
                                ll.addLast(edge.child);
                            }
                        }
                    }
                }

                // scope propagates....
                if( node.isScope("test") )  {
                    for (Edge edge : new ArrayList(node.children)) {
                        if( !edge.isScope("test") ) {
                            remove(edge);
                            add(edge.scope("test"));

                            // If a child filpped to test.. then we need
                            // to enqueue so we process it's children
                            if( edge.child.isScope("test") ) {
                                ll.addLast(edge.child);
                            }
                        }
                    }
                }
            }
        }

        // Remove all the non root nodes that are disconnected.
        for (Node node : new ArrayList(nodes.values()) ) {
            if (node.parents.size()==0 && node.roots==0) {
                log.debug("Dropping orphaned node: "+node);
                remove(node);
            }
        }

        // Write the source file...
        boolean convertDotFile=true;
        File source = new File(target.getParentFile(), target.getName() + ".dot");

        // User might just be requesting a dot file..
        if( target.getName().endsWith(".dot") ) {
            convertDotFile = false;
            source = target;
        }

        PrintStream os = null;
        try {
            log.debug("Exporting to: "+source);
            os = new PrintStream(source);
            DotExporter exporter = new DotExporter(os);
            exporter.export();
        } catch (Exception e) {
            throw new MojoExecutionException("Could not create the dot file used to generate the image.", e);
        } finally {
            os.close();
        }


        if (!convertDotFile) {
            return;
        }
        
        try {
            Commandline commandline = new Commandline();
            try {
                commandline.addSystemEnvironment();
            } catch (Exception ignore) {
            }
            commandline.setExecutable("dot");
            commandline.addArguments(new String[]{
                    "-T" + FileUtils.getExtension(target.getName()),
                    "-o" + target.getAbsolutePath(),
                    source.getAbsolutePath()
            });

            log.debug("Executing dot command...");
            int rc = CommandLineUtils.executeCommandLine(commandline, new DefaultConsumer(), new DefaultConsumer());
            if (rc != 0) {
                throw new MojoExecutionException("Execution of the 'dot' command failed.  Perhaps it's not installed.  See: http://www.graphviz.org/");
            }
            log.debug("Graph generated. ");
            if( !keepDot ) {
                source.delete();
            }

        } catch (CommandLineException e) {
            throw new MojoExecutionException("Execution of the 'dot' command failed.", e);
        }

    }

    private class DotExporter {
        private final PrintStream out;
        int indent = 0;

        public DotExporter(PrintStream os) {
            this.out = os;
        }

        public void export() {
        	
            String graphFont = "Serif";
            String nodeFont = "SanSerif";
            
            String osName = System.getProperty("os.name", "NO OS NAME!!");
            if (osName.contains("Windows")) {
                graphFont = "arial";
                nodeFont = "arial";
            }
        	
            p("digraph dependencies {").i(1);
            {
                p("graph [").i(1);
                {
                    if (label != null) {
                        p("label=" + q(label));
                    }
                    p("labeljust=l");
                    p("labelloc=t");
                    p("fontsize=18");
                    p("fontname="+q(graphFont));
                    p("ranksep=1");
                    p("rankdir="+q(direction));
                    p("nodesep=.05");

                }
                i(-1).p("];");
                p("node [").i(1);
                {
                    p("fontsize=8");
                    p("fontname="+q(nodeFont));
                    p("shape=\"rectangle\"");
                }
                i(-1).p("];");
                p("edge [").i(1);
                {
                    p("fontsize=8");
                    p("fontname="+q(nodeFont));
                }
                i(-1).p("];");

                // Write the nodes..
                for (Node node : nodes.values()) {
                    p(q(node.getId()) + " [").i(1);
                    {
                        p("fontsize="+node.getFontSize());
                        p("label=" + q(node.getLabel()));
                        p("color=" + q(node.getColor()));
                        p("fontcolor=" + q(node.getFontColor()));
                        p("fillcolor=" + q(node.getFillColor()));
                        p("style=" + q(node.getLineStyle()));
                    }
                    i(-1).p("];");
                }

                // Write the edges..
                for (Edge edge : edges) {
                    p(q(edge.parent.getId()) + " -> " + q(edge.child.getId()) + " [").i(1);
                    {
                        p("label=" + q(edge.getLabel()));
                        p("style=" + q(edge.getLineStyle()));
                        p("color=" + q(edge.getColor()));
                        p("fontcolor=" + q(edge.getColor()));
                        p("weight=" + edge.getWeight());
                    }
                    i(-1).p("];");
                }

            }
            i(-1).p("}");
        }

        private String q(String value) {
            return "\"" + value + "\"";
        }

        private DotExporter i(int indent) {
            this.indent += indent;
            return this;
        }

        private DotExporter p(String x) {
            for (int i = 0; i < indent; i++) {
                out.print("  ");
            }
            out.println(x);
            return this;
        }

    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy