
org.fusesource.mvnplugins.graph.DependencyVisualizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven-graph-plugin Show documentation
Show all versions of maven-graph-plugin Show documentation
Creates a dependency graph of your project using graphvis
/**
* 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