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 }