Coverage Report - th.co.edge.jseq.svg.SVGGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
SVGGenerator
0%
0/167
0%
0/32
0
 
 1  
 /*
 2  
  * Copyright (c) 2003-2008, by Henrik Arro and Contributors
 3  
  *
 4  
  * This file is part of JSeq, a tool to automatically create
 5  
  * sequence diagrams by tracing program execution.
 6  
  *
 7  
  * See <http://jseq.sourceforge.net> for more information.
 8  
  *
 9  
  * JSeq is free software: you can redistribute it and/or modify
 10  
  * it under the terms of the GNU Lesser General Public License as
 11  
  * published by the Free Software Foundation, either version 3 of
 12  
  * the License, or (at your option) any later version.
 13  
  *
 14  
  * JSeq is distributed in the hope that it will be useful,
 15  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 17  
  * GNU Lesser General Public License for more details.
 18  
  *
 19  
  * You should have received a copy of the GNU Lesser General Public License
 20  
  * along with JSeq. If not, see <http://www.gnu.org/licenses/>.
 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  
  * A <code>SVGGenerator</code> is used to create a sequence diagram in <a
 44  
  * href="http://www.w3.org/Graphics/SVG/">SVG</a> format.
 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  
      * Creates a new <code>SVGGenerator</code>.
 88  
      *
 89  
      * @throws ParserConfigurationException
 90  
      *             if there is an error in the XML parser configuration (should
 91  
      *             normally not occur)
 92  
      */
 93  0
     public SVGGenerator() throws ParserConfigurationException {
 94  0
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 95  0
         factory.setNamespaceAware(true);
 96  0
         this.builder = factory.newDocumentBuilder();
 97  0
     }
 98  
 
 99  
     /**
 100  
      * Returns a new SVG sequence diagram based on the given
 101  
      * <code>ActivationList</code>, representing the root activations.
 102  
      *
 103  
      * @param activationList
 104  
      *            the root activations to use to generate the diagram
 105  
      *
 106  
      * @return an SVG <code>TextDiagram</code> that can be written to file as
 107  
      *         XML
 108  
      */
 109  
     public Diagram generate(ActivationList activationList) {
 110  0
         Document doc = createSVGDocument(activationList);
 111  0
         return new TextDiagram(XMLUtil.toString(doc));
 112  
     }
 113  
 
 114  
     /**
 115  
      * Returns a new XML <code>Document</code> representing an
 116  
      * <code>ActivationList</code> as an SVG document.
 117  
      *
 118  
      * @param activationList
 119  
      *            the root activations to use to generate this diagram
 120  
      *
 121  
      * @return an XML <code>Document</code> representing
 122  
      *         <code>activationList</code> as an SVG document
 123  
      */
 124  
     public Document createSVGDocument(ActivationList activationList) {
 125  0
         DOMImplementation impl = builder.getDOMImplementation();
 126  0
         DocumentType docType =
 127  
                 impl.createDocumentType("svg", SVG_1_1_PUBLIC_ID,
 128  
                         SVG_1_1_SYSTEM_ID);
 129  0
         Document doc = impl.createDocument(SVG_NAMESPACE, "svg", docType);
 130  0
         Element root = doc.getDocumentElement();
 131  0
         root.setAttribute("xmlns", SVG_NAMESPACE);
 132  0
         this.startRow = 0;
 133  0
         for (Activation activation : activationList) {
 134  0
             fillSVGDocument(doc, activation);
 135  0
             this.startRow += row + NUM_ROWS_BETWEEN_DIAGRAMS;
 136  
         }
 137  0
         root.setAttributeNS(null, "width", Integer.toString(maxX +
 138  
                 EXTRA_DIAGRAM_WIDTH));
 139  0
         root.setAttributeNS(null, "height", Integer.toString(maxY +
 140  
                 EXTRA_DIAGRAM_HEIGHT));
 141  0
         return doc;
 142  
     }
 143  
 
 144  
     private void fillSVGDocument(Document doc, Activation activation) {
 145  0
         this.objectMap = MockObjectMap.addAll(activation);
 146  0
         this.row = 0;
 147  0
         createGroups(doc);
 148  0
         addHeaders(doc);
 149  0
         addCalls(doc, activation);
 150  0
         addLifelines(doc);
 151  0
     }
 152  
 
 153  
     private void createGroups(Document doc) {
 154  0
         groupHeaders = doc.createElementNS(SVG_NAMESPACE, "g");
 155  0
         groupHeaders.setAttributeNS(null, "id", "Headers");
 156  0
         groupCalls = doc.createElementNS(SVG_NAMESPACE, "g");
 157  0
         groupCalls.setAttributeNS(null, "id", "Calls");
 158  0
         groupLifeLines = doc.createElementNS(SVG_NAMESPACE, "g");
 159  0
         groupLifeLines.setAttributeNS(null, "id", "Lifelines");
 160  0
         groupActivationBoxes = doc.createElementNS(SVG_NAMESPACE, "g");
 161  0
         groupActivationBoxes.setAttributeNS(null, "id", "ActivationBoxes");
 162  0
         Element root = doc.getDocumentElement();
 163  0
         root.appendChild(groupHeaders);
 164  0
         root.appendChild(groupLifeLines);
 165  0
         root.appendChild(groupActivationBoxes);
 166  0
         root.appendChild(groupCalls);
 167  0
     }
 168  
 
 169  
     private void addHeaders(Document doc) {
 170  0
         int x = HEADER_LEFT_MARGIN;
 171  0
         int y = HEADER_TOP_MARGIN + startRow * ROW_HEIGHT;
 172  0
         int column = 0;
 173  0
         for (MockObject object : objectMap.listView()) {
 174  0
             String name = object.getName();
 175  0
             if (name.lastIndexOf(".") >= 0) {
 176  0
                 name = name.substring(name.lastIndexOf(".") + 1);
 177  
             }
 178  0
             x += COLUMN_WIDTH;
 179  0
             Element text = doc.createElementNS(SVG_NAMESPACE, "text");
 180  0
             text.setAttributeNS(null, "x", Integer.toString(x));
 181  0
             text.setAttributeNS(null, "y", Integer.toString(getHeaderY(y,
 182  
                     column++)));
 183  0
             text.appendChild(doc.createTextNode(XMLUtil.makeXMLSafe(name)));
 184  0
             groupHeaders.appendChild(text);
 185  0
         }
 186  0
         if (x > maxX) {
 187  0
             maxX = x;
 188  
         }
 189  0
     }
 190  
 
 191  
     private int getHeaderY(int y, int column) {
 192  
         int headerY;
 193  0
         if (column % 2 == 0) {
 194  0
             headerY = y;
 195  
         } else {
 196  0
             headerY = y - HEADER_VERTICAL_SHIFT;
 197  
         }
 198  0
         return headerY;
 199  
     }
 200  
 
 201  
     private void addCalls(Document doc, Activation activation) {
 202  0
         int firstRow = row;
 203  0
         addCall(doc, activation);
 204  0
         for (Activation nestedActivation : activation.getNestedActivations()) {
 205  0
             addCalls(doc, nestedActivation);
 206  
         }
 207  0
         int lastRow = row;
 208  0
         addActivationBox(doc, activation, firstRow, lastRow);
 209  0
     }
 210  
 
 211  
     private void addCall(Document doc, Activation activation) {
 212  0
         MockObject sender = null;
 213  0
         if (activation.getParent() != null) {
 214  0
             sender = objectMap.get(activation.getParent().getClassName());
 215  
         }
 216  0
         MockObject receiver = objectMap.get(activation.getClassName());
 217  0
         String methodName = activation.getMethod().name();
 218  0
         if (activation.getNumRepetitions() > 1) {
 219  0
             methodName =
 220  
                     "*[" + activation.getNumRepetitions() + "] " + methodName;
 221  
         }
 222  0
         if (sender == receiver) {
 223  0
             addSelfArrow(doc, sender, methodName);
 224  
         } else {
 225  0
             addArrow(doc, sender, receiver, methodName);
 226  
         }
 227  0
     }
 228  
 
 229  
     private void addSelfArrow(Document doc, MockObject sender, String methodName) {
 230  0
         int x1 =
 231  
                 LIFE_LINE_LEFT_MARGIN + ACTIVATION_BOX_WIDTH / 2 +
 232  
                         sender.getColumn() * COLUMN_WIDTH;
 233  0
         int y1 = ARROW_VERTICAL_MARGIN + (row + startRow) * ROW_HEIGHT;
 234  0
         int x2 = x1 + SELF_ARROW_WIDTH;
 235  0
         int y2 = y1;
 236  0
         int x3 = x2;
 237  0
         int y3 = y2 + SELF_ARROW_HEIGHT;
 238  0
         int x4 = x1;
 239  0
         int y4 = y3;
 240  0
         String points =
 241  
                 x1 + "," + y1 + "," + x2 + "," + y2 + "," + x3 + "," + y3 +
 242  
                         "," + x4 + "," + y4;
 243  0
         Element line = doc.createElementNS(SVG_NAMESPACE, "polyline");
 244  0
         line.setAttributeNS(null, "fill", "none");
 245  0
         line.setAttributeNS(null, "stroke", "black");
 246  0
         line.setAttributeNS(null, "points", points);
 247  0
         groupCalls.appendChild(line);
 248  
 
 249  0
         addMethodName(doc, methodName, x1, y1, x2, y2);
 250  0
         addArrowHead(doc, x3, y3, x4, y4);
 251  
 
 252  0
         row += 2;
 253  0
     }
 254  
 
 255  
     private void addArrow(Document doc, MockObject sender, MockObject receiver,
 256  
             String methodName) {
 257  0
         int x1 =
 258  
                 (sender == null ? INDENT_FIRST_ARROW : LIFE_LINE_LEFT_MARGIN +
 259  
                         ACTIVATION_BOX_WIDTH / 2 + sender.getColumn() *
 260  
                         COLUMN_WIDTH);
 261  0
         int y1 = ARROW_VERTICAL_MARGIN + (row + startRow) * ROW_HEIGHT;
 262  0
         int x2 =
 263  
                 LIFE_LINE_LEFT_MARGIN - ACTIVATION_BOX_WIDTH / 2 +
 264  
                         receiver.getColumn() * COLUMN_WIDTH;
 265  0
         int y2 = y1;
 266  0
         if (x1 > x2) {
 267  0
             x1 -= ACTIVATION_BOX_WIDTH;
 268  0
             x2 += ACTIVATION_BOX_WIDTH;
 269  
         }
 270  0
         Element line = doc.createElementNS(SVG_NAMESPACE, "line");
 271  0
         line.setAttributeNS(null, "stroke", "black");
 272  0
         line.setAttributeNS(null, "x1", Integer.toString(x1));
 273  0
         line.setAttributeNS(null, "y1", Integer.toString(y1));
 274  0
         line.setAttributeNS(null, "x2", Integer.toString(x2));
 275  0
         line.setAttributeNS(null, "y2", Integer.toString(y2));
 276  0
         groupCalls.appendChild(line);
 277  
 
 278  0
         addMethodName(doc, methodName, x1, y1, x2, y2);
 279  0
         addArrowHead(doc, x1, y1, x2, y2);
 280  
 
 281  0
         row++;
 282  0
     }
 283  
 
 284  
     private void addMethodName(Document doc, String methodName, int x1, int y1,
 285  
             int x2, int y2) {
 286  0
         Element text = doc.createElementNS(SVG_NAMESPACE, "text");
 287  
         int x;
 288  0
         if (x1 < x2) {
 289  0
             x = x1 + ACTIVATION_BOX_WIDTH;
 290  
         } else {
 291  0
             x = x1 - COLUMN_WIDTH + ACTIVATION_BOX_WIDTH * 2;
 292  
         }
 293  0
         int y = y1 - METHOD_NAME_BOTTOM_MARGIN;
 294  0
         text.setAttributeNS(null, "x", Integer.toString(x));
 295  0
         text.setAttributeNS(null, "y", Integer.toString(y));
 296  0
         text.appendChild(doc.createTextNode(XMLUtil.makeXMLSafe(methodName)));
 297  0
         groupCalls.appendChild(text);
 298  0
     }
 299  
 
 300  
     private void addArrowHead(Document doc, int x1, int y1, int x2, int y2) {
 301  0
         Element line = doc.createElementNS(SVG_NAMESPACE, "polyline");
 302  0
         line.setAttributeNS(null, "fill", "none");
 303  0
         line.setAttributeNS(null, "stroke", "black");
 304  
         String points;
 305  0
         if (x1 < x2) {
 306  0
             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  0
             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  0
         line.setAttributeNS(null, "points", points);
 319  0
         groupCalls.appendChild(line);
 320  0
     }
 321  
 
 322  
     private void addActivationBox(Document doc, Activation activation,
 323  
             int firstRow, int lastRow) {
 324  0
         MockObject sender = null;
 325  0
         if (activation.getParent() != null) {
 326  0
             sender = objectMap.get(activation.getParent().getClassName());
 327  
         }
 328  0
         MockObject receiver = objectMap.get(activation.getClassName());
 329  0
         if (sender != receiver) {
 330  0
             int x =
 331  
                     LIFE_LINE_LEFT_MARGIN - ACTIVATION_BOX_WIDTH / 2 +
 332  
                             receiver.getColumn() * COLUMN_WIDTH;
 333  0
             int y =
 334  
                     LIFE_LINE_TOP_MARGIN + ACTIVATION_BOX_TOP_MARGIN +
 335  
                             (startRow + firstRow) * ROW_HEIGHT;
 336  0
             int width = ACTIVATION_BOX_WIDTH;
 337  0
             int height =
 338  
                     (lastRow - firstRow) * ROW_HEIGHT -
 339  
                             ACTIVATION_BOX_BOTTOM_MARGIN;
 340  0
             Element rect = doc.createElementNS(SVG_NAMESPACE, "rect");
 341  0
             rect.setAttributeNS(null, "fill", "white");
 342  0
             rect.setAttributeNS(null, "stroke", "gray");
 343  0
             rect.setAttributeNS(null, "x", Integer.toString(x));
 344  0
             rect.setAttributeNS(null, "y", Integer.toString(y));
 345  0
             rect.setAttributeNS(null, "width", Integer.toString(width));
 346  0
             rect.setAttributeNS(null, "height", Integer.toString(height));
 347  0
             groupActivationBoxes.appendChild(rect);
 348  
         }
 349  0
     }
 350  
 
 351  
     private void addLifelines(Document doc) {
 352  0
         for (int col = 0; col < objectMap.listView().size(); col++) {
 353  0
             int x1 = LIFE_LINE_LEFT_MARGIN + col * COLUMN_WIDTH;
 354  0
             int y1 = LIFE_LINE_TOP_MARGIN + startRow * ROW_HEIGHT;
 355  0
             int x2 = x1;
 356  0
             int y2 = LIFE_LINE_EXTRA_HEIGHT + (row + startRow) * ROW_HEIGHT;
 357  0
             Element line = doc.createElementNS(SVG_NAMESPACE, "line");
 358  0
             line.setAttributeNS(null, "stroke", "gray");
 359  0
             line.setAttributeNS(null, "stroke-dasharray", "10,5");
 360  0
             line.setAttributeNS(null, "x1", Integer.toString(x1));
 361  0
             line.setAttributeNS(null, "y1", Integer.toString(y1));
 362  0
             line.setAttributeNS(null, "x2", Integer.toString(x2));
 363  0
             line.setAttributeNS(null, "y2", Integer.toString(y2));
 364  0
             groupLifeLines.appendChild(line);
 365  0
             maxY = y2;
 366  
         }
 367  0
     }
 368  
 }