View Javadoc

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.argouml;
24  
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  
31  import javax.xml.parsers.DocumentBuilder;
32  import javax.xml.parsers.DocumentBuilderFactory;
33  import javax.xml.parsers.ParserConfigurationException;
34  
35  import org.w3c.dom.DOMImplementation;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.DocumentType;
38  import org.w3c.dom.Element;
39  
40  import ru.novosoft.uml.behavior.common_behavior.MAction;
41  import ru.novosoft.uml.behavior.common_behavior.MLink;
42  import ru.novosoft.uml.behavior.common_behavior.MObject;
43  import ru.novosoft.uml.behavior.common_behavior.MStimulus;
44  import ru.novosoft.uml.foundation.core.MClass;
45  import ru.novosoft.uml.foundation.core.MClassifier;
46  import ru.novosoft.uml.foundation.core.MNamespace;
47  
48  import th.co.edge.jseq.Activation;
49  import th.co.edge.jseq.MockObject;
50  import th.co.edge.jseq.MockObjectMap;
51  import th.co.edge.jseq.argouml.pgml.Fig;
52  import th.co.edge.jseq.argouml.pgml.FigLink;
53  import th.co.edge.jseq.argouml.pgml.FigObject;
54  import th.co.edge.jseq.argouml.pgml.FigStimulus;
55  
56  /**
57   * A <code>SequenceDiagram</code> represents a sequence diagram as a <a
58   * href="http://en.wikipedia.org/wiki/PGML" target="new">PGML</a> document.
59   */
60  public class SequenceDiagram {
61      private static final String PGML_PUBLIC_ID = null;
62      private static final String PGML_SYSTEM_ID = "pgml.dtd";
63      private static final String PGML_NAMESPACE = null;
64  
65      private static final String UML_SEQUENCE_DIAGRAM_CLASS =
66              "org.argouml.uml.diagram.sequence.ui.UMLSequenceDiagram";
67  
68      private static final int TOP_MARGIN = 10;
69      private static final int LEFT_MARGIN = 10;
70      private static final int COLUMN_WIDTH = 135;
71  
72      private MNamespace namespace;
73      private MockObjectMap mockObjectMap;
74      private DocumentBuilder builder;
75  
76      private Map<MObject, FigObject> figObjectMap =
77              new HashMap<MObject, FigObject>();
78      private List<Fig> figObjects = new LinkedList<Fig>();
79      private List<Fig> figLinks = new LinkedList<Fig>();
80      private List<Fig> figStimuli = new LinkedList<Fig>();
81      private int nextFigNumber = 0;
82      private int nextPortNumber = 0;
83  
84      /**
85       * Creates a new <code>SequenceDiagram</code> depicting a given root
86       * activation.
87       *
88       * @param namespace
89       *            only used to generate the description element in the PGML file
90       * @param activation
91       *            the root activation to depict as a sequence diagram
92       *
93       * @throws ParserConfigurationException
94       *             if there is some serious error in the XML configuration
95       *             (should normally not occur)
96       */
97      public SequenceDiagram(MNamespace namespace, Activation activation)
98              throws ParserConfigurationException {
99          this.namespace = namespace;
100         this.mockObjectMap = MockObjectMap.addAll(activation);
101 
102         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
103         this.builder = factory.newDocumentBuilder();
104     }
105 
106     /**
107      * Returns this sequence diagram as a PGML XML <code>Document</code>.
108      *
109      * @return this sequence diagram as a PGML XML <code>Document</code>
110      */
111     public Document getDocument() {
112         DOMImplementation impl = builder.getDOMImplementation();
113         DocumentType docType =
114                 impl.createDocumentType("pgml", PGML_PUBLIC_ID, PGML_SYSTEM_ID);
115         Document doc = impl.createDocument(PGML_NAMESPACE, "pgml", docType);
116         Element root = doc.getDocumentElement();
117         root.setAttribute("description", getRootDescription());
118         addFigs(doc, figObjects);
119         addFigs(doc, figStimuli);
120         addFigs(doc, figLinks);
121         return doc;
122     }
123 
124     private void addFigs(Document doc, List<Fig> figs) {
125         for (Fig fig : figs) {
126             doc.getDocumentElement().appendChild(fig.getXML(doc));
127         }
128     }
129 
130     private String getRootDescription() {
131         return UML_SEQUENCE_DIAGRAM_CLASS + "|" + namespace.getUUID();
132     }
133 
134     /**
135      * Adds a life-line to this diagram, represented by an <code>MObject</code>.
136      *
137      * @param object
138      *            the <code>MObject</code> to add
139      */
140     public void addObject(MObject object) {
141         FigObject figObject = createEmptyFigObject(object);
142         figObjects.add(figObject);
143         figObjectMap.put(object, figObject);
144     }
145 
146     /**
147      * Adds an arrow representing a method call to this diagram, from a given
148      * sender to a given receiver represented by activation boxes in the
149      * resulting diagram.
150      *
151      * @param sender
152      *            the <code>MObject</code> representing the caller
153      *
154      * @param receiver
155      *            the <code>MObject</code> representing the callee
156      *
157      * @param link
158      */
159     @SuppressWarnings("unchecked")
160     public void addCall(MObject sender, MObject receiver, MLink link) {
161         FigObject figSender = figObjectMap.get(sender);
162         FigObject figReceiver = figObjectMap.get(receiver);
163 
164         String sourcePortFig = figSender.addPort(nextPortNumber);
165         if (!figSender.isActive()) {
166             activate(sender);
167         }
168         String destPortFig = figReceiver.addPort(nextPortNumber);
169         activate(receiver);
170         nextPortNumber++;
171         String sourceFigNode = figSender.getName();
172         String destFigNode = figReceiver.getName();
173 
174         for (Iterator i = link.getStimuli().iterator(); i.hasNext();) {
175             MStimulus stimulus = (MStimulus) i.next();
176             MAction action = stimulus.getDispatchAction();
177             FigStimulus figStimulus =
178                     createFigStimulus(stimulus, action.getName());
179             figStimuli.add(figStimulus);
180         }
181         FigLink figLink =
182                 createFigLink(link, sourcePortFig, destPortFig, sourceFigNode,
183                         destFigNode);
184         figLinks.add(figLink);
185     }
186 
187     /**
188      * Makes the given <code>MObject</code>, represented as a life-line in
189      * the sequence diagram, active, that is, executing a method. This will be
190      * represented as an activation box for the given <code>MObject</code>.
191      *
192      * @param object
193      *            the <code>MObject</code> to activate
194      */
195     private void activate(MObject object) {
196         FigObject figObject = figObjectMap.get(object);
197         figObject.activate(nextPortNumber);
198     }
199 
200     /**
201      * Deactivates the given <code>MObject</code>, that is, returns from a
202      * method call. The activation box currently associated with this object
203      * will be ended.
204      *
205      * @param object
206      *            the <code>MObject</code> to deactivate
207      */
208     public void deactivate(MObject object) {
209         FigObject figObject = figObjectMap.get(object);
210         figObject.deactivate(nextPortNumber - 1);
211     }
212 
213     private FigObject createEmptyFigObject(MObject object) {
214         int column = getColumn(object);
215         FigObject fig =
216                 new FigObject(getNextFigName(), getName(object), object
217                         .getUUID(), LEFT_MARGIN, TOP_MARGIN + column *
218                         COLUMN_WIDTH);
219         return fig;
220     }
221 
222     private FigStimulus createFigStimulus(MStimulus stimulus, String name) {
223         FigStimulus fig =
224                 new FigStimulus(getNextFigName(), name, stimulus.getUUID());
225         return fig;
226     }
227 
228     private FigLink createFigLink(MLink link, String sourcePortFig,
229             String destPortFig, String sourceFigNode, String destFigNode) {
230         FigLink fig =
231                 new FigLink(getNextFigName(), link.getUUID(), LEFT_MARGIN,
232                         TOP_MARGIN, sourcePortFig, destPortFig, sourceFigNode,
233                         destFigNode);
234         return fig;
235     }
236 
237     private int getColumn(MObject object) {
238         MClass cls = getClass(object);
239         MockObject mockObject = mockObjectMap.get(cls.getName());
240         if (mockObject == null) {
241             throw new IllegalArgumentException("Object not found " + object);
242         }
243         return mockObject.getColumn();
244     }
245 
246     private String getName(MObject object) {
247         MClass cls = getClass(object);
248         return object.getName() + " : " + cls.getName();
249     }
250 
251     // TODO: Move SuppressWarnings annotations to inner-most scope possible.
252     @SuppressWarnings("unchecked")
253     private MClass getClass(MObject object) {
254         MClass cls = null;
255         for (Iterator i = object.getClassifiers().iterator(); i.hasNext();) {
256             MClassifier classifier = (MClassifier) i.next();
257             if (classifier instanceof MClass) {
258                 cls = (MClass) classifier;
259                 break;
260             }
261         }
262         if (cls == null) {
263             throw new IllegalArgumentException("No class found for " + object);
264         }
265         return cls;
266     }
267 
268     private String getNextFigName() {
269         return "Fig" + nextFigNumber++;
270     }
271 }