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

Java tutorial

Introduction

Here is the source code for com.github.fritaly.graphml4j.samples.GradleDependencies.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.Map;
import java.util.Stack;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;

import com.github.fritaly.graphml4j.GraphMLWriter;
import com.github.fritaly.graphml4j.NodeStyle;

/**
 * <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 GradleDependencies {

    /**
     * Computes and returns a label from the given value.
     *
     * @param value
     *            a string identifying a dependency (group, artifact, version)
     *            generated by the Gradle dependencies task. Can't be null.
     * @return a string corresponding to a shorter label to uniquely identify a
     *         dependency.
     */
    private static String computeLabel(String value) {
        // Examples of input values:
        // "org.springframework:spring-core:3.2.0.RELEASE (*)" => "spring-core:3.2.0.RELEASE"
        // "com.acme:acme-logging:1.16.0 -> 1.16.3 (*)" => "acme-logging:1.16.3"
        // "junit:junit:3.8.1 -> 4.8.1" => "junit:4.8.1"
        // "sun-jaxb:jaxb-impl:2.2" => "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;
        }

        // Ex: "sun-jaxb:jaxb-impl:2.2" => "jaxb-impl:2.2"
        value = StringUtils.substringAfter(value, ":");

        return StringUtils.replace(value, ":", "\n");
    }

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

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

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

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

        try {
            fileWriter = new FileWriter(file);
            graphWriter = new GraphMLWriter(fileWriter);

            // Customize the rendering of nodes
            final NodeStyle nodeStyle = graphWriter.getNodeStyle();
            nodeStyle.setWidth(250.0f);

            graphWriter.setNodeStyle(nodeStyle);

            // 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(GradleDependencies.class.getResourceAsStream("gradle-dependencies.txt"));
            lineReader = new LineNumberReader(reader);

            String line = null;

            // Stack containing the node identifiers per depth inside the
            // dependency graph (the topmost dependency is the first one in the
            // stack)
            final Stack<String> parentIds = new Stack<String>();

            // Open the graph
            graphWriter.graph();

            // Map storing the node identifiers per label
            final Map<String, String> nodeIdsByLabel = new TreeMap<String, String>();

            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 <= parentIds.size()) {
                    parentIds.pop();
                }

                // Compute a nice label from the dependency (group, artifact,
                // version) tuple
                final String label = computeLabel(line);

                // Has this dependency already been added to the graph ?
                if (!nodeIdsByLabel.containsKey(label)) {
                    // No, add the node
                    nodeIdsByLabel.put(label, graphWriter.node(label));
                }

                final String nodeId = nodeIdsByLabel.get(label);

                parentIds.push(nodeId);

                if (parentIds.size() > 1) {
                    // Generate an edge between the current node and its parent
                    graphWriter.edge(parentIds.get(parentIds.size() - 2), nodeId);
                }
            }

            // Close the graph
            graphWriter.closeGraph();

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