1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package th.co.edge.jseq.svg;
24
25 import javax.xml.parsers.DocumentBuilder;
26 import javax.xml.parsers.DocumentBuilderFactory;
27 import javax.xml.parsers.ParserConfigurationException;
28
29 import org.w3c.dom.DOMImplementation;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.DocumentType;
32 import org.w3c.dom.Element;
33
34 import th.co.edge.jseq.Activation;
35 import th.co.edge.jseq.ActivationList;
36 import th.co.edge.jseq.Diagram;
37 import th.co.edge.jseq.MockObject;
38 import th.co.edge.jseq.MockObjectMap;
39 import th.co.edge.jseq.TextDiagram;
40 import th.co.edge.jseq.util.XMLUtil;
41
42
43
44
45
46 public class SVGGenerator {
47 private static final String SVG_1_1_PUBLIC_ID = "-//W3C//DTD SVG 1.1//EN";
48 private static final String SVG_1_1_SYSTEM_ID =
49 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";
50 private static final String SVG_NAMESPACE = "http://www.w3.org/2000/svg";
51
52 private static final int COLUMN_WIDTH = 100;
53 private static final int ROW_HEIGHT = 30;
54 private static final int LIFE_LINE_LEFT_MARGIN = 150;
55 private static final int LIFE_LINE_TOP_MARGIN = 75;
56 private static final int LIFE_LINE_EXTRA_HEIGHT = 100;
57 private static final int NUM_ROWS_BETWEEN_DIAGRAMS = 5;
58 private static final int EXTRA_DIAGRAM_WIDTH = 200;
59 private static final int EXTRA_DIAGRAM_HEIGHT = 50;
60 private static final int HEADER_LEFT_MARGIN = 25;
61 private static final int HEADER_TOP_MARGIN = 50;
62 private static final int HEADER_VERTICAL_SHIFT = 20;
63 private static final int INDENT_FIRST_ARROW = 55;
64 private static final int ARROW_HEAD_WIDTH = 5;
65 private static final int ARROW_HEAD_HEIGHT = 5;
66 private static final int ARROW_VERTICAL_MARGIN = 100;
67 private static final int SELF_ARROW_WIDTH = 45;
68 private static final int SELF_ARROW_HEIGHT = 30;
69 private static final int ACTIVATION_BOX_WIDTH = 10;
70 private static final int ACTIVATION_BOX_TOP_MARGIN = 15;
71 private static final int ACTIVATION_BOX_BOTTOM_MARGIN = 10;
72 private static final int METHOD_NAME_BOTTOM_MARGIN = 5;
73
74 private DocumentBuilder builder;
75
76 private MockObjectMap objectMap;
77 private int startRow;
78 private int row;
79 private int maxX;
80 private int maxY;
81 private Element groupHeaders;
82 private Element groupCalls;
83 private Element groupActivationBoxes;
84 private Element groupLifeLines;
85
86
87
88
89
90
91
92
93 public SVGGenerator() throws ParserConfigurationException {
94 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
95 factory.setNamespaceAware(true);
96 this.builder = factory.newDocumentBuilder();
97 }
98
99
100
101
102
103
104
105
106
107
108
109 public Diagram generate(ActivationList activationList) {
110 Document doc = createSVGDocument(activationList);
111 return new TextDiagram(XMLUtil.toString(doc));
112 }
113
114
115
116
117
118
119
120
121
122
123
124 public Document createSVGDocument(ActivationList activationList) {
125 DOMImplementation impl = builder.getDOMImplementation();
126 DocumentType docType =
127 impl.createDocumentType("svg", SVG_1_1_PUBLIC_ID,
128 SVG_1_1_SYSTEM_ID);
129 Document doc = impl.createDocument(SVG_NAMESPACE, "svg", docType);
130 Element root = doc.getDocumentElement();
131 root.setAttribute("xmlns", SVG_NAMESPACE);
132 this.startRow = 0;
133 for (Activation activation : activationList) {
134 fillSVGDocument(doc, activation);
135 this.startRow += row + NUM_ROWS_BETWEEN_DIAGRAMS;
136 }
137 root.setAttributeNS(null, "width", Integer.toString(maxX +
138 EXTRA_DIAGRAM_WIDTH));
139 root.setAttributeNS(null, "height", Integer.toString(maxY +
140 EXTRA_DIAGRAM_HEIGHT));
141 return doc;
142 }
143
144 private void fillSVGDocument(Document doc, Activation activation) {
145 this.objectMap = MockObjectMap.addAll(activation);
146 this.row = 0;
147 createGroups(doc);
148 addHeaders(doc);
149 addCalls(doc, activation);
150 addLifelines(doc);
151 }
152
153 private void createGroups(Document doc) {
154 groupHeaders = doc.createElementNS(SVG_NAMESPACE, "g");
155 groupHeaders.setAttributeNS(null, "id", "Headers");
156 groupCalls = doc.createElementNS(SVG_NAMESPACE, "g");
157 groupCalls.setAttributeNS(null, "id", "Calls");
158 groupLifeLines = doc.createElementNS(SVG_NAMESPACE, "g");
159 groupLifeLines.setAttributeNS(null, "id", "Lifelines");
160 groupActivationBoxes = doc.createElementNS(SVG_NAMESPACE, "g");
161 groupActivationBoxes.setAttributeNS(null, "id", "ActivationBoxes");
162 Element root = doc.getDocumentElement();
163 root.appendChild(groupHeaders);
164 root.appendChild(groupLifeLines);
165 root.appendChild(groupActivationBoxes);
166 root.appendChild(groupCalls);
167 }
168
169 private void addHeaders(Document doc) {
170 int x = HEADER_LEFT_MARGIN;
171 int y = HEADER_TOP_MARGIN + startRow * ROW_HEIGHT;
172 int column = 0;
173 for (MockObject object : objectMap.listView()) {
174 String name = object.getName();
175 if (name.lastIndexOf(".") >= 0) {
176 name = name.substring(name.lastIndexOf(".") + 1);
177 }
178 x += COLUMN_WIDTH;
179 Element text = doc.createElementNS(SVG_NAMESPACE, "text");
180 text.setAttributeNS(null, "x", Integer.toString(x));
181 text.setAttributeNS(null, "y", Integer.toString(getHeaderY(y,
182 column++)));
183 text.appendChild(doc.createTextNode(XMLUtil.makeXMLSafe(name)));
184 groupHeaders.appendChild(text);
185 }
186 if (x > maxX) {
187 maxX = x;
188 }
189 }
190
191 private int getHeaderY(int y, int column) {
192 int headerY;
193 if (column % 2 == 0) {
194 headerY = y;
195 } else {
196 headerY = y - HEADER_VERTICAL_SHIFT;
197 }
198 return headerY;
199 }
200
201 private void addCalls(Document doc, Activation activation) {
202 int firstRow = row;
203 addCall(doc, activation);
204 for (Activation nestedActivation : activation.getNestedActivations()) {
205 addCalls(doc, nestedActivation);
206 }
207 int lastRow = row;
208 addActivationBox(doc, activation, firstRow, lastRow);
209 }
210
211 private void addCall(Document doc, Activation activation) {
212 MockObject sender = null;
213 if (activation.getParent() != null) {
214 sender = objectMap.get(activation.getParent().getClassName());
215 }
216 MockObject receiver = objectMap.get(activation.getClassName());
217 String methodName = activation.getMethod().name();
218 if (activation.getNumRepetitions() > 1) {
219 methodName =
220 "*[" + activation.getNumRepetitions() + "] " + methodName;
221 }
222 if (sender == receiver) {
223 addSelfArrow(doc, sender, methodName);
224 } else {
225 addArrow(doc, sender, receiver, methodName);
226 }
227 }
228
229 private void addSelfArrow(Document doc, MockObject sender, String methodName) {
230 int x1 =
231 LIFE_LINE_LEFT_MARGIN + ACTIVATION_BOX_WIDTH / 2 +
232 sender.getColumn() * COLUMN_WIDTH;
233 int y1 = ARROW_VERTICAL_MARGIN + (row + startRow) * ROW_HEIGHT;
234 int x2 = x1 + SELF_ARROW_WIDTH;
235 int y2 = y1;
236 int x3 = x2;
237 int y3 = y2 + SELF_ARROW_HEIGHT;
238 int x4 = x1;
239 int y4 = y3;
240 String points =
241 x1 + "," + y1 + "," + x2 + "," + y2 + "," + x3 + "," + y3 +
242 "," + x4 + "," + y4;
243 Element line = doc.createElementNS(SVG_NAMESPACE, "polyline");
244 line.setAttributeNS(null, "fill", "none");
245 line.setAttributeNS(null, "stroke", "black");
246 line.setAttributeNS(null, "points", points);
247 groupCalls.appendChild(line);
248
249 addMethodName(doc, methodName, x1, y1, x2, y2);
250 addArrowHead(doc, x3, y3, x4, y4);
251
252 row += 2;
253 }
254
255 private void addArrow(Document doc, MockObject sender, MockObject receiver,
256 String methodName) {
257 int x1 =
258 (sender == null ? INDENT_FIRST_ARROW : LIFE_LINE_LEFT_MARGIN +
259 ACTIVATION_BOX_WIDTH / 2 + sender.getColumn() *
260 COLUMN_WIDTH);
261 int y1 = ARROW_VERTICAL_MARGIN + (row + startRow) * ROW_HEIGHT;
262 int x2 =
263 LIFE_LINE_LEFT_MARGIN - ACTIVATION_BOX_WIDTH / 2 +
264 receiver.getColumn() * COLUMN_WIDTH;
265 int y2 = y1;
266 if (x1 > x2) {
267 x1 -= ACTIVATION_BOX_WIDTH;
268 x2 += ACTIVATION_BOX_WIDTH;
269 }
270 Element line = doc.createElementNS(SVG_NAMESPACE, "line");
271 line.setAttributeNS(null, "stroke", "black");
272 line.setAttributeNS(null, "x1", Integer.toString(x1));
273 line.setAttributeNS(null, "y1", Integer.toString(y1));
274 line.setAttributeNS(null, "x2", Integer.toString(x2));
275 line.setAttributeNS(null, "y2", Integer.toString(y2));
276 groupCalls.appendChild(line);
277
278 addMethodName(doc, methodName, x1, y1, x2, y2);
279 addArrowHead(doc, x1, y1, x2, y2);
280
281 row++;
282 }
283
284 private void addMethodName(Document doc, String methodName, int x1, int y1,
285 int x2, int y2) {
286 Element text = doc.createElementNS(SVG_NAMESPACE, "text");
287 int x;
288 if (x1 < x2) {
289 x = x1 + ACTIVATION_BOX_WIDTH;
290 } else {
291 x = x1 - COLUMN_WIDTH + ACTIVATION_BOX_WIDTH * 2;
292 }
293 int y = y1 - METHOD_NAME_BOTTOM_MARGIN;
294 text.setAttributeNS(null, "x", Integer.toString(x));
295 text.setAttributeNS(null, "y", Integer.toString(y));
296 text.appendChild(doc.createTextNode(XMLUtil.makeXMLSafe(methodName)));
297 groupCalls.appendChild(text);
298 }
299
300 private void addArrowHead(Document doc, int x1, int y1, int x2, int y2) {
301 Element line = doc.createElementNS(SVG_NAMESPACE, "polyline");
302 line.setAttributeNS(null, "fill", "none");
303 line.setAttributeNS(null, "stroke", "black");
304 String points;
305 if (x1 < x2) {
306 points =
307 (x2 - ARROW_HEAD_WIDTH) + "," + (y2 - ARROW_HEAD_HEIGHT) +
308 "," + x2 + "," + y2 + "," +
309 (x2 - ARROW_HEAD_WIDTH) + "," +
310 (y2 + ARROW_HEAD_HEIGHT);
311 } else {
312 points =
313 (x2 + ARROW_HEAD_WIDTH) + "," + (y2 - ARROW_HEAD_HEIGHT) +
314 "," + x2 + "," + y2 + "," +
315 (x2 + ARROW_HEAD_WIDTH) + "," +
316 (y2 + ARROW_HEAD_HEIGHT);
317 }
318 line.setAttributeNS(null, "points", points);
319 groupCalls.appendChild(line);
320 }
321
322 private void addActivationBox(Document doc, Activation activation,
323 int firstRow, int lastRow) {
324 MockObject sender = null;
325 if (activation.getParent() != null) {
326 sender = objectMap.get(activation.getParent().getClassName());
327 }
328 MockObject receiver = objectMap.get(activation.getClassName());
329 if (sender != receiver) {
330 int x =
331 LIFE_LINE_LEFT_MARGIN - ACTIVATION_BOX_WIDTH / 2 +
332 receiver.getColumn() * COLUMN_WIDTH;
333 int y =
334 LIFE_LINE_TOP_MARGIN + ACTIVATION_BOX_TOP_MARGIN +
335 (startRow + firstRow) * ROW_HEIGHT;
336 int width = ACTIVATION_BOX_WIDTH;
337 int height =
338 (lastRow - firstRow) * ROW_HEIGHT -
339 ACTIVATION_BOX_BOTTOM_MARGIN;
340 Element rect = doc.createElementNS(SVG_NAMESPACE, "rect");
341 rect.setAttributeNS(null, "fill", "white");
342 rect.setAttributeNS(null, "stroke", "gray");
343 rect.setAttributeNS(null, "x", Integer.toString(x));
344 rect.setAttributeNS(null, "y", Integer.toString(y));
345 rect.setAttributeNS(null, "width", Integer.toString(width));
346 rect.setAttributeNS(null, "height", Integer.toString(height));
347 groupActivationBoxes.appendChild(rect);
348 }
349 }
350
351 private void addLifelines(Document doc) {
352 for (int col = 0; col < objectMap.listView().size(); col++) {
353 int x1 = LIFE_LINE_LEFT_MARGIN + col * COLUMN_WIDTH;
354 int y1 = LIFE_LINE_TOP_MARGIN + startRow * ROW_HEIGHT;
355 int x2 = x1;
356 int y2 = LIFE_LINE_EXTRA_HEIGHT + (row + startRow) * ROW_HEIGHT;
357 Element line = doc.createElementNS(SVG_NAMESPACE, "line");
358 line.setAttributeNS(null, "stroke", "gray");
359 line.setAttributeNS(null, "stroke-dasharray", "10,5");
360 line.setAttributeNS(null, "x1", Integer.toString(x1));
361 line.setAttributeNS(null, "y1", Integer.toString(y1));
362 line.setAttributeNS(null, "x2", Integer.toString(x2));
363 line.setAttributeNS(null, "y2", Integer.toString(y2));
364 groupLifeLines.appendChild(line);
365 maxY = y2;
366 }
367 }
368 }