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