de.uni_koblenz.jgralab.utilities.tg2dot.Tg2Dot.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_koblenz.jgralab.utilities.tg2dot.Tg2Dot.java

Source

/*
 * JGraLab - The Java Graph Laboratory
 *
 * Copyright (C) 2006-2014 Institute for Software Technology
 *                         University of Koblenz-Landau, Germany
 *                         ist@uni-koblenz.de
 *
 * For bug reports, documentation and further information, visit
 *
 *                         https://github.com/jgralab/jgralab
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses>.
 *
 * Additional permission under GNU GPL version 3 section 7
 *
 * If you modify this Program, or any covered work, by linking or combining
 * it with Eclipse (or a modified version of that program or an Eclipse
 * plugin), containing parts covered by the terms of the Eclipse Public
 * License (EPL), the licensors of this Program grant you additional
 * permission to convey the resulting work.  Corresponding Source for a
 * non-source form of such a combination shall include the source code for
 * the parts of JGraLab used as well as that of the covered work.
 */
package de.uni_koblenz.jgralab.utilities.tg2dot;

import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.ABBREVIATE_EDGE_ATTRIBUTE_NAMES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.PRINT_DOMAIN_NAMES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.PRINT_EDGE_ATTRIBUTES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.PRINT_ELEMENT_SEQUENCE_INDICES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.PRINT_INCIDENCE_INDICES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.PRINT_ROLENAMES;
import static de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade.SHORTEN_STRINGS;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;

import de.uni_koblenz.ist.utilities.option_handler.OptionHandler;
import de.uni_koblenz.jgralab.AttributedElement;
import de.uni_koblenz.jgralab.Edge;
import de.uni_koblenz.jgralab.Graph;
import de.uni_koblenz.jgralab.JGraLab;
import de.uni_koblenz.jgralab.Vertex;
import de.uni_koblenz.jgralab.exception.GraphIOException;
import de.uni_koblenz.jgralab.graphmarker.AbstractBooleanGraphMarker;
import de.uni_koblenz.jgralab.greql.evaluator.GreqlEvaluatorImpl;
import de.uni_koblenz.jgralab.utilities.tg2dot.dot.DotWriter;
import de.uni_koblenz.jgralab.utilities.tg2dot.dot.GraphType;
import de.uni_koblenz.jgralab.utilities.tg2dot.dot.GraphVizLayouter;
import de.uni_koblenz.jgralab.utilities.tg2dot.dot.GraphVizOutputFormat;
import de.uni_koblenz.jgralab.utilities.tg2dot.dot.GraphVizProgram;
import de.uni_koblenz.jgralab.utilities.tg2dot.graph_layout.GraphLayout;
import de.uni_koblenz.jgralab.utilities.tg2dot.graph_layout.GraphLayoutFactory;
import de.uni_koblenz.jgralab.utilities.tg2dot.graph_layout.definition.Definition;
import de.uni_koblenz.jgralab.utilities.tg2dot.graph_layout.definition.ElementDefinition;
import de.uni_koblenz.jgralab.utilities.tg2dot.graph_layout.definition.TypeDefinition;
import de.uni_koblenz.jgralab.utilities.tg2dot.greql.GreqlEvaluatorFacade;
import de.uni_koblenz.jgralab.utilities.tg2whatever.Tg2Whatever;

/**
 * Tg2Dot2 takes a graph layout and a JGraLab graph and transforms the graph
 * into a DOT-graph of GraphViz.
 * 
 * @author ist@uni-koblenz.de
 * 
 */
public class Tg2Dot extends Tg2Whatever {

    /**
     * Indicates to abbreviate all edge attribute names.
     */
    private boolean abbreviateEdgeAttributeNames = false;

    /**
     * Indicates to print the incidence indices.
     */
    private boolean printIncidenceIndices = false;

    /**
     * Indicates to print the element sequence index number.
     */
    private boolean printElementSequenceIndices = false;

    /**
     * Name of the graph layout file.
     */
    private String graphLayoutFilename;

    /**
     * Holds the loaded graph layout.
     */
    private GraphLayout layout;

    /**
     * Provides a GreqlEvaluator with convenient functionality.
     */
    private GreqlEvaluatorFacade evaluator;

    /**
     * Provides a dot file writer with a simple interface.
     */
    private DotWriter writer;

    /**
     * Specifies the type of file, which will be passed to dot in order to
     * generate an output. Defaults to DOT.
     */
    private GraphVizLayouter graphVizLayouter;

    private GraphVizOutputFormat graphVizOutputFormat;

    private Level jGraLabLogLevel;

