001 /*--------------------------------------------------------------------------+ 002 $Id: GraphvizGenerator.java 26283 2010-02-18 11:18:57Z juergens $ 003 | | 004 | Copyright 2005-2010 Technische Universitaet Muenchen | 005 | | 006 | Licensed under the Apache License, Version 2.0 (the "License"); | 007 | you may not use this file except in compliance with the License. | 008 | You may obtain a copy of the License at | 009 | | 010 | http://www.apache.org/licenses/LICENSE-2.0 | 011 | | 012 | Unless required by applicable law or agreed to in writing, software | 013 | distributed under the License is distributed on an "AS IS" BASIS, | 014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 015 | See the License for the specific language governing permissions and | 016 | limitations under the License. | 017 +--------------------------------------------------------------------------*/ 018 package edu.tum.cs.commons.graph; 019 020 import java.awt.image.BufferedImage; 021 import java.io.BufferedReader; 022 import java.io.File; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.io.OutputStreamWriter; 027 import java.io.Writer; 028 029 import javax.imageio.ImageIO; 030 031 import edu.tum.cs.commons.io.StreamReaderThread; 032 033 /** 034 * Java interface to the Graphviz graph drawing toolkit. 035 * 036 * @author Florian Deissenboeck 037 * @author Benjamin Hummel 038 * @author $Author: juergens $ 039 * @version $Rev: 26283 $ 040 * @levd.rating GREEN Hash: B550212FE943BEBCD0FD6F32553ABF78 041 */ 042 public class GraphvizGenerator { 043 044 /** Path to the layout engine executable. */ 045 private final String layoutEnginePath; 046 047 /** 048 * Create a new generator that uses <code>dot</code> and expects it to be 049 * on the path. 050 * 051 */ 052 public GraphvizGenerator() { 053 this("dot"); 054 } 055 056 /** 057 * Create a new generator by specifying the executable of the layout engine. 058 * This may be used if <code>dot</code> is not on the path or if another 059 * layout engine like <code>neato</code> should be used. 060 * 061 * @param layoutEnginePath 062 * path to layout engine excutable 063 */ 064 public GraphvizGenerator(String layoutEnginePath) { 065 this.layoutEnginePath = layoutEnginePath; 066 } 067 068 /** 069 * Export a graph to a file. 070 * 071 * @param description 072 * the graph description 073 * @param file 074 * the file to export to. 075 * @param format 076 * the export format. 077 * @throws IOException 078 * if an I/O problem occurrs. 079 * @throws GraphvizException 080 * if Graphviz produced an error (exit code != 0) 081 */ 082 public void generateFile(String description, File file, 083 EGraphvizOutputFormat format) throws IOException, GraphvizException { 084 runDot(description, null, "-T" + format.name().toLowerCase(), "-o" 085 + file); 086 } 087 088 /** 089 * Export a graph to a file and return the HTML image map code. 090 * 091 * @param description 092 * the graph description 093 * @param file 094 * the file to export to. 095 * @param format 096 * the export format. 097 * @return the generated image map. These are only area-tags. The map tags 098 * including the name of the map must be created by the calling 099 * application. 100 * @throws IOException 101 * if an I/O problem occurrs. 102 * @throws GraphvizException 103 * if Graphviz produced an error (exit code != 0) 104 */ 105 public String generateFileAndImageMap(String description, File file, 106 EGraphvizOutputFormat format) throws IOException, GraphvizException { 107 TextReader reader = new TextReader(); 108 runDot(description, reader, "-T" + format.name().toLowerCase(), "-o" 109 + file, "-Tcmap"); 110 return reader.contents.toString(); 111 } 112 113 /** 114 * Generate an image from a graph description. This uses Graphviz to 115 * generate a PNG image of the graph and javax.imageio to create the image 116 * object. All communication with Graphviz is handled via streams so no 117 * temporary files are used. 118 * 119 * @param description 120 * the graph description. 121 * @return the image 122 * @throws IOException 123 * if an I/O problem occurrs. 124 * @throws GraphvizException 125 * if Graphviz produced an error (exit code != 0) 126 */ 127 public BufferedImage generateImage(String description) throws IOException, 128 GraphvizException { 129 ImageReader reader = new ImageReader(); 130 runDot(description, reader, "-Tpng"); 131 return reader.image; 132 } 133 134 /** 135 * Executes DOT, feeding in the provided graph description. The output of 136 * dot may be handled using an {@link IStreamReader}. DOT errorr are 137 * handled in this method. 138 * 139 * @param description 140 * the graph description. 141 * @param streamReader 142 * the reader used for the output stream of DOT. If this is null, 143 * a dummy reader is used to keep DOT from blocking. 144 * @param arguments 145 * the arguments passed to DOT. 146 * @throws IOException 147 * if an I/O problem occurrs. 148 * @throws GraphvizException 149 * if Graphviz produced an error (exit code != 0) 150 */ 151 private void runDot(String description, IStreamReader streamReader, 152 String... arguments) throws IOException, GraphvizException { 153 154 String[] completeArguments = new String[arguments.length + 1]; 155 completeArguments[0] = layoutEnginePath; 156 for (int i = 0; i < arguments.length; ++i) { 157 completeArguments[i + 1] = arguments[i]; 158 } 159 160 ProcessBuilder builder = new ProcessBuilder(completeArguments); 161 Process dotProcess = builder.start(); 162 163 // read error for later use 164 StreamReaderThread errReader = new StreamReaderThread(dotProcess 165 .getErrorStream()); 166 167 // pipe graph into dot 168 Writer stdIn = new OutputStreamWriter(dotProcess.getOutputStream()); 169 stdIn.write(description); 170 stdIn.close(); 171 172 if (streamReader == null) { 173 // read dot standard output to drain the buffer, then throw away 174 new StreamReaderThread(dotProcess.getInputStream()); 175 } else { 176 // reading may happen in this thread, as stderr is read in a thread 177 // of its own 178 streamReader.performReading(dotProcess.getInputStream()); 179 } 180 181 // wait for dot 182 try { 183 dotProcess.waitFor(); 184 } catch (InterruptedException e) { 185 // ignore this one 186 } 187 188 if (dotProcess.exitValue() != 0) { 189 throw new GraphvizException(errReader.getContent()); 190 } 191 192 } 193 194 /** 195 * Interface used from 196 * {@link GraphvizGenerator#runDot(String, edu.tum.cs.commons.graph.GraphvizGenerator.IStreamReader, String[])}. 197 */ 198 private static interface IStreamReader { 199 200 /** Perform the desired action on the given input stream. */ 201 void performReading(InputStream inputStream) throws IOException; 202 } 203 204 /** A stream reader for reading an image. */ 205 private static class ImageReader implements IStreamReader { 206 /** The image read. */ 207 BufferedImage image = null; 208 209 /** {@inheritDoc} */ 210 public void performReading(InputStream inputStream) throws IOException { 211 image = ImageIO.read(inputStream); 212 } 213 } 214 215 /** A stream reader for reading plain text. */ 216 private static class TextReader implements IStreamReader { 217 /** The contents read from the stream. */ 218 StringBuilder contents = new StringBuilder(); 219 220 /** {@inheritDoc} */ 221 public void performReading(InputStream inputStream) throws IOException { 222 BufferedReader reader = new BufferedReader(new InputStreamReader( 223 inputStream)); 224 char[] buffer = new char[1024]; 225 int read = 0; 226 while ((read = reader.read(buffer)) != -1) { 227 contents.append(buffer, 0, read); 228 } 229 } 230 } 231 }