package org.kohsuke.bali.writer; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.text.MessageFormat; import org.kohsuke.bali.automaton.*; /** * Produces a gif/ps file from automaton by using * <a href="http://www.research.att.com/sw/tools/graphviz/">GraphViz</a>. * * @author Kohsuke Kawaguchi (kk@kohsuke.org) */ public class AutomatonVisualizer implements AutomatonWriter, AlphabetVisitor { public AutomatonVisualizer( String fileType, OutputStream target ) { this.fileType = fileType; this.target = target; } /** Format of the picture (e.g., "gif", "ps", "png".) */ private final String fileType; /** Stream that receives the image. */ private final OutputStream target; public void write( TreeAutomaton automaton ) throws IOException { System.err.println("producing a "+fileType+" file from the automaton"); if( fileType.equals("dot") ) { out = new PrintWriter(target); writeDot(automaton); } else { Process proc = Runtime.getRuntime().exec( new String[]{"dot","-T"+fileType}); out = new PrintWriter( new BufferedOutputStream(proc.getOutputStream())); writeDot(automaton); InputStream in = proc.getInputStream(); byte[] buf = new byte[256]; while(true) { int len = in.read(buf); if(len==-1) break; target.write(buf,0,len); } in.close(); try { proc.waitFor(); } catch( InterruptedException e ) { e.printStackTrace(); } } } private void writeDot( TreeAutomaton automaton ) { out.println("digraph G {"); out.println("node [shape=\"circle\"];"); State[] states = (State[]) automaton.getStates(); for( int i=0; i<states.length; i++ ) { s = states[i]; // to access this variable from visitor methods, 's' is a member variable String style=""; if(s.isFinal && !s.isEpsilon()) style += ",shape=\"doublecircle\""; if(s.getTextSensitivity()==State.TEXT_IGNORABLE) style += ",style=filled,fillcolor=lightgray"; if(style.length()>0) out.println(s+" ["+style.substring(1)+"];"); if(s.nextState!=null) { out.println(MessageFormat.format( "{0} -> {1} [style=dotted]", new Object[]{ s, s.nextState })); } Transition[] transitions = s.getDeclaredTransitions(); for( int j=0; j<transitions.length; j++ ) { t = transitions[j]; // to access this variable from visitor methods, 't' is a member variable t.alphabet.accept(this); } } out.println("}"); out.flush(); out.close(); } /** * OutputStream that is connected to stdin of dot. * Available only during the write method is being executed. */ private PrintWriter out; /** * The current state. * We are drawing transitions leaving from this state. * Available only during the write method is being executed. */ private State s; /** * The current transition. * We are drawing this transition as an edge. */ private Transition t; /** * Writes a state as a circle and returns an identifier for it. */ private String state( State s ) { if(s.isEpsilon()) { String tmp = getID(); out.println(tmp+" [shape=\"doublecircle\",label=\"eps\"];"); return tmp; } else { return s.toString(); } } public Object attribute(AttributeAlphabet alpha) { String name = '@'+alpha.name.nameClass.toString(); if( alpha.repeated ) name = name + '+'; out.println(MessageFormat.format( "{0} -> {1} [ label=\"{2} {3}\",color=\"{4} 1 .5\",fontcolor=\"{4} 1 .3\" ];", new Object[]{ s, state(t.right), name, t.left==null?"":t.left.toString(), new Float(0) } )); return null; } public Object nonExistentAttribute(NonExistentAttributeAlphabet alpha) { String name = alpha.toString(); out.println(MessageFormat.format( "{0} -> {1} [ label=\"{2} {3}\",color=\"{4} 1 .5\",fontcolor=\"{4} 1 .3\" ];", new Object[]{ s, state(t.right), name, t.left==null?"":t.left.toString(), new Float(0.8) } )); return null; } public Object element(ElementAlphabet alpha) { out.println(MessageFormat.format( "{0} -> {1} [ label=\"{2} {3}\",color=\"{4} 1 .5\",fontcolor=\"{4} 1 .3\" ];", new Object[]{ s, state(t.right), alpha.name.nameClass, t.left, new Float(0.2) } )); return null; } public Object interleave(InterleaveAlphabet alpha) { String tmp = getID(); out.println(MessageFormat.format( "{0} [shape=\"box\",label=\"\",color=\"black\",style=\"filled\"];", new Object[]{tmp})); out.println(MessageFormat.format( "{0} -> {1} [color=\"black\"];", new Object[]{s,tmp})); out.println(MessageFormat.format( "{0} -> {1} [color=\"black\"];", new Object[]{tmp,state(t.left)})); out.println(MessageFormat.format( "{0} -> {1} [color=\"black\"];", new Object[]{tmp,state(t.right)})); out.println(MessageFormat.format( "{0} -> {1} [color=\"black\",style=\"dotted\"];", new Object[]{tmp,state(alpha.join)})); return null; } public Object list(ListAlphabet alpha) { String tmp = getID(); out.println(tmp+" [shape=\"box\",label=\"\",color=\"purple\",style=\"filled\"];"); out.println(MessageFormat.format( "{0} -> {1} [color=\"purple\"];", new Object[]{s,tmp})); out.println(MessageFormat.format( "{0} -> {1} [color=\"purple\",style=\"dotted\"];", new Object[]{tmp,state(t.left)})); out.println(MessageFormat.format( "{0} -> {1} [color=\"purple\"];", new Object[]{tmp,state(t.right)})); return null; } public Object data(DataAlphabet alpha) { out.println(MessageFormat.format( "{0} -> {1} [ label=\"{2} {3}\",color=\"{4} 1 .5\",fontcolor=\"{4} 1 .3\" ];", new Object[]{ s, state(t.right), "data", state(t.left), new Float(.4) } )); return null; } public Object value(ValueAlphabet alpha) { out.println(MessageFormat.format( "{0} -> {1} [ label={2},color=\"{3} 1 .5\",fontcolor=\"{3} 1 .3\" ];", new Object[]{ s, state(t.right), alpha.value, new Float(.6) } )); return null; } /** * Sequence number generator. Used when an unique identifier * is necessary. */ private int num =0; /** Creates a new unique ID. */ private String getID() { return "t"+(num++); } }