    /**
     * @param args
     * @throws IOException
     * @throws GraphIOException
     */
    public static void main(String[] args) {
        Tg2Dot converter = new Tg2Dot();
        converter.getOptions(args);
        System.out.print("Starting processing of graph...");
        try {
            converter.convert();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Finished Processing.");
    }

    @Override
    public void convert() throws IOException {
        if (graphVizOutputFormat == null) {
            super.convert();
            return;
        }
        // write dot output into pipe to GraphViz layouter and store result as
        // file
        GraphVizProgram prog = new GraphVizProgram().layouter(graphVizLayouter).outputFormat(graphVizOutputFormat);
        pipeToGraphViz(prog);
    }

    public static Tg2Dot createConverterAndSetAttributes(Graph graph, boolean reversedEdges) {

        Tg2Dot converter = new Tg2Dot();
        converter.setGraph(graph);
        converter.setReversedEdges(reversedEdges);
        converter.setPrintEdgeAttributes(true);
        return converter;
    }

    public static void convertGraph(Graph graph, String outputFileName) throws IOException {
        convertGraph(graph, outputFileName, false, GraphVizOutputFormat.XDOT);
    }

    public static void convertGraph(Graph graph, String outputFileName, boolean reversedEdges) throws IOException {
        convertGraph(graph, outputFileName, reversedEdges, GraphVizOutputFormat.XDOT);
    }

    public static void convertGraph(Graph graph, String outputFileName, GraphVizOutputFormat format)
            throws IOException {
        convertGraph(graph, outputFileName, false, format);
    }

    public static void convertGraph(Graph graph, String outputFileName, boolean reversedEdges,
            GraphVizOutputFormat format) throws IOException {

        Tg2Dot converter = createConverterAndSetAttributes(graph, reversedEdges);
        converter.setOutputFile(outputFileName);
        converter.setGraphVizOutputFormat(format);
        converter.convert();
    }

    public static void convertGraph(AbstractBooleanGraphMarker marker, String outputFileName) throws IOException {
        convertGraph(marker, outputFileName, false);
    }

    public static void convertGraph(AbstractBooleanGraphMarker marker, String outputFileName, boolean reversedEdges)
            throws IOException {
        convertGraph(marker, outputFileName, GraphVizOutputFormat.PDF, reversedEdges);
    }

    public static void convertGraph(AbstractBooleanGraphMarker marker, String outputFileName,
            GraphVizOutputFormat format, boolean reversedEdges) throws IOException {
        Tg2Dot converter = createConverterAndSetAttributes(marker.getGraph(), reversedEdges);
        converter.setOutputFile(outputFileName);
        converter.setGraphMarker(marker);
        converter.convert();
    }

    public void pipeToGraphViz(GraphVizProgram prog) throws IOException {
        String executionString = String.format("%s%s -T%s -o%s", prog.path, prog.layouter, prog.outputFormat,
                outputName);
        final Process process = Runtime.getRuntime().exec(executionString);
        new Thread() {
            @Override
            public void run() {
                PrintStream ps = new PrintStream(process.getOutputStream());
                convert(ps);
                ps.flush();
                ps.close();
            };
        }.start();
        try {
            int retVal = process.waitFor();
            if (retVal != 0) {
                throw new RuntimeException("GraphViz process failed! Error code = " + retVal);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public InputStream convertToGraphVizStream(GraphVizProgram prog) throws IOException {
        String executionString = String.format("%s%s -T%s", prog.path, prog.layouter, prog.outputFormat);
        final Process process = Runtime.getRuntime().exec(executionString);
        InputStream inputStream = new BufferedInputStream(process.getInputStream());
        new Thread() {
            @Override
            public void run() {
                PrintStream ps = new PrintStream(process.getOutputStream());
                convert(ps);
                ps.flush();
                ps.close();
            };
        }.start();
        return inputStream;
    }

    public ImageIcon convertToGraphVizImageIcon(GraphVizProgram prog) throws IOException {
        BufferedInputStream imageStream = new BufferedInputStream(convertToGraphVizStream(prog));
        return new ImageIcon(ImageIO.read(imageStream));
    }

    /**
     * Initializes all data structures.
     */
    public Tg2Dot() {
    }

    @Override
    protected void getAdditionalOptions(CommandLine comLine) {
        initializeGraphAndSchema();

        graphLayoutFilename = comLine.getOptionValue('p');

        printIncidenceIndices = comLine.hasOption('i');
        printElementSequenceIndices = comLine.hasOption('m');

        String gvLayouter = comLine.getOptionValue('l');

        if (gvLayouter != null) {
            try {
                graphVizLayouter = GraphVizLayouter.valueOf(gvLayouter);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("Unknown layouter '" + gvLayouter + "'. Possible values are "
                        + GraphVizLayouter.describeValues());
            }
        }

        String gvOutputFormat = comLine.getOptionValue('t');
        if (gvOutputFormat != null) {
            try {
                graphVizOutputFormat = GraphVizOutputFormat.valueOf(gvOutputFormat.toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("Unknown output format  '" + gvOutputFormat + "'. Possible values are "
                        + GraphVizOutputFormat.describeValues());
            }
        }
    }

    @Override
    protected void addAdditionalOptions(OptionHandler optionHandler) {

        Option pListLayout = new Option("p", "pListLayout", true,
                "(optional): declares a PList-layout file, which should be used to lay out the given graph.");
        pListLayout.setRequired(false);
        optionHandler.addOption(pListLayout);

        Option incidenceIndices = new Option("i", "incidenceIndices", false,
                "(optional): prints the incidence index to every edge.");
        incidenceIndices.setRequired(false);
        optionHandler.addOption(incidenceIndices);

        Option elementSequenceIndices = new Option("m", "elementSequenceIndices", false,
                "(optional): prints the element sequence index of every vertex and edge.");
        elementSequenceIndices.setRequired(false);
        optionHandler.addOption(elementSequenceIndices);

        Option gvFormat = new Option("t", "graphVizFormat", true,
                "(optional): determines the GraphViz output format");
        gvFormat.setRequired(false);
        optionHandler.addOption(gvFormat);

        Option gvLayouter = new Option("l", "graphVizLayouter", true,
                "(optional): determines the GraphViz layout program (default: 'dot')");
        gvLayouter.setRequired(false);
        optionHandler.addOption(gvLayouter);
    }

    @Override
    protected void graphStart(PrintStream out) {
        // Disable debugging to prevent recursive execution and restore it in
        // graphEnd()
        jGraLabLogLevel = JGraLab.getRootLogger().getLevel();
        JGraLab.setLogLevel(Level.OFF);

        initializeEvaluator();
        initializeGraphLayout();

        setGlobalVariables();
        setCommandLineVariables();

        createDotWriter(out);
        startDotGraph();
    }

    /**
     * Initializes the GreqlEvaluator and sets all known variables.
     */
    private void initializeEvaluator() {
        evaluator = new GreqlEvaluatorFacade(graph);
    }

    /**
     * Creates a GraphLayoutFactory and loads the GraphLayout.
     */
    private void initializeGraphLayout() {
        GraphLayoutFactory factory = new GraphLayoutFactory(evaluator);

        if (graphLayoutFilename != null) {
            File layoutFile = new File(graphLayoutFilename);
            factory.setPListGraphLayoutFilename(layoutFile);
        }

        layout = factory.createGraphLayout();
    }

    /**
     * Sets all known global variables in the {@link GreqlEvaluatorImpl}.
     */
    private void setGlobalVariables() {
        evaluator.setVariablesWithGreqlValues(layout.getGlobalVariables());
    }

    /**
     * Sets all provided command line switch in the {@link GreqlEvaluatorImpl}.
     */
    private void setCommandLineVariables() {
        evaluator.setVariable(PRINT_ROLENAMES, roleNames);
        evaluator.setVariable(PRINT_INCIDENCE_INDICES, printIncidenceIndices);
        evaluator.setVariable(PRINT_ELEMENT_SEQUENCE_INDICES, printElementSequenceIndices);
        evaluator.setVariable(PRINT_DOMAIN_NAMES, domainNames);
        evaluator.setVariable(SHORTEN_STRINGS, shortenStrings);
        evaluator.setVariable(ABBREVIATE_EDGE_ATTRIBUTE_NAMES, abbreviateEdgeAttributeNames);
        evaluator.setVariable(PRINT_EDGE_ATTRIBUTES, edgeAttributes);
    }

    /**
     * Creates a {@link DotWriter}.
     * 
     * @param out
     *            Provides stream, the DotWriter will use.
     */
    private void createDotWriter(PrintStream out) {
        writer = new DotWriter(out);
    }

    /**
     * Starts the Graph in the output file.
     */
    private void startDotGraph() {
        writer.startGraph(GraphType.DIRECTED, graph.getAttributedElementClass().getQualifiedName(),
                graph.getId() + " / " + graph.getGraphVersion());
    }

    @Override
    protected void printVertex(PrintStream out, Vertex vertex) {

        Definition definition = getCorrespondingDefinition(vertex);
        evaluator.setStaticVariablesOfGreqlEvaluator(vertex.getAttributedElementClass());
        writeLayoutedVertex(vertex, definition);
    }

    /**
     * Returns the responsible {@link TypeDefinition} or
     * {@link ElementDefinition} for the specified {@link AttributedElement}.
     * 
     * @param attributedElement
     *            Given {@link AttributedElement}.
     * @return Responsible {@link Definition}.
     */
    private Definition getCorrespondingDefinition(AttributedElement<?, ?> attributedElement) {
        if (layout.isDefinedbyElementDefinitions(attributedElement)) {
            return constructSpecificElementDefinition(attributedElement);
        } else {
            TypeDefinition definition = layout.getTypeDefinition(attributedElement);
            return definition;
        }
    }

    /**
     * Constructs an {@link ElementDefinition} for the given
     * {@link AttributedElement}.
     * 
     * @param element
     *            Given AttributedElement.
     * @return {@link ElementDefinition} for the provided
     *         {@link AttributedElement}.
     */
    private Definition constructSpecificElementDefinition(AttributedElement<?, ?> element) {

        // Retrieves corresponding underlying TypeDefinition
        Definition definition = layout.getTypeDefinition(element);
        definition = definition.clone();

        // Overwrites all Attributes redefined by ElementDefinitions in the
        // order of declaration
        for (ElementDefinition elementDefinition : layout.getElementDefinitions()) {
            if (elementDefinition.hasElement(element)) {
                definition.overwriteAttributes(elementDefinition);
            }
        }
        return definition;
    }

    /**
     * Evaluates all attributes of a given {@link Vertex} and prints via the
     * {@link DotWriter}.
     * 
     * @param vertex
     *            Provides Vertex.
     * @param definition
     *            Definition object used to layout the given Vertex.
     */
    private void writeLayoutedVertex(Vertex vertex, Definition definition) {

        // Resets changed variables in the GreqlEvaluator
        evaluator.setVariablesOfGreqlEvaluator(vertex, getCurrentElementSequenceIndex());

        // Retrieves the unified vertex name ("v1", "v2", ... ) and the
        // evaluated style attribute list.
        String name = getVertexName(vertex);
        Map<String, String> evalutatedList = createEvaluatedStyleAttributeList(definition);
        if (!evalutatedList.containsKey("id")) {
            evalutatedList.put("id", name);
        }
        writer.writeNode(name, evalutatedList);
    }

    /**
     * Returns unified vertex names. A vertex name has the prefix 'v' followed
     * by the {@link Vertex} id.
     * 
     * @param vertex
     *            Given {@link Vertex}
     * @return Unified vertex name.
     */
    private String getVertexName(Vertex vertex) {
        return "v" + vertex.getId();
    }

    /**
     * Creates a evaluated style attribute list of the given {@link Definition}.
     * <b>Note:</b><br>
     * The current element has been set to the {@link GreqlEvaluatorImpl} via
     * the method
     * {@link GreqlEvaluatorFacade#setVariablesOfGreqlEvaluator(AttributedElement, int)}
     * .
     * 
     * @param spec
     *            Given {@link Definition} with the style attributes and their
     *            queries.
     * @return Evaluated style attribute list with attribute names as key and
     *         the evaluated value string as value.
     */
    private Map<String, String> createEvaluatedStyleAttributeList(Definition spec) {

        Map<String, String> evaluatedList = new HashMap<>();

        for (String attributeName : spec.getAttributeNames()) {
            String query = spec.getAttributeValue(attributeName);
            Object result = evaluator.evaluate(query);
            evaluatedList.put(attributeName, result.toString());
        }
        return evaluatedList;
    }

    @Override
    protected void printEdge(PrintStream out, Edge edge) {
        Definition definition = getCorrespondingDefinition(edge);
        evaluator.setStaticVariablesOfGreqlEvaluator(edge.getAttributedElementClass());
        writeLayoutedEdge(edge, definition);
    }

    /**
     * Evaluates all attributes of a given {@link Edge} and prints via the
     * {@link DotWriter}.
     * 
     * @param edge
     *            Provides Edge.
     * @param definition
     *            Definition object used to layout the given Vertex.
     */
    private void writeLayoutedEdge(Edge edge, Definition definition) {

        // Resets changed variables in the GreqlEvaluator
        evaluator.setVariablesOfGreqlEvaluator(edge, getCurrentElementSequenceIndex());

        // Reverts the direction of the if isReversedEdge is true
        // This will not change the style, but will change the layout process in
        // GraphViz
        boolean isReversedEdge = isReversedEdges();

        // Simple swap
        Vertex alpha = !isReversedEdge ? edge.getAlpha() : edge.getOmega();
        Vertex omega = !isReversedEdge ? edge.getOmega() : edge.getAlpha();

        // Retrieves the unified names of the alpha and omega edges.
        String alphaVertex = getVertexName(alpha);
        String omegaVertex = getVertexName(omega);

        // Retrieves the evaluated style attribute list
        Map<String, String> evaluatedList = createEvaluatedStyleAttributeList(definition);
        // Swaps all reversible style attributes.
        if (isReversedEdge) {
            reverseEdgeAttributes(evaluatedList);
        }
        if (!evaluatedList.containsKey("id")) {
            evaluatedList.put("id", "e" + edge.getNormalEdge().getId());
        }
        writer.writeEdge(alphaVertex, omegaVertex, evaluatedList);
    }

    /**
     * Reverses all reversible style attributes in the provided evaluted style
     * attribute list.
     * 
     * @param evaluatedList
     *            Given evaluated style attribute list as {@link Map}.
     */
    private void reverseEdgeAttributes(Map<String, String> evaluatedList) {

        for (Entry<String, String> entry : DotWriter.reversableEdgeAttributePairs.entrySet()) {
            swapAttributes(entry.getKey(), entry.getValue(), evaluatedList);
        }
    }

    /**
     * Swaps two key-value pairs in a map.
     * 
     * @param head
     *            Key of the first key-value pair.
     * @param tail
     *            Key of the second key-value pair.
     * @param evaluatedList
     *            Evaluated style attribute list.
     */
    private void swapAttributes(String head, String tail, Map<String, String> evaluatedList) {
        String headValue = evaluatedList.remove(head);
        String tailValue = evaluatedList.remove(tail);

        if (headValue != null) {
            evaluatedList.put(tail, headValue);
        }
        if (tailValue != null) {
            evaluatedList.put(head, tailValue);
        }
    }

    @Override
    protected void graphEnd(PrintStream out) {
        writer.close();
        writer = null;
        JGraLab.setLogLevel(jGraLabLogLevel);
    }

    @Override
    protected String stringQuote(String s) {
        throw new RuntimeException("This method should have been called!");
    }

    /**
     * Return a flag indicating that incidence numbers should be included in the
     * graph layout process.
     * 
     * @return True, if incidence numbers should be be printed.
     */
    public boolean printsIncidenceNumbers() {
        return printIncidenceIndices;
    }

    /**
     * @param printIncidenceNumbers
     *            If true, then the incidence numbers will be printed near the
     *            start and end points of edges.
     */
    public void setPrintIncidenceNumbers(boolean printIncidenceNumbers) {
        printIncidenceIndices = printIncidenceNumbers;
    }

    public boolean isAbbreviateAttributeNames() {
        return abbreviateEdgeAttributeNames;
    }

    public void setAbbreviateAttributeNames(boolean abbreviateAttributeNames) {
        abbreviateEdgeAttributeNames = abbreviateAttributeNames;
    }

    public GraphLayout getGraphLayout() {
        return layout;
    }

    public void setGraphLayout(GraphLayout layout) {
        this.layout = layout;
    }

    public boolean isAbbreviateEdgeAttributeNames() {
        return abbreviateEdgeAttributeNames;
    }

    public void setAbbreviateEdgeAttributeNames(boolean abbreviateEdgeAttributeNames) {
        this.abbreviateEdgeAttributeNames = abbreviateEdgeAttributeNames;
    }

    public boolean isPrintElementSequenceIndices() {
        return printElementSequenceIndices;
    }

    public void setPrintElementSequenceIndices(boolean printElementSequenceIndices) {
        this.printElementSequenceIndices = printElementSequenceIndices;
    }

    public String getGraphLayoutFilename() {
        return graphLayoutFilename;
    }

    public void setPListGraphLayoutFilename(String graphLayoutFilename) {
        this.graphLayoutFilename = graphLayoutFilename;
    }

    public GraphVizLayouter getGraphVizLayouter() {
        return graphVizLayouter;
    }

    public void setGraphVizLayouter(GraphVizLayouter graphVizLayouter) {
        this.graphVizLayouter = graphVizLayouter;
    }

    public GraphVizOutputFormat getGraphVizOutputFormat() {
        return graphVizOutputFormat;
    }

    public void setGraphVizOutputFormat(GraphVizOutputFormat graphVizOutputFormat) {
        this.graphVizOutputFormat = graphVizOutputFormat;
    }
}