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 }