
net.kemitix.dependency.digraph.maven.plugin.AbstractDotFileFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of digraph-dependency-maven-plugin Show documentation
Show all versions of digraph-dependency-maven-plugin Show documentation
Generates a DOT Digraph of dependencies between packages within a project
The newest version!
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 Paul Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package net.kemitix.dependency.digraph.maven.plugin;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.val;
import net.kemitix.dependency.digraph.maven.plugin.digraph.Digraph;
import net.kemitix.dependency.digraph.maven.plugin.digraph.EdgeElement;
import net.kemitix.dependency.digraph.maven.plugin.digraph.EdgeEndpoint;
import net.kemitix.dependency.digraph.maven.plugin.digraph.ElementContainer;
import net.kemitix.dependency.digraph.maven.plugin.digraph.GraphElement;
import net.kemitix.dependency.digraph.maven.plugin.digraph.NodeElement;
import net.kemitix.dependency.digraph.maven.plugin.digraph.NodeProperties;
import net.kemitix.dependency.digraph.maven.plugin.digraph.PropertyElement;
import net.kemitix.dependency.digraph.maven.plugin.digraph.Subgraph;
import net.kemitix.node.Node;
import javax.annotation.concurrent.Immutable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Abstract base for {@link DotFileFormat} implementations.
*
* @author Paul Campbell ([email protected])
*/
@Immutable
public abstract class AbstractDotFileFormat implements DotFileFormat {
protected static final String CLOSE_BRACE = "]";
private static final String DOUBLE_QUOTE = "\"";
private static final String LINE = System.lineSeparator();
@Getter(AccessLevel.PROTECTED)
private final Node base;
private final NodePathGenerator nodePathGenerator;
private final NodePackageDataComparator nodePackageDataComparator;
private final Map, GraphElement> graphElements = new HashMap<>();
private final GraphFilter graphFilter;
/**
* Constructor.
*
* @param base The root node
* @param nodePathGenerator The Node Path Generator
* @param graphFilter The Exclusion factory
*/
AbstractDotFileFormat(
final Node base, final NodePathGenerator nodePathGenerator, final GraphFilter graphFilter
) {
this.base = base;
this.nodePathGenerator = nodePathGenerator;
this.graphFilter = graphFilter;
this.nodePackageDataComparator = new NodePackageDataComparator();
}
@Override
public final String renderReport() {
Digraph digraph = createDigraph();
getNodeInjector().injectNodes(digraph, base);
getUsageInjector().injectUsages(digraph, base);
return render(digraph);
}
@Override
public final String render(final Digraph digraph) {
return "digraph{" + LINE + renderElements(digraph.getElements()) + "}" + LINE;
}
@Override
public final String render(final NodeProperties nodeProperties) {
return "node[" + renderProperties(nodeProperties.getProperties()) + CLOSE_BRACE;
}
@Override
public final String render(final PropertyElement propertyElement) {
return propertyElement.getName() + "=" + quoted(propertyElement.getValue());
}
/**
* Returns the ID of the node in Cluster format.
*
* @param node the cluster node
*
* @return the cluster id
*/
final String getClusterId(final Node node) {
return getPath(node, "_");
}
/**
* Returns the node path for the node, using the delimiter.
*
* @param headNode The node to get the path for
* @param delimiter The delimiter to separate each path element
*
* @return the path to of the node
*/
private String getPath(
final Node headNode, final String delimiter
) {
return nodePathGenerator.getPath(headNode, getBase(), delimiter);
}
/**
* Creates a new Digraph.
*
* @return the Digraph
*/
private Digraph createDigraph() {
return new Digraph.Builder(this).build();
}
private GraphUsageInjector getUsageInjector() {
return (container, node) -> node.getChildren()
.stream()
.sorted(nodePackageDataComparator)
.forEach(injectUsagesByChildren(container));
}
private Consumer> injectUsagesByChildren(
final ElementContainer container
) {
return (Node childNode) -> {
childNode.findData()
.ifPresent(data -> data.getUses()
.stream()
.filter(n -> n.isDescendantOf(getBase()))
.sorted(nodePackageDataComparator)
.map(usedNode -> createEdgeElement(childNode, usedNode))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(container::add));
getUsageInjector().injectUsages(container, childNode);
};
}
/**
* Searches for the NodeElement for leaf nodes, or a Subgraph, for the node.
*
* @param node The node to search for
*
* @return the EdgeEndpoint for the node
*/
private EdgeEndpoint findEdgeEndpoint(final Node node) {
if (node.getChildren()
.isEmpty()) {
return findNodeElement(node);
}
return findSubgraph(node);
}
/**
* Creates a new NodeElement for the Node PackageData.
*
* @param node The node to create the NodeElement for
*
* @return the NodeElement for the node
*/
private NodeElement createNodeElement(
final Node node
) {
return new NodeElement(node, getNodeId(node), node.getData()
.getName(), this);
}
/**
* Creates a new EdgeElement linking the tail node to the head node.
*
* @param tail The node at the tail of the link
* @param head The node at the head of the link
*
* @return the EdgeElement linking tail to head
*/
private Optional createEdgeElement(
final Node tail, final Node head
) {
if (graphFilter.filterNodes(tail) || graphFilter.filterNodes(head)) {
return Optional.of(new EdgeElement(findEdgeEndpoint(tail), findEdgeEndpoint(head), this));
}
return Optional.empty();
}
/**
* Finds the NodeElement for the given node in the graphElements, adding a
* new one if one is not found.
*
* @param node The node to find the equivalent NodeElement for
*
* @return the NodeElement
*/
private NodeElement findNodeElement(final Node node) {
if (!graphElements.containsKey(node)) {
graphElements.put(node, createNodeElement(node));
}
return (NodeElement) graphElements.get(node);
}
/**
* Finds the Subgraph for the given node in the graphElements, adding a new
* one if one is not found.
*
* @param node The node to find the equivalent Subgraph for
*
* @return the Subgraph
*/
private Subgraph findSubgraph(final Node node) {
if (!graphElements.containsKey(node)) {
graphElements.put(node, createSubgraph(node));
}
return (Subgraph) graphElements.get(node);
}
private Subgraph createSubgraph(final Node node) {
return new Subgraph(node, getClusterId(node), node.getData()
.getName(), this);
}
private GraphNodeInjector getNodeInjector() {
return new GraphNodeInjector() {
@Override
public void injectNodes(final ElementContainer container, final Node node) {
val children = node.getChildren();
if (children.isEmpty()) {
container.add(findNodeElement(node));
} else {
val subgraph = findSubgraph(node);
children.stream()
.sorted(nodePackageDataComparator)
.forEach(c -> this.injectNodes(subgraph, c));
container.add(subgraph);
}
}
};
}
/**
* Returns the ID of the node in dot node format.
*
* @param node the node
*
* @return the dotnode id
*/
final String getNodeId(final Node node) {
return getPath(node, ".");
}
/**
* Renders the GraphElements.
*
* @param elements The GraphElements to be rendered
*
* @return the rendered GraphElements
*/
final String renderElements(final Collection elements) {
return elements.stream()
.map(GraphElement::render)
.collect(Collectors.joining(LINE));
}
/**
* Renders the PropertyElements.
*
* @param properties The PropertyElements to be rendered
*
* @return the rendered PropertyElements
*/
private String renderProperties(final Set properties) {
return properties.stream()
.map(GraphElement::render)
.collect(Collectors.joining(";" + LINE));
}
/**
* Wraps the string in double quotes.
*
* @param text The string to be surrounded
*
* @return The string wrapped in double quotes
*/
final String quoted(final String text) {
return DOUBLE_QUOTE + text + DOUBLE_QUOTE;
}
/**
* Functional Interface for rendering a node.
*/
@FunctionalInterface
interface GraphNodeInjector {
/**
* Adds the node and its child nodes to the container.
*
* @param container The container to add the node and its children to
* @param node The node to add to the container
*/
void injectNodes(
ElementContainer container, Node node
);
}
/**
* Functional Interface for rendering a usage.
*/
@FunctionalInterface
interface GraphUsageInjector {
/**
* Adds uses of packages be the node's children to the container.
*
* @param container The container to add the usages to
* @param node The node to scan to find uses
*/
void injectUsages(
ElementContainer container, Node node
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy