Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2014 - 2017 the original author or authors.
*
* 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 com.github.ferstl.depgraph;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils.StringStreamConsumer;
import org.codehaus.plexus.util.cli.Commandline;
import com.github.ferstl.depgraph.dependency.DependencyGraphException;
import com.github.ferstl.depgraph.dependency.GraphFactory;
import com.github.ferstl.depgraph.dependency.GraphStyleConfigurer;
import com.github.ferstl.depgraph.dependency.dot.DotGraphStyleConfigurer;
import com.github.ferstl.depgraph.dependency.dot.style.StyleConfiguration;
import com.github.ferstl.depgraph.dependency.dot.style.resource.BuiltInStyleResource;
import com.github.ferstl.depgraph.dependency.dot.style.resource.ClasspathStyleResource;
import com.github.ferstl.depgraph.dependency.dot.style.resource.FileSystemStyleResource;
import com.github.ferstl.depgraph.dependency.dot.style.resource.StyleResource;
import com.github.ferstl.depgraph.dependency.gml.GmlGraphStyleConfigurer;
import com.github.ferstl.depgraph.dependency.json.JsonGraphStyleConfigurer;
import com.github.ferstl.depgraph.dependency.puml.PumlGraphStyleConfigurer;
import com.github.ferstl.depgraph.dependency.text.TextGraphStyleConfigurer;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import static com.github.ferstl.depgraph.GraphFormat.JSON;
/**
* Abstract mojo to create all possible kinds of graphs. Graphs are created with instances of the
* {@link GraphFactory} interface. This class defines an abstract method to create such factories. In case Graphviz is
* installed on the system where this plugin is executed, it is also possible to run the dot program and create images
* out of the generated dot files. Besides that, this class allows the configuration of several basic mojo parameters,
* such as includes, excludes, etc.
*/
abstract class AbstractGraphMojo extends AbstractMojo {
private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("\r?\n");
private static final String OUTPUT_FILE_NAME = "dependency-graph";
/**
* The scope of the artifacts that should be included in the graph. An empty string indicates all scopes (default).
* The scopes being interpreted are the scopes as Maven sees them, not as specified in the pom. In summary:
*
*
{@code compile}: Shows compile, provided and system dependencies
*
{@code provided}: Shows provided dependencies
*
{@code runtime}: Shows compile and runtime dependencies
*
{@code system}: Shows system dependencies
*
{@code test} (default): Shows all dependencies
*
*
* @since 1.0.0
*/
@Parameter(property = "scope")
private String scope;
/**
* List of artifacts to be included in the form of {@code groupId:artifactId:type:classifier}.
*
* @since 1.0.0
*/
@Parameter(property = "includes", defaultValue = "")
private List includes;
/**
* List of artifacts to be excluded in the form of {@code groupId:artifactId:type:classifier}.
*
* @since 1.0.0
*/
@Parameter(property = "excludes", defaultValue = "")
private List excludes;
/**
* List of artifacts in the form of {@code groupId:artifactId:type:classifier} to be included if they are
* transitive.
*
* @since 3.0.0
*/
@Parameter(property = "transitiveIncludes", defaultValue = "")
private List transitiveIncludes;
/**
* List of artifacts in the form of {@code groupId:artifactId:type:classifier} to be excluded if they are
* transitive.
*
* @since 3.0.0
*/
@Parameter(property = "transitiveExcludes", defaultValue = "")
private List transitiveExcludes;
/**
* List of artifacts, in the form of {@code groupId:artifactId:type:classifier}, to restrict the dependency graph
* only to artifacts that depend on them.
*
* @since 1.0.4
*/
@Parameter(property = "targetIncludes", defaultValue = "")
private List targetIncludes;
/**
* Format of the graph, either "dot" (default), "gml", "puml", "json" or "text".
*
* @since 2.1.0
*/
@Parameter(property = "graphFormat", defaultValue = "dot")
private String graphFormat;
/**
* If set to {@code true} (which is the default) and the graph format is 'json', the graph will show
* any information that is possible.
* The idea behind this option is, that the consumer of the JSON data, for example a Javascript library, will do its
* own filtering of the data.
*
* @since 3.0.0
*/
@Parameter(property = "showAllAttributesForJson", defaultValue = "true")
private boolean showAllAttributesForJson;
/**
* The path to the generated output file. A file extension matching the configured {@code graphFormat} will be
* added if not specified.
* ATTENTION: THIS OPTION WILL BE REMOVED IN VERSION 3.1.0!
*
* @since 1.0.0
* @deprecated Deprecated since 2.2.0. Use {@code outputDirectory} and {@code outputFileName} instead.
*/
@Parameter(property = "outputFile")
@Deprecated
private String outputFile;
/**
* Output directory to write the dependency graph to.
*
* @since 2.2.0
*/
@Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}")
private File outputDirectory;
/**
* The name of the dependency graph file. A file extension matching the configured {@code graphFormat} will be
* added if not specified.
*
* @since 2.2.0
*/
@Parameter(property = "outputFileName", defaultValue = OUTPUT_FILE_NAME)
private String outputFileName;
/**
* Indicates whether the project's artifact ID should be used as file name for the generated graph files.
*
*
This flag does not have an effect when the (deprecated) {@code outputFile} parameter is used.
*
When set to {@code true}, the content of the {@code outputFileName} parameter is ignored.
*
*
* @since 2.2.0
*/
@Parameter(property = "useArtifactIdInFileName", defaultValue = "false")
private boolean useArtifactIdInFileName;
/**
* Only relevant when {@code graphFormat=dot}: If set to {@code true} and Graphviz is installed on the system where
* this plugin is executed, the dot file will be converted to a graph image using Graphviz' dot executable.
*
* @see #imageFormat
* @see #dotExecutable
* @since 1.0.0
*/
@Parameter(property = "createImage", defaultValue = "false")
private boolean createImage;
/**
* Only relevant when {@code graphFormat=dot}: The format for the graph image when {@link #createImage} is set to
* {@code true}.
*
* @since 1.0.0
*/
@Parameter(property = "imageFormat", defaultValue = "png")
private String imageFormat;
/**
* Only relevant when {@code graphFormat=dot}: Path to the dot executable. Use this option in case
* {@link #createImage} is set to {@code true} and the dot executable is not on the system {@code PATH}.
*
* @since 1.0.0
*/
@Parameter(property = "dotExecutable")
private File dotExecutable;
/**
* Only relevant when {@code graphFormat=dot}: Path to a custom style configuration in JSON format.
*
* @since 2.0.0
*/
@Parameter(property = "customStyleConfiguration", defaultValue = "")
private String customStyleConfiguration;
/**
* Only relevant when {@code graphFormat=dot}: If set to {@code true} the effective style configuration used to
* create this graph will be printed on the console.
*
* @since 2.0.0
*/
@Parameter(property = "printStyleConfiguration", defaultValue = "false")
private boolean printStyleConfiguration;
/**
* The project's artifact ID.
*/
@Parameter(defaultValue = "${project.artifactId}", readonly = true)
private String artifactId;
/**
* Local maven repository required by the {@link DependencyTreeBuilder}.
*/
@Parameter(defaultValue = "${localRepository}", readonly = true)
ArtifactRepository localRepository;
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Component(hint = "default")
DependencyGraphBuilder dependencyGraphBuilder;
@Component
DependencyTreeBuilder dependencyTreeBuilder;
@Override
public final void execute() throws MojoExecutionException, MojoFailureException {
GraphFormat graphFormat = GraphFormat.forName(this.graphFormat);
ArtifactFilter globalFilter = createGlobalArtifactFilter();
ArtifactFilter transitiveIncludeExcludeFilter = createTransitiveIncludeExcludeFilter();
ArtifactFilter targetFilter = createTargetArtifactFilter();
GraphStyleConfigurer graphStyleConfigurer = createGraphStyleConfigurer(graphFormat);
Path graphFilePath = createGraphFilePath(graphFormat);
try {
GraphFactory graphFactory = createGraphFactory(globalFilter, transitiveIncludeExcludeFilter, targetFilter, graphStyleConfigurer);
String dependencyGraph = graphFactory.createGraph(this.project);
writeGraphFile(dependencyGraph, graphFilePath);
if (graphFormat == GraphFormat.DOT && this.createImage) {
createDotGraphImage(graphFilePath);
} else if (graphFormat == GraphFormat.TEXT) {
getLog().info("Dependency graph:\n" + dependencyGraph);
}
} catch (DependencyGraphException e) {
throw new MojoExecutionException("Unable to create dependency graph.", e.getCause());
} catch (IOException e) {
throw new MojoExecutionException("Unable to write graph file.", e);
}
}
protected abstract GraphFactory createGraphFactory(ArtifactFilter globalFilter, ArtifactFilter transitiveIncludeExcludeFilter, ArtifactFilter targetFilter, GraphStyleConfigurer graphStyleConfigurer);
/**
* Override this method to configure additional style resources. It is recommendet to call
* {@code super.getAdditionalStyleResources()} and add them to the set.
*
* @return A set of additional built-in style resources to use.
*/
protected Set getAdditionalStyleResources() {
// We need to preserve the order of style configurations
return new LinkedHashSet<>();
}
/**
* Indicates to subclasses that everything possible should be shown in the graph, no matter what was configured
* for the specific mojo.
*
* @return {@code true} if the full graph should be shown, {@code false} else.
*/
protected boolean showFullGraph() {
return GraphFormat.forName(this.graphFormat) == JSON && this.showAllAttributesForJson;
}
private ArtifactFilter createGlobalArtifactFilter() {
AndArtifactFilter filter = new AndArtifactFilter();
if (this.scope != null) {
filter.add(new ScopeArtifactFilter(this.scope));
}
if (!this.includes.isEmpty()) {
filter.add(new StrictPatternIncludesArtifactFilter(this.includes));
}
if (!this.excludes.isEmpty()) {
filter.add(new StrictPatternExcludesArtifactFilter(this.excludes));
}
return filter;
}
private ArtifactFilter createTransitiveIncludeExcludeFilter() {
AndArtifactFilter filter = new AndArtifactFilter();
if (!this.transitiveIncludes.isEmpty()) {
filter.add(new StrictPatternIncludesArtifactFilter(this.transitiveIncludes));
}
if (!this.transitiveExcludes.isEmpty()) {
filter.add(new StrictPatternExcludesArtifactFilter(this.transitiveExcludes));
}
return filter;
}
private ArtifactFilter createTargetArtifactFilter() {
AndArtifactFilter filter = new AndArtifactFilter();
if (!this.targetIncludes.isEmpty()) {
filter.add(new StrictPatternIncludesArtifactFilter(this.targetIncludes));
}
return filter;
}
private GraphStyleConfigurer createGraphStyleConfigurer(GraphFormat graphFormat) throws MojoFailureException {
switch (graphFormat) {
case DOT:
StyleConfiguration styleConfiguration = loadStyleConfiguration();
return new DotGraphStyleConfigurer(styleConfiguration);
case GML:
return new GmlGraphStyleConfigurer();
case PUML:
return new PumlGraphStyleConfigurer();
case JSON:
return new JsonGraphStyleConfigurer();
case TEXT:
return new TextGraphStyleConfigurer();
default:
throw new IllegalArgumentException("Unsupported output format: " + graphFormat);
}
}
private StyleConfiguration loadStyleConfiguration() throws MojoFailureException {
// default style resources
ClasspathStyleResource defaultStyleResource = BuiltInStyleResource.DEFAULT_STYLE.createStyleResource(getClass().getClassLoader());
// additional style resources from the mojo
Set styleResources = new LinkedHashSet<>();
for (BuiltInStyleResource additionalResource : getAdditionalStyleResources()) {
styleResources.add(additionalResource.createStyleResource(getClass().getClassLoader()));
}
// custom style resource
if (StringUtils.isNotBlank(this.customStyleConfiguration)) {
StyleResource customStyleResource = getCustomStyleResource();
getLog().info("Using custom style configuration " + customStyleResource);
styleResources.add(customStyleResource);
}
// load and print
StyleConfiguration styleConfiguration = StyleConfiguration.load(defaultStyleResource, styleResources.toArray(new StyleResource[0]));
if (this.printStyleConfiguration) {
getLog().info("Using effective style configuration:\n" + styleConfiguration.toJson());
}
return styleConfiguration;
}
private StyleResource getCustomStyleResource() throws MojoFailureException {
StyleResource customStyleResource;
if (StringUtils.startsWith(this.customStyleConfiguration, "classpath:")) {
String resourceName = StringUtils.substring(this.customStyleConfiguration, 10, this.customStyleConfiguration.length());
customStyleResource = new ClasspathStyleResource(resourceName, getClass().getClassLoader());
} else {
customStyleResource = new FileSystemStyleResource(Paths.get(this.customStyleConfiguration));
}
if (!customStyleResource.exists()) {
throw new MojoFailureException("Custom configuration '" + this.customStyleConfiguration + "' does not exist.");
}
return customStyleResource;
}
private Path createGraphFilePath(GraphFormat graphFormat) {
Path outputFilePath;
String fileName;
if (StringUtils.isNotBlank(this.outputFile)) {
getLog().warn("************************************************************************");
getLog().warn("* WARNING: *");
getLog().warn("* The 'outputFile' parameter has been deprecated and will be removed *");
getLog().warn("* in Version 3.1.0! Use 'outputDirectory' and 'outputFileName' instead.*");
getLog().warn("************************************************************************");
outputFilePath = Paths.get(this.outputFile);
fileName = outputFilePath.getFileName().toString();
fileName = addFileExtensionIfNeeded(graphFormat, fileName);
} else {
fileName = this.useArtifactIdInFileName ? this.artifactId : this.outputFileName;
fileName = addFileExtensionIfNeeded(graphFormat, fileName);
outputFilePath = this.outputDirectory.toPath().resolve(fileName);
}
outputFilePath = outputFilePath.resolveSibling(fileName);
return outputFilePath;
}
private String addFileExtensionIfNeeded(GraphFormat graphFormat, String fileName) {
String fileExtension = graphFormat.getFileExtension();
if (!fileName.endsWith(fileExtension)) {
fileName += fileExtension;
}
return fileName;
}
private void writeGraphFile(String graph, Path graphFilePath) throws IOException {
Path parent = graphFilePath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
try (Writer writer = Files.newBufferedWriter(graphFilePath, StandardCharsets.UTF_8)) {
writer.write(graph);
}
}
private void createDotGraphImage(Path graphFilePath) throws IOException {
String graphFileName = createDotImageFileName(graphFilePath);
Path graphFile = graphFilePath.resolveSibling(graphFileName);
String dotExecutable = determineDotExecutable();
String[] arguments = new String[]{
"-T", this.imageFormat,
"-o", graphFile.toAbsolutePath().toString(),
graphFilePath.toAbsolutePath().toString()};
Commandline cmd = new Commandline();
cmd.setExecutable(dotExecutable);
cmd.addArguments(arguments);
getLog().info("Running Graphviz: " + dotExecutable + " " + Joiner.on(" ").join(arguments));
StringStreamConsumer systemOut = new StringStreamConsumer();
StringStreamConsumer systemErr = new StringStreamConsumer();
int exitCode;
try {
exitCode = CommandLineUtils.executeCommandLine(cmd, systemOut, systemErr);
} catch (CommandLineException e) {
throw new IOException("Unable to execute Graphviz", e);
}
Splitter lineSplitter = Splitter.on(LINE_SEPARATOR_PATTERN).omitEmptyStrings().trimResults();
Iterable output = Iterables.concat(
lineSplitter.split(systemOut.getOutput()),
lineSplitter.split(systemErr.getOutput()));
for (String line : output) {
getLog().info(" dot> " + line);
}
if (exitCode != 0) {
throw new IOException("Graphviz terminated abnormally. Exit code: " + exitCode);
}
getLog().info("Graph image created on " + graphFile.toAbsolutePath());
}
private String createDotImageFileName(Path graphFilePath) {
String graphFileName = graphFilePath.getFileName().toString();
if (graphFileName.endsWith(GraphFormat.DOT.getFileExtension())) {
graphFileName = graphFileName.substring(0, graphFileName.lastIndexOf(".")) + "." + this.imageFormat;
} else {
graphFileName = graphFileName + this.imageFormat;
}
return graphFileName;
}
private String determineDotExecutable() throws IOException {
if (this.dotExecutable == null) {
return "dot";
}
Path dotExecutablePath = this.dotExecutable.toPath();
if (!Files.exists(dotExecutablePath)) {
throw new NoSuchFileException("The dot executable '" + this.dotExecutable + "' does not exist.");
} else if (Files.isDirectory(dotExecutablePath) || !Files.isExecutable(dotExecutablePath)) {
throw new IOException("The dot executable '" + this.dotExecutable + "' is not a file or cannot be executed.");
}
return dotExecutablePath.toAbsolutePath().toString();
}
}