| 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 | } |