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    }