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

org.kuali.maven.plugins.graph.mojo.MojoHelper Maven / Gradle / Ivy

Go to download

Create customizable dependency graphs for Maven projects using Graphviz. The Graphviz "dot" executable needs to be installed and in your path for the plugin to produce graphs - http://www.graphviz.org/

There is a newer version: 1.2.3
Show newest version
/**
 * Copyright 2011-2012 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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.kuali.maven.plugins.graph.mojo;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.codehaus.plexus.util.StringUtils;
import org.kuali.maven.plugins.graph.collector.ArtifactIdTokenCollector;
import org.kuali.maven.plugins.graph.collector.MavenContextTokenCollector;
import org.kuali.maven.plugins.graph.collector.TokenCollector;
import org.kuali.maven.plugins.graph.dot.Dot;
import org.kuali.maven.plugins.graph.dot.GraphHelper;
import org.kuali.maven.plugins.graph.dot.StringGenerator;
import org.kuali.maven.plugins.graph.filter.ArtifactFilterWrapper;
import org.kuali.maven.plugins.graph.filter.DepthFilter;
import org.kuali.maven.plugins.graph.filter.Filter;
import org.kuali.maven.plugins.graph.filter.Filters;
import org.kuali.maven.plugins.graph.filter.IncludeExcludeFilter;
import org.kuali.maven.plugins.graph.filter.MatchCondition;
import org.kuali.maven.plugins.graph.filter.MavenContextFilterWrapper;
import org.kuali.maven.plugins.graph.filter.NodeFilter;
import org.kuali.maven.plugins.graph.filter.NodeFilterChain;
import org.kuali.maven.plugins.graph.filter.ReverseNodeFilter;
import org.kuali.maven.plugins.graph.pojo.Category;
import org.kuali.maven.plugins.graph.pojo.Conflicts;
import org.kuali.maven.plugins.graph.pojo.Edge;
import org.kuali.maven.plugins.graph.pojo.Graph;
import org.kuali.maven.plugins.graph.pojo.GraphDescriptor;
import org.kuali.maven.plugins.graph.pojo.GraphException;
import org.kuali.maven.plugins.graph.pojo.GraphNode;
import org.kuali.maven.plugins.graph.pojo.Layout;
import org.kuali.maven.plugins.graph.pojo.MavenContext;
import org.kuali.maven.plugins.graph.pojo.MojoContext;
import org.kuali.maven.plugins.graph.pojo.Row;
import org.kuali.maven.plugins.graph.pojo.Scope;
import org.kuali.maven.plugins.graph.processor.CascadeOptionalProcessor;
import org.kuali.maven.plugins.graph.processor.FlatEdgeProcessor;
import org.kuali.maven.plugins.graph.processor.HideConflictsProcessor;
import org.kuali.maven.plugins.graph.processor.HideDuplicatesProcessor;
import org.kuali.maven.plugins.graph.processor.LabelProcessor;
import org.kuali.maven.plugins.graph.processor.LinkedEdgeProcessor;
import org.kuali.maven.plugins.graph.processor.PathDisplayProcessor;
import org.kuali.maven.plugins.graph.processor.PathTreeDisplayProcessor;
import org.kuali.maven.plugins.graph.processor.Processor;
import org.kuali.maven.plugins.graph.processor.ReduceClutterProcessor;
import org.kuali.maven.plugins.graph.processor.SanitizingProcessor;
import org.kuali.maven.plugins.graph.processor.ShowMetadataProcessor;
import org.kuali.maven.plugins.graph.processor.StyleProcessor;
import org.kuali.maven.plugins.graph.processor.TreeDisplayProcessor;
import org.kuali.maven.plugins.graph.processor.ValidatingProcessor;
import org.kuali.maven.plugins.graph.tree.Node;
import org.kuali.maven.plugins.graph.tree.TreeHelper;
import org.kuali.maven.plugins.graph.util.Counter;
import org.kuali.maven.plugins.graph.util.Helper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MojoHelper {
    private static final Logger logger = LoggerFactory.getLogger(MojoHelper.class);
    Filters filters = new Filters();

    public Filter> getIncludeExcludeFilter(GraphDescriptor descriptor) {
        NodeFilter include = getIncludeFilter(descriptor);
        NodeFilter exclude = getExcludeFilter(descriptor);
        return new IncludeExcludeFilter>(include, exclude);
    }

    public void categories(MojoContext mc, GraphDescriptor gc, List categories) {
        if (mc.isSkip()) {
            logger.info("Skipping execution");
            return;
        }
        if (Helper.isEmpty(categories) && !mc.isGenerateDefaultGraphs()) {
            logger.info("No categories");
            return;
        }
        if (mc.isGenerateDefaultGraphs()) {
            categories.addAll(0, getDefaultCategories(gc));
        }
        int count = 0;
        for (Category category : categories) {
            for (Row row : category.getRows()) {
                row.setCategory(category);
                fillInDescriptors(gc, row.getDescriptors(), mc.getOutputDir(), row);
                List executed = execute(mc, gc, row.getDescriptors());
                count += executed.size();
                row.setDescriptors(executed);
            }
        }
        if (count == 0) {
            logger.info("No graphs to generate");
        }
    }

    public List executeMulti(MojoContext mc, GraphDescriptor gd, List descriptors) {
        try {
            if (mc.isSkip()) {
                logger.info("Skipping execution");
                return null;
            }
            if (Helper.isEmpty(descriptors) && !mc.isGenerateDefaultGraphs()) {
                logger.info("No descriptors");
                return null;
            }
            if (mc.isGenerateDefaultGraphs()) {
                descriptors.addAll(0, getDefaultDescriptors(mc, gd));
            }
            fillInDescriptors(gd, descriptors, mc.getOutputDir(), null);
            List executedGraphs = new ArrayList();
            int count = 0;
            for (GraphDescriptor descriptor : descriptors) {
                GraphDescriptor executed = execute(mc, descriptor);
                if (executed != null) {
                    executedGraphs.add(executed);
                    count++;
                }
            }
            if (count == 0) {
                logger.info("No graphs to generate");
            }
            return executedGraphs;
        } catch (Exception e) {
            throw new GraphException(e);
        }
    }

    public List execute(MojoContext mc, GraphDescriptor gd, List descriptors) {
        try {
            if (mc.isSkip()) {
                logger.info("Skipping execution");
                return null;
            }
            if (Helper.isEmpty(descriptors)) {
                logger.info("No descriptors");
                return null;
            }
            List executedGraphs = new ArrayList();
            for (GraphDescriptor descriptor : descriptors) {
                GraphDescriptor executed = execute(mc, descriptor);
                if (executed != null) {
                    executedGraphs.add(executed);
                }
            }
            return executedGraphs;
        } catch (Exception e) {
            throw new GraphException(e);
        }
    }

    protected void fillInDescriptors(GraphDescriptor gd, List gds, File outputDir, Row row) {
        gd.setRow(row);
        logger.debug("default output format={}", gd.getOutputFormat());
        Counter counter = new Counter(1);
        for (GraphDescriptor desc : gds) {
            Helper.copyPropertiesIfNull(desc, gd);
            if (desc.getName() == null) {
                desc.setName(counter.increment() + "");
            }
            if (desc.getTransitive() == null) {
                desc.setTransitive(true);
            }
            if (desc.getLayout() == null) {
                desc.setLayout(Layout.LINKED);
            }
            String relativeFilename = desc.getRelativeFilename();
            if (relativeFilename == null) {
                String path = "";
                String filename = getFilename(desc);
                if (row != null) {
                    path = getPathFromRow(desc.getRow());
                }
                relativeFilename = StringUtils.isBlank(path) ? filename : path + "/" + filename;
            }
            if (desc.getFile() == null) {
                File file = new File(outputDir, relativeFilename);
                desc.setFile(file);
            }
        }
    }

    protected List getDefaultDescriptors(MojoContext mc, GraphDescriptor gd) {
        List descriptors = new ArrayList();
        List categories = getDefaultCategories(gd);
        for (Category category : categories) {
            for (Row row : category.getRows()) {
                descriptors.addAll(row.getDescriptors());
                fillInDescriptors(gd, row.getDescriptors(), mc.getOutputDir(), row);
            }
        }
        return descriptors;
    }

    protected List getDefaultCategories(GraphDescriptor gd) {
        List categories = new ArrayList();
        categories.add(getCategory(gd, false));
        categories.add(getCategory(gd, true));
        return categories;
    }

    protected String getPathFromRow(Row row) {
        StringBuilder sb = new StringBuilder();
        sb.append(row.getCategory().getName());
        sb.append("/");
        sb.append(row.getName());
        return sb.toString();
    }

    protected String getFilename(GraphDescriptor gd) {
        StringBuilder sb = new StringBuilder();
        sb.append(gd.getName());
        sb.append(".");
        sb.append(gd.getOutputFormat());
        return sb.toString();
    }

    protected String getRelativeFilename(GraphDescriptor gd) {
        StringBuilder sb = new StringBuilder();
        sb.append(getPathFromRow(gd.getRow()));
        sb.append("/");
        sb.append(getFilename(gd));
        return sb.toString();
    }

    protected Category getCategory(GraphDescriptor gd, boolean transitive) {
        String name = getTransitiveLabel(transitive);
        Category c = new Category(name);
        c.setDescription(getDescription(transitive));
        c.setRows(getRows(gd, transitive, c));
        return c;
    }

    protected String getTransitiveLabel(boolean transitive) {
        return transitive ? "transitive" : "direct";
    }

    protected String getDescription(Scope scope) {
        if (scope == null) {
            return "These are the dependencies of the project for all scopes.";
        }
        switch (scope) {
        case COMPILE:
            return "These dependencies are required for compilation.  They are available in all classpaths of the project and are also propagated as transitive dependencies to projects that depend on this project";
        case IMPORT:
            return "This scope is only used on a dependency of type pom in the  section. It indicates that the specified POM should be replaced with the dependencies in that POM's  section";
        case PROVIDED:
            return "Similar to compile, but with the expectation that the JDK or a container will provide the dependency at runtime.  These dependencies are only available on the compilation and test classpaths, and are not transitive.";
        case RUNTIME:
            return "These dependencies are not required for compilation, but are for execution. They are in the runtime classpath (eg WEB-INF/lib) and test classpath, but not the compile classpath.";
        case SYSTEM:
            return "This scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact must always be available and is not looked up in a repository.";
        case TEST:
            return "These dependencies are only required to compile and run unit tests for the application";
        default:
            throw new IllegalArgumentException("Unknown scope " + scope);
        }
    }

    protected String getDescription(boolean transitive) {
        if (transitive) {
            return "These are the dependencies declared in this project's pom plus any transitive dependencies of those dependencies.";
        } else {
            return "These are the dependencies declared in this project's pom";
        }
    }

    protected String getScopeLabel(Scope scope) {
        return scope == null ? "all" : scope.toString();
    }

    protected List getRows(GraphDescriptor gd, boolean transitive, Category category) {
        List rows = new ArrayList();
        Row any = new Row(getScopeLabel(null));
        any.setCategory(category);
        any.setDescriptors(getDescriptors(gd, any, transitive, null));
        any.setDescription(getDescription(null));
        rows.add(any);
        for (Scope scope : Scope.values()) {
            Row row = new Row(getScopeLabel(scope));
            row.setCategory(category);
            row.setDescription(getDescription(scope));
            row.setDescriptors(getDescriptors(gd, row, transitive, scope));
            rows.add(row);
        }
        return rows;
    }

    protected List getDescriptors(GraphDescriptor gd, Row row, boolean transitive, Scope scope) {
        List descriptors = new ArrayList();
        for (Layout layout : Layout.values()) {
            descriptors.add(getDescriptor(gd, row, transitive, scope, layout));
        }
        return descriptors;
    }

    protected GraphDescriptor getDescriptor(GraphDescriptor gd, Row row, boolean transitive, Scope scope, Layout layout) {
        GraphDescriptor descriptor = Helper.copyProperties(GraphDescriptor.class, gd);
        descriptor.setShow(scope == null ? null : scope.toString());
        descriptor.setTransitive(transitive);
        descriptor.setName(layout.toString().toLowerCase());
        descriptor.setLayout(layout);
        descriptor.setRow(row);
        return descriptor;
    }

    public GraphDescriptor execute(MojoContext mc, GraphDescriptor gc) {
        if (mc.isSkip()) {
            logger.info("Skipping execution");
            return null;
        }

        try {
            Node tree = getProcessedTree(mc, gc);
            Graph graph = getGraph(tree, mc, gc);
            if (isEmptyGraph(graph) && Boolean.TRUE.equals(gc.getSkipEmptyGraphs())) {
                logger.debug("Skipping empty graph");
                return null;
            }
            String content = getDotFileContent(graph);
            Dot dot = new Dot();
            dot.fillInContext(gc, content);
            int exitValue = dot.execute(gc);
            if (exitValue == Dot.SUCCESS) {
                logger.info(gc.getFile().getPath());
                return gc;
            } else {
                return null;
            }
        } catch (Exception e) {
            throw new GraphException(e);
        }
    }

    protected boolean isEmptyGraph(Graph graph) {
        int count = 0;
        List nodes = graph.getNodes();
        for (GraphNode node : nodes) {
            if (!node.isHidden()) {
                count++;
            }
        }
        return count <= 1;
    }

    protected String getDotFileContent(Graph graph) {
        return new StringGenerator().getString(graph);
    }

    public DependencyNode getMavenTree(MojoContext c) {
        try {
            DependencyTreeBuilder builder = c.getTreeBuilder();
            return builder.buildDependencyTree(c.getProject(), c.getLocalRepository(), c.getArtifactFactory(),
                    c.getArtifactMetadataSource(), null, c.getArtifactCollector());
        } catch (DependencyTreeBuilderException e) {
            throw new GraphException(e);
        }
    }

    protected List getProcessors(GraphDescriptor gd, boolean verbose) {
        List processors = new ArrayList();

        // Generate node labels
        processors.add(new LabelProcessor(gd));

        // Show some metadata
        if (verbose) {
            processors.add(new ShowMetadataProcessor());
        }

        // Figure out what nodes we are going to display
        processors.add(getDisplayProcessor(gd));

        // Style the nodes based on scope, optional/required, and state
        processors.add(new StyleProcessor());

        // Generate lines connecting the tree nodes
        processors.addAll(getEdgeProcessors(gd));

        // Hide duplicates
        if (!Boolean.TRUE.equals(gd.getShowDuplicates())) {
            processors.add(new HideDuplicatesProcessor());
        }

        return processors;
    }

    protected Processor getDisplayProcessor(GraphDescriptor gd) {
        switch (gd.getDisplay()) {
        case PATH:
            return new PathDisplayProcessor(gd, false);
        case TREE:
            return new TreeDisplayProcessor(gd);
        case PT: // Path and Tree
            return new PathTreeDisplayProcessor(gd);
        default:
            throw new IllegalStateException("Unknown filter type " + gd.getDisplay());
        }

    }

    protected Node getProcessedTree(MojoContext mc, GraphDescriptor gc) {
        TreeHelper helper = new TreeHelper();
        if (mc.getMavenTree() == null) {
            DependencyNode mavenTree = getMavenTree(mc);
            mc.setMavenTree(mavenTree);
        }
        if (mc.getSanitizedTree() == null) {
            // The sanitized tree has all the funky conflict/duplicate stuff sorted out,
            // but has no edges or styling except for the root node being gray
            Node tree = helper.getTree(mc.getMavenTree());
            sanitizeTree(tree, gc);
            mc.setSanitizedTree(tree);

        }
        Node copy = helper.copy(mc.getSanitizedTree());
        List processors = getProcessors(gc, mc.isVerbose());
        for (Processor processor : processors) {
            logger.debug("processor={}", processor.getClass());
            processor.process(copy);
        }
        return copy;
    }

    protected void sanitizeTree(Node node, GraphDescriptor gd) {
        // Validate some basic things about the tree Maven gave us
        new ValidatingProcessor().process(node);

        // Clean up a few funky edge cases
        // Maven gives us a tree where CONFLICT nodes have identical artifacts and DUPLICATE nodes have
        // different artifacts. Also, DUPLICATE and CONFLICT nodes sometimes don't have a match with an artifact in the
        // INCLUDED nodes
        new SanitizingProcessor().process(node);

        // Cascade Maven's optional flag to transitive dependencies of optional dependencies
        if (Boolean.TRUE.equals(gd.getCascadeOptional())) {
            new CascadeOptionalProcessor().process(node);
        }
    }

    public Graph getGraph(Node tree, MojoContext mc, GraphDescriptor gc) {
        GraphHelper gh = new GraphHelper();
        TreeHelper helper = new TreeHelper();
        List nodes = helper.getGraphNodes(tree);
        List edges = helper.getEdges(tree);
        if (mc.isVerbose()) {
            helper.show(nodes, edges);
        }
        String title = gh.getGraphTitle(gc);
        return gh.getGraph(title, gc.getDirection(), nodes, edges);
    }

    protected List getEdgeProcessors(GraphDescriptor gd) {
        Conflicts conflicts = gd.getConflicts();
        List processors = new ArrayList();
        Processor conflictsProcessor = new HideConflictsProcessor(gd);
        switch (gd.getLayout()) {
        case LINKED:
            processors.add(new LinkedEdgeProcessor());
            if (conflicts == Conflicts.LABEL) {
                logger.debug("labeling conflicts");
                processors.add(new ReduceClutterProcessor());
                processors.add(conflictsProcessor);
            } else if (conflicts == Conflicts.SHOW) {
                logger.debug("showing conflicts");
                processors.add(new ReduceClutterProcessor());
            } else if (conflicts == Conflicts.IGNORE) {
                logger.debug("ignoring conflicts");
                processors.add(conflictsProcessor);
                processors.add(new ReduceClutterProcessor());
            }
            return processors;
        case FLAT:
            processors.add(new FlatEdgeProcessor());
            if (conflicts == Conflicts.LABEL) {
                logger.debug("labeling conflicts");
                processors.add(conflictsProcessor);
            } else if (conflicts == Conflicts.SHOW) {
                logger.debug("showing conflicts");
            } else if (conflicts == Conflicts.IGNORE) {
                processors.add(conflictsProcessor);
                logger.debug("ignoring conflicts");
            }
            return processors;
        default:
            throw new IllegalStateException("Layout style " + gd.getLayout() + " is unknown");
        }
    }

    protected NodeFilter getShowFilter(GraphDescriptor gc) {
        TokenCollector collector = new MavenContextTokenCollector();
        Filter filter = filters.getIncludePatternFilter(gc.getShow(), collector);
        return new MavenContextFilterWrapper(filter);
    }

    protected NodeFilter getHideFilter(GraphDescriptor gc) {
        TokenCollector collector = new MavenContextTokenCollector();
        Filter filter = filters.getExcludePatternFilter(gc.getHide(), collector);
        return new MavenContextFilterWrapper(filter);
    }

    public NodeFilter getIncludeFilter(GraphDescriptor gc) {
        TokenCollector collector = new ArtifactIdTokenCollector();
        Filter filter = filters.getIncludePatternFilter(gc.getIncludes(), collector);
        ArtifactFilterWrapper artifactFilter = new ArtifactFilterWrapper(filter);
        List> filters = new ArrayList>();
        NodeFilter artifactQualifierFilter = getShowFilter(gc);
        filters.add(artifactQualifierFilter);
        filters.add(artifactFilter);
        return new NodeFilterChain(filters, MatchCondition.ALL, true);
    }

    public NodeFilter getExcludeFilter(GraphDescriptor gc) {
        TokenCollector collector = new ArtifactIdTokenCollector();
        Filter filter = filters.getExcludePatternFilter(gc.getExcludes(), collector);
        ArtifactFilterWrapper artifactFilter = new ArtifactFilterWrapper(filter);
        ReverseNodeFilter depthFilter = new ReverseNodeFilter(getDepthFilter(gc));
        NodeFilter artifactQualifierFilter = getHideFilter(gc);
        List> filters = new ArrayList>();
        filters.add(artifactQualifierFilter);
        filters.add(artifactFilter);
        filters.add(depthFilter);
        return new NodeFilterChain(filters, MatchCondition.ANY, false);
    }

    protected DepthFilter getDepthFilter(GraphDescriptor gc) {
        int maxDepth = gc.getTransitive() ? DepthFilter.INFINITE : 1;
        maxDepth = (gc.getDepth() != null && gc.getDepth() >= 0) ? gc.getDepth() : maxDepth;
        return new DepthFilter(maxDepth);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy