001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of AutomataLib, http://www.automatalib.net/. 003 * 004 * AutomataLib is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License version 3.0 as published by the Free Software Foundation. 007 * 008 * AutomataLib is distributed in the hope that it will be useful, 009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * Lesser General Public License for more details. 012 * 013 * You should have received a copy of the GNU Lesser General Public 014 * License along with AutomataLib; if not, see 015 * http://www.gnu.de/documents/lgpl.en.html. 016 */ 017package net.automatalib.commons.dotutil; 018 019import java.awt.Desktop; 020import java.awt.Dialog; 021import java.awt.Dimension; 022import java.awt.event.ActionEvent; 023import java.awt.event.KeyAdapter; 024import java.awt.event.KeyEvent; 025import java.awt.image.BufferedImage; 026import java.io.BufferedReader; 027import java.io.File; 028import java.io.FileNotFoundException; 029import java.io.FileReader; 030import java.io.IOException; 031import java.io.InputStream; 032import java.io.OutputStream; 033import java.io.OutputStreamWriter; 034import java.io.Reader; 035import java.io.StringReader; 036import java.io.StringWriter; 037import java.io.Writer; 038import java.util.logging.Logger; 039 040import javax.imageio.ImageIO; 041import javax.swing.AbstractAction; 042import javax.swing.JDialog; 043import javax.swing.JFrame; 044import javax.swing.JMenu; 045import javax.swing.JMenuBar; 046import javax.swing.JOptionPane; 047import javax.swing.JScrollPane; 048 049import net.automatalib.commons.util.IOUtil; 050 051 052/** 053 * Utility class to simplify operating the GraphVIZ "dot" utility. Please note that all 054 * of the provided methods require GraphVIZ to be installed on the system, and that the 055 * "dot" binary resides in the execution path. 056 * 057 * @author Malte Isberner <malte.isberner@gmail.com> 058 * 059 */ 060public class DOT { 061 062 private static final Logger LOGGER = Logger.getLogger("automatalib.dotutil"); 063 064 private static final int MAX_WIDTH = 800; 065 private static final int MAX_HEIGHT = 600; 066 067 068 public static Process executeDOT(String format, String ...additionalOpts) throws IOException { 069 String[] dotArgs = new String[2 + additionalOpts.length]; 070 dotArgs[0] = "dot"; 071 dotArgs[1] = "-T" + format; 072 System.arraycopy(additionalOpts, 0, dotArgs, 2, additionalOpts.length); 073 Process dot = Runtime.getRuntime().exec(dotArgs); 074 075 return dot; 076 } 077 078 079 /** 080 * Invokes the GraphVIZ DOT utility for rendering graphs. 081 * @param r the reader from which the GraphVIZ description is obtained. 082 * @param format the output format, as understood by the dot utility, e.g., 083 * png, ps, ... 084 * @return an input stream from which the image data can be read. 085 * @throws IOException if reading from the specified reader fails. 086 */ 087 public static InputStream runDOT(Reader r, String format, String ...additionalOpts) throws IOException { 088 Process dot = executeDOT(format, additionalOpts); 089 090 OutputStream dotIn = dot.getOutputStream(); 091 Writer dotWriter = new OutputStreamWriter(dotIn); 092 093 IOUtil.copy(r, dotWriter); 094 095 try { 096 IOUtil.skip(dot.getErrorStream()); 097 } 098 catch(IOException e) {} 099 100 return dot.getInputStream(); 101 } 102 103 /** 104 * Invokes the DOT utility on a string. 105 * Convenience method, see {@link #runDOT(Reader, String)}. 106 */ 107 public static InputStream runDOT(String dotText, String format, String ...additionalOpts) throws IOException { 108 StringReader sr = new StringReader(dotText); 109 return runDOT(sr, format, additionalOpts); 110 } 111 112 /** 113 * Invokes the DOT utility on a file. 114 * Convenience method, see {@link #runDOT(Reader, String)}. 115 */ 116 public static InputStream runDOT(File dotFile, String format, String ...additionalOpts) throws IOException { 117 FileReader fr = new FileReader(dotFile); 118 return runDOT(fr, format, additionalOpts); 119 } 120 121 /** 122 * Invokes the GraphVIZ DOT utility for rendering graphs, writing output 123 * to the specified file. 124 * @param r the reader from which the GraphVIZ description is read. 125 * @param format the output format to produce. 126 * @param out the file to which the output is written. 127 * @throws IOException if an I/O error occurs reading from the given input 128 * or writing to the output file. 129 */ 130 public static void runDOT(Reader r, String format, File out) throws IOException { 131 Process dot = executeDOT(format, "-o" + out.getAbsolutePath()); 132 133 OutputStream dotIn = dot.getOutputStream(); 134 Writer dotWriter = new OutputStreamWriter(dotIn); 135 136 IOUtil.copy(r, dotWriter); 137 dot.getErrorStream().close(); 138 dot.getInputStream().close(); 139 try { 140 dot.waitFor(); 141 } 142 catch(InterruptedException ex) { 143 LOGGER.warning("Interrupted while waiting for 'dot' process to exit." + ex); 144 } 145 } 146 147 148 /** 149 * Invokes the DOT utility on a string, producing an output file. 150 * Convenience method, see {@link #runDOT(Reader, String, File)}. 151 */ 152 public static void runDOT(String dotText, String format, File out) throws IOException { 153 runDOT(new StringReader(dotText), format, out); 154 } 155 156 /** 157 * Invokes the DOT utility on a file, producing an output file. 158 * Convenience method, see {@link #runDOT(Reader, String, File)}. 159 */ 160 public static void runDOT(File dotFile, String format, File out) throws IOException, FileNotFoundException { 161 runDOT(new FileReader(dotFile), format, out); 162 } 163 164 /** 165 * Renders a GraphVIZ description, using an external program for displaying. 166 * The program is determined by the system's file type associations, 167 * using the {@link Desktop#open(File)} method. 168 * @param r the reader from which the GraphVIZ description is read. 169 * @param format the output format, as understood by the dot utility, e.g., 170 * png, ps, ... 171 */ 172 public static void renderDOTExternal(Reader r, String format) { 173 try { 174 File image = File.createTempFile("dot", format); 175 runDOT(r, format, image); 176 Desktop.getDesktop().open(image); 177 } 178 catch(Exception e) { 179 JOptionPane.showMessageDialog(null, "Error rendering DOT: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); 180 } 181 } 182 183 /** 184 * Renders a GraphVIZ description from a string, using an external program 185 * for displaying. 186 * Convenience method, see {@link #renderDOTExternal(Reader, String)}. 187 */ 188 public static void renderDOTExternal(String dotText, String format) { 189 renderDOTExternal(new StringReader(dotText), format); 190 } 191 192 /** 193 * Renders a GraphVIZ description from a file, using an external program 194 * for displaying. 195 * Convenience method, see {@link #renderDOTExternal(Reader, String)}. 196 * @throws FileNotFoundException if the specified file was not found. 197 */ 198 public static void renderDOTExternal(File dotFile, String format) throws FileNotFoundException { 199 renderDOTExternal(new FileReader(dotFile), format); 200 } 201 202 /** 203 * Renders a GraphVIZ description and displays it in a Swing window. 204 * @param r the reader from which the description is obtained. 205 * @param modal whether or not the dialog should be modal. 206 */ 207 public static void renderDOT(Reader r, boolean modal) { 208 final DOTComponent cmp = createDOTComponent(r); 209 if (cmp == null) 210 return; 211 212 final JDialog frame = new JDialog((Dialog) null, modal); 213 JScrollPane scrollPane = new JScrollPane(cmp); 214 frame.setContentPane(scrollPane); 215 frame.setMaximumSize(new Dimension(MAX_WIDTH, MAX_HEIGHT)); 216 frame.pack(); 217 JMenu menu = new JMenu("File"); 218 menu.add(cmp.getSavePngAction()); 219 menu.add(cmp.getSaveDotAction()); 220 menu.addSeparator(); 221 menu.add(new AbstractAction("Close") { 222 private static final long serialVersionUID = -1L; 223 224 @Override 225 public void actionPerformed(ActionEvent e) { 226 frame.setVisible(false); 227 } 228 }); 229 JMenuBar menuBar = new JMenuBar(); 230 menuBar.add(menu); 231 frame.setJMenuBar(menuBar); 232 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 233 frame.setVisible(true); 234 frame.addKeyListener(new KeyAdapter() { 235 @Override 236 public void keyTyped(KeyEvent e) { 237 if(e.getKeyCode() == KeyEvent.VK_ESCAPE) 238 frame.setVisible(false); 239 } 240 }); 241 } 242 243 244 /** 245 * Renders a GraphVIZ description from a string and displays it in a 246 * Swing window. 247 * Convenience method, see {@link #renderDOT(Reader, boolean)}. 248 */ 249 public static void renderDOT(String dotText, boolean modal) { 250 renderDOT(new StringReader(dotText), modal); 251 } 252 253 /** 254 * Renders a GraphVIZ description from a string and displays it in a 255 * Swing window. 256 * Convenience method, see {@link #renderDOT(Reader, boolean)}. 257 * @throws FileNotFoundException if the specified file was not found. 258 */ 259 public static void renderDOT(File dotFile, boolean modal) throws FileNotFoundException { 260 renderDOT(new FileReader(dotFile), modal); 261 } 262 263 /** 264 * Reads a DOT description from a reader and returns the PNG rendering result as a 265 * {@link BufferedImage}. 266 * @param dotReader the reader from which to read the description 267 * @return the rendering result 268 * @throws IOException if reading from the reader fails, or the pipe to the 269 * DOT process breaks. 270 */ 271 public static BufferedImage renderDOTImage(Reader dotReader) throws IOException { 272 InputStream pngIs = runDOT(dotReader, "png"); 273 BufferedImage img = ImageIO.read(pngIs); 274 pngIs.close(); 275 276 return img; 277 } 278 279 /** 280 * Reads a DOT description from a string and returns the PNG rendering result as a 281 * {@link BufferedImage}. 282 * @param dotText the DOT description 283 * @return the rendering result 284 * @throws IOException if the pipe to the DOT process breaks. 285 */ 286 public static BufferedImage renderDOTImage(String dotText) throws IOException { 287 return renderDOTImage(new StringReader(dotText)); 288 } 289 290 /** 291 * Reads a DOT description from a file and returns the PNG rendering result as a 292 * {@link BufferedImage}. 293 * @param dotFile the file containing the DOT description 294 * @return the rendering result 295 * @throws IOException if reading from the file fails or the pipe to the DOT process breaks. 296 */ 297 public static BufferedImage renderDOTImage(File dotFile) throws IOException { 298 return renderDOTImage(new BufferedReader(new FileReader(dotFile))); 299 } 300 301 /** 302 * Creates a {@link DOTComponent} that displays the result of rendering a DOT description 303 * read from a {@link Reader}. 304 * @param r the reader to read from 305 * @return the DOT component 306 */ 307 public static DOTComponent createDOTComponent(Reader r) { 308 try { 309 DOTComponent dc = new DOTComponent(r); 310 return dc; 311 } 312 catch(IOException e) { 313 JOptionPane.showMessageDialog(null, "Could not run DOT: " + e.getMessage(), "Failed to run DOT", JOptionPane.ERROR_MESSAGE); 314 return null; 315 } 316 } 317 318 /** 319 * Creates a Writer that can be used to write a DOT description to. Upon closing the writer, 320 * a window with the rendering result appears. 321 * @param modal whether or not this window is modal (if set to <tt>true</tt>, calls to 322 * {@link Writer#close()} will block. 323 * @return the writer 324 */ 325 public static Writer createDotWriter(final boolean modal) { 326 return new StringWriter() { 327 @Override 328 public void close() throws IOException { 329 renderDOT(getBuffer().toString(), modal); 330 super.close(); 331 } 332 }; 333 } 334}