001    package com.hammurapi.eventbus;
002    
003    import java.io.File;
004    import java.io.FileWriter;
005    import java.io.IOException;
006    import java.io.Writer;
007    import java.util.Collection;
008    import java.util.Date;
009    import java.util.LinkedHashMap;
010    import java.util.LinkedList;
011    import java.util.List;
012    import java.util.Map;
013    import java.util.Set;
014    import java.util.concurrent.Callable;
015    
016    import com.hammurapi.eventbus.AbstractEventBus.Snapshot;
017    import com.hammurapi.extract.Predicate;
018    
019    /**
020     * Outputs dispatch network in DOT format.
021     * @author Pavel Vlasov
022     *
023     * @param <E>
024     * @param <P>
025     * @param <C>
026     * @param <K>
027     */
028    public class DispatchNetworkDotSnapshot<E, P extends Comparable<P>, C, K, H extends EventBus.Handle<E,P,C>, S extends EventStore<E,P,C,H,S>> implements Snapshot<E, P, C, K, H, S> {
029            
030            private File out;
031            private Writer writer;
032            
033            private List<Callable<Object>> refCommands = new LinkedList<Callable<Object>>();        
034    
035            public DispatchNetworkDotSnapshot(File out) {
036                    this.out = out;
037            }
038    
039            @Override
040            public synchronized void start() {
041                    try {
042                            writer = new FileWriter(out);
043                            writer.write("digraph \""+new Date()+"\" {\n");
044                            for (Map.Entry<String, String> attr: graphAttributes.entrySet()) {
045                                    writer.write("\t"+attr.getKey()+"="+attr.getValue()+";\n");
046                            }
047                    } catch (IOException e) {
048                            throw new EventBusException(e);
049                    }
050            }
051    
052            @Override
053            public synchronized void end(boolean success) {
054                    try {
055                            if (success && out!=null) {
056                                    for (Callable<Object> c: refCommands) {
057                                            c.call();
058                                    }
059                            }
060                            writer.write("}");
061                            writer.close();
062                            writer = null;
063                    } catch (Exception e) {
064                            throw new EventBusException(e);
065                    }               
066            }
067    
068            @Override
069            public synchronized void handler(K id, EventHandler<E, P, C, H, S> eventHandler) {
070                    try {
071                            writer.write("\t"+id+" [");
072                            String hName = eventHandler.getClass().getName();
073                            int idx = hName.lastIndexOf('.');
074                            if (idx!=-1) {
075                                    hName = hName.substring(idx+1);
076                            }
077                            StringBuilder sb = new StringBuilder("shape=box, style=filled, fillcolor=burlywood1, label=\"");
078                            encode(hName, sb);
079                            sb.append(" ("+id+")\", tooltip=\"");
080                            encode(String.valueOf(eventHandler), sb);
081                            sb.append("\"");
082                            writer.write(sb.toString());
083                            writer.write("];\n");                   
084                    } catch (IOException e) {
085                            throw new EventBusException(e);
086                    }                               
087            }
088            
089            private Map<String,String> graphAttributes = new LinkedHashMap<String, String>();
090            
091            /**
092             * Sets gpraph attribute, see DOT language documentation (http://www.graphviz.org/pdf/dotguide.pdf) for details.
093             */
094            public void setGraphAttribute(String name, String value) {
095                    graphAttributes.put(name, value);
096            }
097    
098            @Override
099            public synchronized void predicateNode(
100                            final K id, 
101                            Predicate<E, C> predicate,
102                            final Collection<K> trueChildren, 
103                            final Collection<K> trueHandlers,
104                            final Collection<K> falseChildren, 
105                            final Collection<K> falseHandlers,
106                            boolean isRoot) {
107                    try {
108                            writer.write("\t"+id+" [");
109                            if (isRoot) {
110                                    writer.write("shape=circle, style=filled, fillcolor=aliceblue, label=\"Root\"");
111                            } else {
112                                    String pName = predicate.getClass().getName();
113                                    int idx = pName.lastIndexOf('.');
114                                    if (idx!=-1) {
115                                            pName = pName.substring(idx+1);
116                                    }
117                                    StringBuilder sb = new StringBuilder("shape=house, style=filled, fillcolor=aliceblue, label=\"");
118                                    encode(pName,sb);
119                                    sb.append(" ("+id+")\", tooltip=\"");
120                                    encode(String.valueOf(predicate), sb);
121                                    sb.append("\"");
122                                    writer.write(sb.toString());
123                            }
124                            writer.write("];\n");
125                            refCommands.add(new Callable<Object>() {
126    
127                                    @Override
128                                    public Object call() throws Exception {
129                                            for (K tcid: trueChildren) {
130                                                    writer.write("\t"+id+" -> "+tcid+" [color=green];\n");
131                                            }
132                                            
133                                            for (K thid: trueHandlers) {
134                                                    writer.write("\t"+id+" -> "+thid+" [color=green];\n");
135                                            }
136                                            
137                                            for (K fcid: falseChildren) {
138                                                    writer.write("\t"+id+" -> "+fcid+" [color=red];\n");
139                                            }
140                                            
141                                            for (K fhid: falseHandlers) {
142                                                    writer.write("\t"+id+" -> "+fhid+" [color=red];\n");
143                                            }
144                                            
145                                            return null;
146                                    }
147                                    
148                            });
149                    } catch (IOException e) {
150                            throw new EventBusException(e);
151                    }               
152            }
153    
154            @Override
155            public synchronized void joinInput(final K id, final K joinNodeId, int index) {
156                    try {
157                            writer.write("\t"+id+" [");
158                            writer.write("shape=circle, style=filled, fillcolor=chartreuse, label=\""+index+"\"");
159                            writer.write("];\n");                   
160                            
161                            refCommands.add(new Callable<Object>() {
162    
163                                    @Override
164                                    public Object call() throws Exception {
165                                            writer.write("\t"+id+" -> "+joinNodeId+";\n");
166                                            return null;
167                                    }
168                                    
169                            });
170                    } catch (IOException e) {
171                            throw new EventBusException(e);
172                    }               
173            }
174    
175            @Override
176            public synchronized void joinNode(
177                            final K id, 
178                            Predicate<E, C> predicate,
179                            Set<Integer> outputIndices, 
180                            final K eventHandlerId, 
181                            final K nextJoinNodeId) {
182                    try {
183                            writer.write("\t"+id+" [");
184                            writer.write("shape=invhouse, style=filled, fillcolor=chartreuse, label=\""+outputIndices+"\"");
185                            if (predicate!=null) {                          
186                                    StringBuilder sb = new StringBuilder(", tooltip=\"");
187                                    encode(String.valueOf(predicate), sb);
188                                    sb.append("\"");
189                                    writer.write(sb.toString());
190                            }
191                            writer.write("];\n");                                                                                                   
192                            refCommands.add(new Callable<Object>() {
193    
194                                    @Override
195                                    public Object call() throws Exception {
196                                            if (eventHandlerId==null) {
197                                                    writer.write("\t"+id+" -> "+nextJoinNodeId+";\n");
198                                            } else {
199                                                    writer.write("\t"+id+" -> "+eventHandlerId+";\n");                                           
200                                            }
201                                            return null;
202                                    }
203                                    
204                            });
205                    } catch (IOException e) {
206                            throw new EventBusException(e);
207                    }               
208            }
209                    
210            private void encode(String str, StringBuilder out) {
211                    
212                    for (char ch: str.toCharArray()) {
213                            if (Character.isLetterOrDigit(ch) || Character.isWhitespace(ch)) {
214                                    out.append(ch);
215                            } else {
216                                    out.append("&#");
217                                    out.append((int) ch);
218                                    out.append(";");
219                            }
220                            if (out.length()>500) {
221                                    out.append("...");
222                                    break;
223                            }
224                    }
225                    
226            }
227    
228    }