1 | package com.hammurapi.eventbus; |
2 | |
3 | import java.io.File; |
4 | import java.io.FileWriter; |
5 | import java.io.IOException; |
6 | import java.io.Writer; |
7 | import java.util.Collection; |
8 | import java.util.Date; |
9 | import java.util.LinkedHashMap; |
10 | import java.util.LinkedList; |
11 | import java.util.List; |
12 | import java.util.Map; |
13 | import java.util.Set; |
14 | import java.util.concurrent.Callable; |
15 | |
16 | import com.hammurapi.eventbus.AbstractEventBus.Snapshot; |
17 | import com.hammurapi.extract.Predicate; |
18 | |
19 | /** |
20 | * Outputs dispatch network in DOT format. |
21 | * @author Pavel Vlasov |
22 | * |
23 | * @param <E> |
24 | * @param <P> |
25 | * @param <C> |
26 | * @param <K> |
27 | */ |
28 | 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> { |
29 | |
30 | private File out; |
31 | private Writer writer; |
32 | |
33 | private List<Callable<Object>> refCommands = new LinkedList<Callable<Object>>(); |
34 | |
35 | public DispatchNetworkDotSnapshot(File out) { |
36 | this.out = out; |
37 | } |
38 | |
39 | @Override |
40 | public synchronized void start() { |
41 | try { |
42 | writer = new FileWriter(out); |
43 | writer.write("digraph \""+new Date()+"\" {\n"); |
44 | for (Map.Entry<String, String> attr: graphAttributes.entrySet()) { |
45 | writer.write("\t"+attr.getKey()+"="+attr.getValue()+";\n"); |
46 | } |
47 | } catch (IOException e) { |
48 | throw new EventBusException(e); |
49 | } |
50 | } |
51 | |
52 | @Override |
53 | public synchronized void end(boolean success) { |
54 | try { |
55 | if (success && out!=null) { |
56 | for (Callable<Object> c: refCommands) { |
57 | c.call(); |
58 | } |
59 | } |
60 | writer.write("}"); |
61 | writer.close(); |
62 | writer = null; |
63 | } catch (Exception e) { |
64 | throw new EventBusException(e); |
65 | } |
66 | } |
67 | |
68 | @Override |
69 | public synchronized void handler(K id, EventHandler<E, P, C, H, S> eventHandler) { |
70 | try { |
71 | writer.write("\t"+id+" ["); |
72 | String hName = eventHandler.getClass().getName(); |
73 | int idx = hName.lastIndexOf('.'); |
74 | if (idx!=-1) { |
75 | hName = hName.substring(idx+1); |
76 | } |
77 | StringBuilder sb = new StringBuilder("shape=box, style=filled, fillcolor=burlywood1, label=\""); |
78 | encode(hName, sb); |
79 | sb.append(" ("+id+")\", tooltip=\""); |
80 | encode(String.valueOf(eventHandler), sb); |
81 | sb.append("\""); |
82 | writer.write(sb.toString()); |
83 | writer.write("];\n"); |
84 | } catch (IOException e) { |
85 | throw new EventBusException(e); |
86 | } |
87 | } |
88 | |
89 | private Map<String,String> graphAttributes = new LinkedHashMap<String, String>(); |
90 | |
91 | /** |
92 | * Sets gpraph attribute, see DOT language documentation (http://www.graphviz.org/pdf/dotguide.pdf) for details. |
93 | */ |
94 | public void setGraphAttribute(String name, String value) { |
95 | graphAttributes.put(name, value); |
96 | } |
97 | |
98 | @Override |
99 | 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 | } |