com.github.fritaly.graphml4j.samples.GradleDependenciesWithGroupsAndBuffering.java Source code

Java tutorial

Introduction

Here is the source code for com.github.fritaly.graphml4j.samples.GradleDependenciesWithGroupsAndBuffering.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.fritaly.graphml4j.samples;

import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.Stack;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.HashCodeBuilder;

import com.github.fritaly.graphml4j.EdgeStyle;
import com.github.fritaly.graphml4j.GroupStyles;
import com.github.fritaly.graphml4j.NodeStyle;
import com.github.fritaly.graphml4j.Renderer;
import com.github.fritaly.graphml4j.datastructure.Edge;
import com.github.fritaly.graphml4j.datastructure.Graph;
import com.github.fritaly.graphml4j.datastructure.Node;

/**
 * <p>
 * This sample demonstrates how to generate a dependency graph (to be visualized
 * in yEd) from the output of a "gradle dependencies" command. The Gradle output
 * has been saved as a resource file (see resource "gradle-dependencies.txt").
 * </p>
 * <p>
 * Instructions:
 * <ul>
 * <li>Download and install yEd (if necessary)</li>
 * <li>Execute this sample and generate a GraphML file</li>
 * <li>Open the generated file in yEd</li>
 * <li>In yEd, render the graph with the "Hierarchical" layout</li>
 * </ul>
 * </p>
 *
 * @author francois_ritaly
 */
public class GradleDependenciesWithGroupsAndBuffering {

    private static class Artifact {

        private final String group, artifact, version;

        public Artifact(String value) {
            // Ex: "junit:junit:4.8.1"
            this.group = StringUtils.substringBefore(value, ":");
            this.artifact = StringUtils.substringBetween(value, ":");
            this.version = StringUtils.substringAfterLast(value, ":");
        }

        String getLabel() {
            return String.format("%s\n%s", artifact, version);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == this) {
                return true;
            }
            if (obj instanceof Artifact) {
                final Artifact other = (Artifact) obj;

                return StringUtils.equals(this.group, other.group)
                        && StringUtils.equals(this.artifact, other.artifact)
                        && StringUtils.equals(this.version, other.version);
            }

            return false;
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(7, 23).append(this.group).append(this.artifact).append(this.version)
                    .toHashCode();
        }

        @Override
        public String toString() {
            return String.format("%s:%s:%s", group, artifact, version);
        }
    }

    private static Artifact createArtifact(String value) {
        // Examples of input values:
        // "org.springframework:spring-core:3.2.0.RELEASE (*)"
        // "com.acme:acme-logging:1.16.0 -> 1.16.3 (*)"
        // "junit:junit:3.8.1 -> 4.8.1"
        // "sun-jaxb:jaxb-impl:2.2"

        if (value.endsWith(" (*)")) {
            // Ex: "org.springframework:spring-core:3.2.0.RELEASE (*)" => "org.springframework:spring-core:3.2.0.RELEASE"
            value = value.replace(" (*)", "");
        }
        if (value.contains(" -> ")) {
            // Ex: "junit:junit:3.8.1 -> 4.8.1" => "junit:junit:4.8.1"
            final String version = StringUtils.substringAfter(value, " -> ");

            value = StringUtils.substringBeforeLast(value, ":") + ":" + version;
        }

        return new Artifact(value);
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.out.println(String.format("%s <output-file>",
                    GradleDependenciesWithGroupsAndBuffering.class.getSimpleName()));
            System.exit(1);
        }

        final File file = new File(args[0]);

        System.out.println("Writing GraphML file to " + file.getAbsolutePath() + " ...");

        FileWriter fileWriter = null;
        Reader reader = null;
        LineNumberReader lineReader = null;

        try {
            fileWriter = new FileWriter(file);

            final com.github.fritaly.graphml4j.datastructure.Graph graph = new Graph();

            // The dependency graph has been generated by Gradle with the
            // command "gradle dependencies". The output of this command has
            // been saved to a text file which will be parsed to rebuild the
            // dependency graph
            reader = new InputStreamReader(
                    GradleDependenciesWithGroupsAndBuffering.class.getResourceAsStream("gradle-dependencies.txt"));
            lineReader = new LineNumberReader(reader);

            String line = null;

            // Stack containing the nodes per depth inside the dependency graph
            // (the topmost dependency is the first one in the stack)
            final Stack<Node> parentNodes = new Stack<Node>();

            while ((line = lineReader.readLine()) != null) {
                // Determine the depth of the current dependency inside the
                // graph. The depth can be inferred from the indentation used by
                // Gradle. Each level of depth adds 5 more characters of
                // indentation
                final int initialLength = line.length();

                // Remove the strings used by Gradle to indent dependencies
                line = StringUtils.replace(line, "+--- ", "");
                line = StringUtils.replace(line, "|    ", "");
                line = StringUtils.replace(line, "\\--- ", "");
                line = StringUtils.replace(line, "     ", "");

                // The depth can easily be inferred now
                final int depth = (initialLength - line.length()) / 5;

                // Remove unnecessary node ids
                while (depth <= parentNodes.size()) {
                    parentNodes.pop();
                }

                final Artifact artifact = createArtifact(line);

                Node node = graph.getNodeByData(artifact);

                // Has this dependency already been added to the graph ?
                if (node == null) {
                    // No, add the node
                    node = graph.addNode(artifact);
                }

                parentNodes.push(node);

                if (parentNodes.size() > 1) {
                    // Generate an edge between the current node and its parent
                    graph.addEdge("Depends on", parentNodes.get(parentNodes.size() - 2), node);
                }
            }

            // Create the groups after creating the nodes & edges
            for (Node node : graph.getNodes()) {
                final Artifact artifact = (Artifact) node.getData();

                final String groupId = artifact.group;

                Node groupNode = graph.getNodeByData(groupId);

                if (groupNode == null) {
                    groupNode = graph.addNode(groupId);
                }

                // add the node to the group
                node.setParent(groupNode);
            }

            graph.toGraphML(fileWriter, new Renderer() {

                @Override
                public String getNodeLabel(Node node) {
                    return node.isGroup() ? node.getData().toString() : ((Artifact) node.getData()).getLabel();
                }

                @Override
                public boolean isGroupOpen(Node node) {
                    return true;
                }

                @Override
                public NodeStyle getNodeStyle(Node node) {
                    // Customize the rendering of nodes
                    final NodeStyle nodeStyle = new NodeStyle();
                    nodeStyle.setWidth(250.0f);

                    return nodeStyle;
                }

                @Override
                public GroupStyles getGroupStyles(Node node) {
                    return new GroupStyles();
                }

                @Override
                public EdgeStyle getEdgeStyle(Edge edge) {
                    return new EdgeStyle();
                }
            });

            System.out.println("Done");
        } finally {
            // Calling GraphMLWriter.close() is necessary to dispose the underlying resources
            fileWriter.close();
            lineReader.close();
            reader.close();
        }
    }
}