com.github.jabbalaci.graphviz.GraphViz Maven / Gradle / Ivy
package com.github.jabbalaci.graphviz;
// GraphViz.java - a simple API to call dot from Java programs
/*$Id: e9ad844c512f9584877bc31321347a85e6ec3ff7 $*/
/*
******************************************************************************
* *
* (c) Copyright Laszlo Szathmary *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 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 Lesser General Public *
* License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program; if not, write to the Free Software Foundation, *
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
* *
******************************************************************************
*/
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import static java.lang.String.format;
/**
*
* - Purpose: GraphViz Java API
*
-
*
*
- Description:
*
- With this Java class you can simply call dot
* from your Java programs.
*
- Example usage:
*
-
*
* GraphViz gv = new GraphViz();
* gv.addln(gv.start_graph());
* gv.addln("A -> B;");
* gv.addln("A -> C;");
* gv.addln(gv.end_graph());
* System.out.println(gv.getDotSource());
*
* String type = "gif";
* String representationType="dot";
* File out = new File("out." + type); // out.gif in this example
* gv.writeGraphToFile( gv.getGraph(gv.getDotSource(), type, representationType), out );
*
*
*
*
*
* @author Laszlo Szathmary ([email protected])
* @version v0.1, 2003/12/04 (December) -- first release
*/
@Slf4j
public class GraphViz {
private static final GraphViz.Os os = detectOs();
/**
* The image size in dpi. 96 dpi is normal size. Higher values are 10% higher each.
* Lower values 10% lower each.
*
* dpi patch by Peter Mueller
*/
private DpiSizes dpiSize = DpiSizes.DPI_96;
/**
* The source of the graph written in dot language.
*/
private StringBuilder graph = new StringBuilder();
private String tempDir;
private String executable;
private RepresentationType representationType = RepresentationType.DOT;
private Type type = Type.PNG;
/**
* Configurable Constructor with path to executable dot and a temp dir
*
* @param executable absolute path to dot executable
* @param tempDir absolute path to temp directory
*/
public GraphViz(String executable, String tempDir) {
this.executable = executable;
this.tempDir = tempDir;
}
/**
* Convenience Constructor with default OS specific pathes
* creates a new GraphViz object that will contain a graph.
* Windows:
* executable = c:/Program Files (x86)/Graphviz 2.28/bin/dot.exe
* tempDir = c:/temp
* MacOs:
* executable = /usr/local/bin/dot
* tempDir = /tmp
* Linux:
* executable = /usr/bin/dot
* tempDir = /tmp
*/
public GraphViz() {
this.executable = findDot();
this.tempDir = findTempDir();
}
private String findTempDir() {
final String value = System.getProperty("java.io.tmpdir");
if (value != null) {
return value;
}
switch (os) {
case WINDOWS:
return "c:/temp";
case LINUX:
case MAC:
return "/tmp";
default:
throw new IllegalStateException("Unknown OS");
}
}
private String findDot() {
final String value = System.getenv("GRAPHVIZ_DOT");
if (value != null) {
return value;
}
switch (os) {
case WINDOWS:
return "c:/Program Files (x86)/Graphviz 2.28/bin/dot.exe";
case LINUX:
return "/usr/bin/dot";
case MAC:
return "/usr/local/bin/dot";
default:
throw new IllegalStateException("Unknown OS");
}
}
/**
* Sets image dpi.
*
* @param dpiSize the dpi size
*/
public void setImageDpi(final DpiSizes dpiSize) {
this.dpiSize = dpiSize;
}
/**
* Gets image dpi.
*
* @return the image dpi
*/
public int getImageDpi() {
return this.dpiSize.value();
}
/**
* Returns the graph's source description in dot language.
*
* @return Source of the graph in dot language.
*/
public String source() {
return this.graph.toString();
}
/**
* Start a graph.
*
* @param name Graph name.
*/
public void startGraph(final String name) {
this.graph.append(format("digraph %s {", name)).append("\n");
}
/**
* End a graph.
*/
public void endGraph() {
this.graph.append("}").append("\n");
}
/**
* Takes the cluster or subgraph id as input parameter start a subgraph.
*
* @param clusterid the clusterid
*/
public void startSubgraph(int clusterid) {
this.graph
.append("subgraph cluster_")
.append(clusterid)
.append(" {")
.append("\n");
}
/**
* End a subgraph.
*/
public void endSubgraph() {
this.graph.append("}").append("\n");
}
/**
* Add Node.
*
* @param name the name
* @param label the label
*/
public void node(final String name, final String label) {
addln(format("%s [label=\"%s\"];", name, label));
}
/**
* Add Node.
*
* @param name the name
*/
public void node(final String name) {
addln(format("%s;", name));
}
/**
* Adds a string to the graph's source (without newline).
*
* @param line the line
*/
public void add(final String line) {
this.graph.append(line);
}
/**
* Adds a string to the graph's source (with newline).
*
* @param line the line
*/
public void addln(final String line) {
this.graph.append(line).append("\n");
}
/**
* Adds a newline to the graph's source.
*/
public void addln() {
this.graph.append('\n');
}
/**
* Clear graph.
*/
public void clearGraph() {
this.graph = new StringBuilder();
}
/**
* Returns the graph as an image in binary format.
*
* @param dot Dot file
* @return A byte array containing the image of the graph. http://www.graphviz.org under the Roadmap title
*/
private byte[] process(final File dot) {
final byte[] imgStream = getImgStream(dot);
if (!dot.delete()) {
log.warn("Warning: " + dot.getAbsolutePath() + " could not be deleted!");
}
return imgStream;
}
/**
* Writes the graph's image in a fileName.
*
* @param fileName Name of the fileName to where we want to write.
* @return true on Success
*/
public boolean writeTo(final String fileName) {
return writeTo(new File(fileName + "." + type.value()));
}
/**
* Writes the graph's image in a file.
*
* @param to A File object to where we want to write.
* * @return true on Success
*/
private boolean writeTo(final File to) {
try (FileOutputStream fos = new FileOutputStream(to)) {
fos.write(process(fileFrom(source())));
} catch (RuntimeException | IOException e) {
log.error(e.getMessage(), e);
return false;
}
return true;
}
/**
* It will call the external dot program, and return the image in binary format.
*
* @param dot Source of the graph (in dot language).
* http://www.graphviz.org under the Roadmap title
* @return The image of the graph in .gif format.
*/
private byte[] getImgStream(final File dot) {
final File img;
try {
img = File.createTempFile("graph_", "." + type.value(), new File(this.tempDir));
} catch (IOException ioe1) {
throw new IllegalStateException("Error: in I/O processing of tempfile in dir " + tempDir, ioe1);
}
try {
exec(dot, img);
} catch (java.io.IOException ioe) {
throw new IllegalStateException("Error: in calling external command", ioe);
} catch (java.lang.InterruptedException ie) {
throw new IllegalStateException("Error: the execution of the external program was interrupted", ie);
}
try (FileInputStream in = new FileInputStream(img.getAbsolutePath())) {
byte[] imgStream = new byte[in.available()];
int read = in.read(imgStream);
if (read < imgStream.length) {
log.error("Error: in read file");
}
if (!img.delete()) {
log.warn("Warning: " + img.getAbsolutePath() + " could not be deleted!");
}
return imgStream;
} catch (java.io.IOException ioe) {
throw new IllegalStateException("Error: in I/O processing of tempfile in dir " + tempDir, ioe);
}
}
private void exec(File dot, File img) throws IOException, InterruptedException {
// patch by Mike Chenault
// representation type with -K argument by Olivier Duplouy
String[] args = {
executable,
"-T" + type.value(),
"-K" + representationType.value(),
"-Gdpi=" + dpiSize.value(),
dot.getAbsolutePath(),
"-o",
img.getAbsolutePath()
};
Process p = Runtime.getRuntime().exec(args);
p.waitFor();
}
/**
* Writes the source of the graph in a file, and returns the written file
* as a File object.
*
* @param str Source of the graph (in dot language).
* @return The file (as a File object) that contains the source of the graph.
*/
private File fileFrom(final String str) throws java.io.IOException {
final File temp = File.createTempFile("graph_", ".dot.tmp", new File(tempDir));
try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(temp), "UTF-8"))) {
out.write(str);
} catch (IOException e) {
throw new IllegalStateException("Error: I/O error while writing the dot source to temp file!", e);
}
return temp;
}
/**
* Detects the client's operating system.
*/
private static Os detectOs() {
final String osName = System.getProperty("os.name").replaceAll("\\s", "");
if (osName.startsWith("Windows")) {
return Os.WINDOWS;
} else if ("MacOSX".equals(osName)) {
return Os.MAC;
} else if ("Linux".equals(osName)) {
return Os.LINUX;
} else {
return Os.UNKNOWN;
}
}
/**
* Sets representation type.
*
* @param representationType the representation type
*/
public void setRepresentationType(final RepresentationType representationType) {
this.representationType = representationType;
}
/**
* Sets representation type.
*
* @param type the representation type
*/
public void setType(final Type type) {
this.type = type;
}
private enum Os {
/**
* Linux os.
*/
LINUX,
/**
* Mac os.
*/
MAC,
/**
* Windows os.
*/
WINDOWS,
/**
* Unknown os.
*/
UNKNOWN
}
/**
* Type of how you want to represent the graph: dot, neato, fdp, sfdp, twopi, circo.
*/
public enum RepresentationType {
/**
* Dot representation type.
*/
DOT("dot"),
/**
* Fig representation type.
*/
NEATO("neato"),
/**
* Pdf representation type.
*/
FDP("fdp"),
/**
* Ps representation type.
*/
SFDP("sfdp"),
/**
* Svg representation type.
*/
TWOPI("twopi"),
/**
* Png representation type.
*/
CIRCO("circo");
private final String value;
RepresentationType(String value) {
this.value = value;
}
/**
* Value string.
*
* @return the string
*/
public String value() {
return value;
}
}
/**
* Type of the output image to be produced, e.g.: gif, dot, fig, pdf, ps, svg, png.
*/
public enum Type {
/**
* Gif representation type.
*/
GIF("gif"),
/**
* Dot representation type.
*/
DOT("dot"),
/**
* Fig representation type.
*/
FIG("fig"),
/**
* Pdf representation type.
*/
PDF("pdf"),
/**
* Ps representation type.
*/
PS("ps"),
/**
* Svg representation type.
*/
SVG("svg"),
/**
* Png representation type.
*/
PNG("png");
private final String value;
Type(String value) {
this.value = value;
}
/**
* Value string.
*
* @return the string
*/
public String value() {
return value;
}
}
/**
* The enum Dpi sizes.
*/
public enum DpiSizes {
/**
* Dpi 46 dpi sizes.
*/
DPI_46(46),
/**
* Dpi 51 dpi sizes.
*/
DPI_51(51),
/**
* Dpi 57 dpi sizes.
*/
DPI_57(57),
/**
* Dpi 63 dpi sizes.
*/
DPI_63(63),
/**
* Dpi 70 dpi sizes.
*/
DPI_70(70),
/**
* Dpi 78 dpi sizes.
*/
DPI_78(78),
/**
* Dpi 86 dpi sizes.
*/
DPI_86(86),
/**
* Dpi 96 dpi sizes.
*/
DPI_96(96),
/**
* Dpi 106 dpi sizes.
*/
DPI_106(106),
/**
* Dpi 116 dpi sizes.
*/
DPI_116(116),
/**
* Dpi 128 dpi sizes.
*/
DPI_128(128),
/**
* Dpi 141 dpi sizes.
*/
DPI_141(141),
/**
* Dpi 155 dpi sizes.
*/
DPI_155(155),
/**
* Dpi 170 dpi sizes.
*/
DPI_170(170),
/**
* Dpi 187 dpi sizes.
*/
DPI_187(187),
/**
* Dpi 206 dpi sizes.
*/
DPI_206(206),
/**
* Dpi 226 dpi sizes.
*/
DPI_226(226),
/**
* Dpi 1249 dpi sizes.
*/
DPI_249(249);
private final int value;
DpiSizes(int value) {
this.value = value;
}
/**
* Value int.
*
* @return the int
*/
int value() {
return value;
}
}
}