Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PGMLStackParser |
|
| 5.478260869565218;5.478 | ||||
PGMLStackParser$EdgeData |
|
| 5.478260869565218;5.478 |
1 | /* $Id: PGMLStackParser.java 18024 2010-02-16 21:25:10Z bobtarling $ | |
2 | ***************************************************************************** | |
3 | * Copyright (c) 2009 Contributors - see below | |
4 | * All rights reserved. This program and the accompanying materials | |
5 | * are made available under the terms of the Eclipse Public License v1.0 | |
6 | * which accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Michiel Van Der Wulp | |
11 | * Bob Tarling | |
12 | * Thomas Neustupny | |
13 | ***************************************************************************** | |
14 | * | |
15 | * Some portions of this file was previously release using the BSD License: | |
16 | */ | |
17 | // Copyright (c) 2005-2009 The Regents of the University of California. All | |
18 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
19 | // software and its documentation without fee, and without a written | |
20 | // agreement is hereby granted, provided that the above copyright notice | |
21 | // and this paragraph appear in all copies. This software program and | |
22 | // documentation are copyrighted by The Regents of the University of | |
23 | // California. The software program and documentation are supplied "AS | |
24 | // IS", without any accompanying services from The Regents. The Regents | |
25 | // does not warrant that the operation of the program will be | |
26 | // uninterrupted or error-free. The end-user understands that the program | |
27 | // was developed for research purposes and is advised not to rely | |
28 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
29 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
30 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
31 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
32 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
33 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
34 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
35 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
36 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
37 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
38 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
39 | ||
40 | package org.argouml.persistence; | |
41 | ||
42 | import java.awt.Rectangle; | |
43 | import java.io.InputStream; | |
44 | import java.lang.reflect.Constructor; | |
45 | import java.lang.reflect.InvocationTargetException; | |
46 | import java.net.URL; | |
47 | import java.util.ArrayList; | |
48 | import java.util.HashMap; | |
49 | import java.util.LinkedHashMap; | |
50 | import java.util.List; | |
51 | import java.util.Map; | |
52 | import java.util.StringTokenizer; | |
53 | ||
54 | import org.apache.log4j.Logger; | |
55 | import org.argouml.model.Model; | |
56 | import org.argouml.uml.diagram.ArgoDiagram; | |
57 | import org.argouml.uml.diagram.DiagramEdgeSettings; | |
58 | import org.argouml.uml.diagram.DiagramSettings; | |
59 | import org.argouml.uml.diagram.PathContainer; | |
60 | import org.argouml.uml.diagram.StereotypeContainer; | |
61 | import org.argouml.uml.diagram.VisibilityContainer; | |
62 | import org.argouml.uml.diagram.ui.FigCompartmentBox; | |
63 | import org.argouml.uml.diagram.ui.FigEdgeModelElement; | |
64 | import org.argouml.uml.diagram.ui.FigEdgePort; | |
65 | import org.tigris.gef.base.Diagram; | |
66 | import org.tigris.gef.persistence.pgml.Container; | |
67 | import org.tigris.gef.persistence.pgml.FigEdgeHandler; | |
68 | import org.tigris.gef.persistence.pgml.FigGroupHandler; | |
69 | import org.tigris.gef.persistence.pgml.HandlerStack; | |
70 | import org.tigris.gef.presentation.Fig; | |
71 | import org.tigris.gef.presentation.FigEdge; | |
72 | import org.tigris.gef.presentation.FigGroup; | |
73 | import org.tigris.gef.presentation.FigNode; | |
74 | import org.xml.sax.Attributes; | |
75 | import org.xml.sax.InputSource; | |
76 | import org.xml.sax.SAXException; | |
77 | import org.xml.sax.helpers.DefaultHandler; | |
78 | ||
79 | // TODO: Move to Diagram subsystem? | |
80 | ||
81 | /** | |
82 | * The PGML Parser. | |
83 | * <p> | |
84 | * | |
85 | * This replaces much of the identically named class from GEF. | |
86 | */ | |
87 | class PGMLStackParser extends org.tigris.gef.persistence.pgml.PGMLStackParser { | |
88 | ||
89 | 0 | private static final Logger LOG = Logger.getLogger(PGMLStackParser.class); |
90 | ||
91 | 0 | private List<EdgeData> figEdges = new ArrayList<EdgeData>(50); |
92 | ||
93 | 0 | private LinkedHashMap<FigEdge, Object> modelElementsByFigEdge = new LinkedHashMap<FigEdge, Object>( |
94 | 50); | |
95 | ||
96 | private DiagramSettings diagramSettings; | |
97 | ||
98 | // TODO: Use stylesheet to convert or wait till we use Fig | |
99 | // factories in diagram subsystem. | |
100 | // What is the last version that used FigNote? | |
101 | private void addTranslations() { | |
102 | 0 | addTranslation("org.argouml.uml.diagram.ui.FigNote", |
103 | "org.argouml.uml.diagram.static_structure.ui.FigComment"); | |
104 | 0 | addTranslation("org.argouml.uml.diagram.static_structure.ui.FigNote", |
105 | "org.argouml.uml.diagram.static_structure.ui.FigComment"); | |
106 | 0 | addTranslation("org.argouml.uml.diagram.state.ui.FigState", |
107 | "org.argouml.uml.diagram.state.ui.FigSimpleState"); | |
108 | 0 | addTranslation("org.argouml.uml.diagram.ui.FigCommentPort", |
109 | "org.argouml.uml.diagram.ui.FigEdgePort"); | |
110 | 0 | addTranslation("org.tigris.gef.presentation.FigText", |
111 | "org.argouml.uml.diagram.ui.ArgoFigText"); | |
112 | 0 | addTranslation("org.tigris.gef.presentation.FigLine", |
113 | "org.argouml.gefext.ArgoFigLine"); | |
114 | 0 | addTranslation("org.tigris.gef.presentation.FigPoly", |
115 | "org.argouml.gefext.ArgoFigPoly"); | |
116 | 0 | addTranslation("org.tigris.gef.presentation.FigCircle", |
117 | "org.argouml.gefext.ArgoFigCircle"); | |
118 | 0 | addTranslation("org.tigris.gef.presentation.FigRect", |
119 | "org.argouml.gefext.ArgoFigRect"); | |
120 | 0 | addTranslation("org.tigris.gef.presentation.FigRRect", |
121 | "org.argouml.gefext.ArgoFigRRect"); | |
122 | 0 | addTranslation( |
123 | "org.argouml.uml.diagram.deployment.ui.FigMNodeInstance", | |
124 | "org.argouml.uml.diagram.deployment.ui.FigNodeInstance"); | |
125 | 0 | addTranslation("org.argouml.uml.diagram.ui.FigRealization", |
126 | "org.argouml.uml.diagram.ui.FigAbstraction"); | |
127 | 0 | } |
128 | ||
129 | /** | |
130 | * Construct a PGML parser with the given HREF/Object map and default | |
131 | * diagram settings. | |
132 | * | |
133 | * @param modelElementsByUuid map of HREF ids to objects used to associate | |
134 | * Figs with their owning model elements | |
135 | * @param defaultSettings default diagram settings to use for newly created | |
136 | * diagram and its contained Figs | |
137 | */ | |
138 | public PGMLStackParser(Map<String, Object> modelElementsByUuid, | |
139 | DiagramSettings defaultSettings) { | |
140 | 0 | super(modelElementsByUuid); |
141 | 0 | addTranslations(); |
142 | // Create a new diagram wide settings block which is backed by | |
143 | // the project-wide defaults that we were passed | |
144 | 0 | diagramSettings = new DiagramSettings(defaultSettings); |
145 | 0 | } |
146 | ||
147 | /* | |
148 | * @see org.tigris.gef.persistence.pgml.HandlerFactory#getHandler( | |
149 | * HandlerStack, Object, String, String, String, Attributes) | |
150 | */ | |
151 | @Override | |
152 | public DefaultHandler getHandler(HandlerStack stack, Object container, | |
153 | String uri, String localname, String qname, Attributes attributes) | |
154 | throws SAXException { | |
155 | ||
156 | 0 | String href = attributes.getValue("href"); |
157 | 0 | Object owner = null; |
158 | ||
159 | 0 | if (href != null) { |
160 | 0 | owner = findOwner(href); |
161 | 0 | if (owner == null) { |
162 | 0 | LOG.warn("Found href of " + href |
163 | + " with no matching element in model"); | |
164 | 0 | return null; |
165 | } | |
166 | } | |
167 | ||
168 | // Ignore non-private elements within FigNode groups | |
169 | 0 | if (container instanceof FigGroupHandler) { |
170 | 0 | FigGroup group = ((FigGroupHandler) container).getFigGroup(); |
171 | 0 | if (group instanceof FigNode && !qname.equals("private")) { |
172 | 0 | return null; |
173 | } | |
174 | } | |
175 | ||
176 | // Handle ItemUID in container contents | |
177 | 0 | if (qname.equals("private") && (container instanceof Container)) { |
178 | 0 | return new PrivateHandler(this, (Container) container); |
179 | } | |
180 | ||
181 | 0 | DefaultHandler handler = super.getHandler(stack, container, uri, |
182 | localname, qname, attributes); | |
183 | ||
184 | 0 | if (handler instanceof FigEdgeHandler) { |
185 | 0 | return new org.argouml.persistence.FigEdgeHandler(this, |
186 | ((FigEdgeHandler) handler).getFigEdge()); | |
187 | } | |
188 | ||
189 | 0 | return handler; |
190 | ||
191 | } | |
192 | ||
193 | /* | |
194 | * @see org.tigris.gef.persistence.pgml.PGMLStackParser#setAttrs( | |
195 | * org.tigris.gef.presentation.Fig, org.xml.sax.Attributes) | |
196 | */ | |
197 | @Override | |
198 | protected final void setAttrs(Fig f, Attributes attrList) | |
199 | throws SAXException { | |
200 | ||
201 | 0 | if (f instanceof FigGroup) { |
202 | 0 | FigGroup group = (FigGroup) f; |
203 | 0 | String clsNameBounds = attrList.getValue("description"); |
204 | 0 | if (clsNameBounds != null) { |
205 | 0 | StringTokenizer st = new StringTokenizer(clsNameBounds, ",;[] "); |
206 | // Discard class name, x y w h | |
207 | 0 | if (st.hasMoreElements()) { |
208 | 0 | st.nextToken(); |
209 | } | |
210 | 0 | if (st.hasMoreElements()) { |
211 | 0 | st.nextToken(); |
212 | } | |
213 | 0 | if (st.hasMoreElements()) { |
214 | 0 | st.nextToken(); |
215 | } | |
216 | 0 | if (st.hasMoreElements()) { |
217 | 0 | st.nextToken(); |
218 | } | |
219 | 0 | if (st.hasMoreElements()) { |
220 | 0 | st.nextToken(); |
221 | } | |
222 | ||
223 | 0 | Map<String, String> attributeMap = interpretStyle(st); |
224 | 0 | setStyleAttributes(group, attributeMap); |
225 | } | |
226 | } | |
227 | ||
228 | // TODO: Attempt to move the following code to GEF | |
229 | ||
230 | 0 | String name = attrList.getValue("name"); |
231 | 0 | if (name != null && !name.equals("")) { |
232 | 0 | registerFig(f, name); |
233 | } | |
234 | ||
235 | 0 | setCommonAttrs(f, attrList); |
236 | ||
237 | 0 | final String href = attrList.getValue("href"); |
238 | 0 | if (href != null && !href.equals("")) { |
239 | 0 | Object modelElement = findOwner(href); |
240 | 0 | if (modelElement == null) { |
241 | 0 | LOG.error("Can't find href of " + href); |
242 | 0 | throw new SAXException("Found href of " + href |
243 | + " with no matching element in model"); | |
244 | } | |
245 | // The owner should always have already been set in the constructor | |
246 | 0 | if (f.getOwner() != modelElement) { |
247 | // Assign nodes immediately but edges later. See issue 4310. | |
248 | 0 | if (f instanceof FigEdge) { |
249 | 0 | modelElementsByFigEdge.put((FigEdge) f, modelElement); |
250 | } else { | |
251 | 0 | f.setOwner(modelElement); |
252 | } | |
253 | } else { | |
254 | 0 | LOG.debug("Ignoring href on " + f.getClass().getName() |
255 | + " as it's already set"); | |
256 | } | |
257 | } | |
258 | 0 | } |
259 | ||
260 | /** | |
261 | * The StringTokenizer is expected to be positioned at the start of a string | |
262 | * of style identifiers in the format name=value;name=value;name=value.... | |
263 | * Each name value pair is interpreted and the Fig configured accordingly. | |
264 | * The value is optional and will default to a value applicable for its | |
265 | * name. The current applicable names are operationsVisible and | |
266 | * attributesVisible and are used to show or hide the compartments within | |
267 | * Class and Interface. The default values are true. | |
268 | * | |
269 | * @param st The StrinkTokenizer positioned at the first style identifier | |
270 | * @return a map of attributes | |
271 | */ | |
272 | private Map<String, String> interpretStyle(StringTokenizer st) { | |
273 | 0 | Map<String, String> map = new HashMap<String, String>(); |
274 | String name; | |
275 | String value; | |
276 | ||
277 | 0 | while (st.hasMoreElements()) { |
278 | 0 | String namevaluepair = st.nextToken(); |
279 | 0 | int equalsPos = namevaluepair.indexOf('='); |
280 | 0 | if (equalsPos < 0) { |
281 | 0 | name = namevaluepair; |
282 | 0 | value = "true"; |
283 | } else { | |
284 | 0 | name = namevaluepair.substring(0, equalsPos); |
285 | 0 | value = namevaluepair.substring(equalsPos + 1); |
286 | } | |
287 | ||
288 | 0 | map.put(name, value); |
289 | 0 | } |
290 | 0 | return map; |
291 | } | |
292 | ||
293 | /** | |
294 | * Set the fig style attributes. | |
295 | * <p> | |
296 | * | |
297 | * TODO: This should move into the render factories as described in issue | |
298 | * 859. | |
299 | * | |
300 | * @param fig the fig to style. | |
301 | * @param attributeMap a map of name value pairs | |
302 | */ | |
303 | private void setStyleAttributes(Fig fig, Map<String, String> attributeMap) { | |
304 | ||
305 | 0 | for (Map.Entry<String, String> entry : attributeMap.entrySet()) { |
306 | 0 | final String name = entry.getKey(); |
307 | 0 | final String value = entry.getValue(); |
308 | ||
309 | 0 | if (fig instanceof FigCompartmentBox) { |
310 | 0 | FigCompartmentBox fcb = (FigCompartmentBox) fig; |
311 | 0 | if ("operationsVisible".equals(name)) { |
312 | 0 | fcb.showCompartment(Model.getMetaTypes().getOperation(), |
313 | value.equalsIgnoreCase("true")); | |
314 | 0 | } else if ("attributesVisible".equals(name)) { |
315 | 0 | fcb.showCompartment(Model.getMetaTypes().getAttribute(), |
316 | value.equalsIgnoreCase("true")); | |
317 | 0 | } else if ("enumerationLiteralsVisible".equals(name)) { |
318 | 0 | fcb.showCompartment(Model.getMetaTypes() |
319 | .getEnumerationLiteral(), value | |
320 | .equalsIgnoreCase("true")); | |
321 | 0 | } else if ("extensionPointVisible".equals(name)) { |
322 | 0 | fcb.showCompartment(Model.getMetaTypes() |
323 | .getExtensionPoint(), value | |
324 | .equalsIgnoreCase("true")); | |
325 | } | |
326 | } | |
327 | 0 | if ("stereotypeVisible".equals(name)) { |
328 | 0 | ((StereotypeContainer) fig).setStereotypeVisible(value |
329 | .equalsIgnoreCase("true")); | |
330 | 0 | } else if ("visibilityVisible".equals(name)) { |
331 | 0 | ((VisibilityContainer) fig).setVisibilityVisible(value |
332 | .equalsIgnoreCase("true")); | |
333 | 0 | } else if ("pathVisible".equals(name)) { |
334 | 0 | ((PathContainer) fig).setPathVisible(value |
335 | .equalsIgnoreCase("true")); | |
336 | } | |
337 | 0 | } |
338 | 0 | } |
339 | ||
340 | /** | |
341 | * Read and parse the input stream to create a new diagram and return it. | |
342 | * | |
343 | * @param is the input stream | |
344 | * @param closeStream true to close the stream when parsing is complete | |
345 | * @return the diagram created as a result of the parse | |
346 | * @throws SAXException | |
347 | */ | |
348 | public ArgoDiagram readArgoDiagram(InputSource is, boolean closeStream) | |
349 | throws SAXException { | |
350 | ||
351 | 0 | InputStream stream = is.getByteStream(); |
352 | 0 | if (stream == null) { |
353 | try { | |
354 | // happens when 'is' comes from a zip file | |
355 | 0 | URL url = new URL(is.getSystemId()); |
356 | 0 | stream = url.openStream(); |
357 | 0 | closeStream = true; |
358 | 0 | } catch (Exception e) { |
359 | // continue with null stream, readDiagram(...) will take care of | |
360 | // it | |
361 | 0 | } |
362 | } | |
363 | 0 | return (ArgoDiagram) readDiagram(stream, closeStream); |
364 | } | |
365 | ||
366 | /** | |
367 | * Read and parse the input stream to create a new diagram and return it. | |
368 | * | |
369 | * @param is the input stream | |
370 | * @param closeStream true to close the stream when parsing is complete | |
371 | * @return the diagram created as a result of the parse | |
372 | * @throws SAXException | |
373 | */ | |
374 | public ArgoDiagram readArgoDiagram(InputStream is, boolean closeStream) | |
375 | throws SAXException { | |
376 | ||
377 | 0 | return (ArgoDiagram) readDiagram(is, closeStream); |
378 | } | |
379 | ||
380 | @Override | |
381 | public Diagram readDiagram(InputStream is, boolean closeStream) | |
382 | throws SAXException { | |
383 | ||
384 | // TODO: we really want to be able replace the initial content handler | |
385 | // which is passed to SAX, but we can't do this without cloning a | |
386 | // whole bunch of code because it's private in the super class. | |
387 | ||
388 | 0 | Diagram d = super.readDiagram(is, closeStream); |
389 | ||
390 | 0 | attachEdges(d); |
391 | ||
392 | 0 | return d; |
393 | } | |
394 | ||
395 | /** | |
396 | * This is called when all nodes and edges have been read and placed on the | |
397 | * diagram. This method then attaches the edges to the correct node, | |
398 | * including the nodes contained within edges allowing edge to edge | |
399 | * connections for comment edges, association classes and dependencies. | |
400 | * | |
401 | * @param d the Diagram | |
402 | */ | |
403 | private void attachEdges(Diagram d) { | |
404 | 0 | for (EdgeData edgeData : figEdges) { |
405 | 0 | final FigEdge edge = edgeData.getFigEdge(); |
406 | ||
407 | 0 | Object modelElement = modelElementsByFigEdge.get(edge); |
408 | 0 | if (modelElement != null) { |
409 | 0 | if (edge.getOwner() == null) { |
410 | 0 | edge.setOwner(modelElement); |
411 | } | |
412 | } | |
413 | 0 | } |
414 | ||
415 | 0 | for (EdgeData edgeData : figEdges) { |
416 | 0 | final FigEdge edge = edgeData.getFigEdge(); |
417 | ||
418 | 0 | Fig sourcePortFig = findFig(edgeData.getSourcePortFigId()); |
419 | 0 | Fig destPortFig = findFig(edgeData.getDestPortFigId()); |
420 | 0 | final FigNode sourceFigNode = getFigNode(edgeData |
421 | .getSourceFigNodeId()); | |
422 | 0 | final FigNode destFigNode = getFigNode(edgeData.getDestFigNodeId()); |
423 | ||
424 | 0 | if (sourceFigNode instanceof FigEdgePort) { |
425 | 0 | sourcePortFig = sourceFigNode; |
426 | } | |
427 | ||
428 | 0 | if (destFigNode instanceof FigEdgePort) { |
429 | 0 | destPortFig = destFigNode; |
430 | } | |
431 | ||
432 | 0 | if (sourcePortFig == null && sourceFigNode != null) { |
433 | 0 | sourcePortFig = getPortFig(sourceFigNode); |
434 | } | |
435 | ||
436 | 0 | if (destPortFig == null && destFigNode != null) { |
437 | 0 | destPortFig = getPortFig(destFigNode); |
438 | } | |
439 | ||
440 | 0 | if (sourcePortFig == null || destPortFig == null |
441 | || sourceFigNode == null || destFigNode == null) { | |
442 | 0 | LOG.error("Can't find nodes for FigEdge: " + edge.getId() + ":" |
443 | + edge.toString()); | |
444 | 0 | edge.removeFromDiagram(); |
445 | } else { | |
446 | 0 | edge.setSourcePortFig(sourcePortFig); |
447 | 0 | edge.setDestPortFig(destPortFig); |
448 | 0 | edge.setSourceFigNode(sourceFigNode); |
449 | 0 | edge.setDestFigNode(destFigNode); |
450 | } | |
451 | 0 | } |
452 | ||
453 | // Once all edges are connected do a compute route on each to make | |
454 | // sure that annotations and the edge port is positioned correctly | |
455 | // Only do this after all edges are connected as compute route | |
456 | // requires all edges to be connected to nodes. | |
457 | // TODO: It would be nice not to have to do this and restore annotation | |
458 | // positions instead. | |
459 | 0 | for (Object edge : d.getLayer().getContentsEdgesOnly()) { |
460 | 0 | FigEdge figEdge = (FigEdge) edge; |
461 | 0 | figEdge.computeRouteImpl(); |
462 | 0 | } |
463 | 0 | } |
464 | ||
465 | // TODO: Move to GEF | |
466 | /** | |
467 | * Store data of a FigEdge together with the id's of nodes to connect to | |
468 | * | |
469 | * @param figEdge The FigEdge | |
470 | * @param sourcePortFigId The id of the source port | |
471 | * @param destPortFigId The id of the destination port | |
472 | * @param sourceFigNodeId The id of the source node | |
473 | * @param destFigNodeId The id of the destination node | |
474 | */ | |
475 | public void addFigEdge(final FigEdge figEdge, final String sourcePortFigId, | |
476 | final String destPortFigId, final String sourceFigNodeId, | |
477 | final String destFigNodeId) { | |
478 | 0 | figEdges.add(new EdgeData(figEdge, sourcePortFigId, destPortFigId, |
479 | sourceFigNodeId, destFigNodeId)); | |
480 | 0 | } |
481 | ||
482 | // TODO: Move to GEF | |
483 | /** | |
484 | * Get the FigNode that the fig id represents. | |
485 | * | |
486 | * @param figId (In the form Figx.y.z) | |
487 | * @return the FigNode with the given id | |
488 | * @throws IllegalStateException if the figId supplied is not of a FigNode | |
489 | */ | |
490 | private FigNode getFigNode(String figId) throws IllegalStateException { | |
491 | 0 | if (figId.contains(".")) { |
492 | // If the id does not look like a top-level Fig then we can assume | |
493 | // that this is an id of a FigEdgePort inside some FigEdge. | |
494 | // So extract the FigEdgePort from the FigEdge and return that as | |
495 | // the FigNode. | |
496 | 0 | figId = figId.substring(0, figId.indexOf('.')); |
497 | 0 | FigEdgeModelElement edge = (FigEdgeModelElement) findFig(figId); |
498 | 0 | if (edge == null) { |
499 | 0 | throw new IllegalStateException("Can't find a FigNode with id " |
500 | + figId); | |
501 | } | |
502 | 0 | edge.makeEdgePort(); |
503 | 0 | return edge.getEdgePort(); |
504 | } else { | |
505 | // If there is no dot then this must be a top level Fig and can be | |
506 | // assumed to be a FigNode. | |
507 | 0 | Fig f = findFig(figId); |
508 | 0 | if (f instanceof FigNode) { |
509 | 0 | return (FigNode) f; |
510 | } else { | |
511 | 0 | LOG.error("FigID " + figId + " is not a node, edge ignored"); |
512 | 0 | return null; |
513 | } | |
514 | } | |
515 | } | |
516 | ||
517 | // TODO: Move to GEF | |
518 | /** | |
519 | * Get the Fig from the FigNode that is the port. | |
520 | * | |
521 | * @param figNode the FigNode | |
522 | * @return the Fig that is the port on the given FigNode | |
523 | */ | |
524 | private Fig getPortFig(FigNode figNode) { | |
525 | 0 | if (figNode instanceof FigEdgePort) { |
526 | // TODO: Can we just do this every time, no need for else - Bob | |
527 | 0 | return figNode; |
528 | } else { | |
529 | 0 | return (Fig) figNode.getPortFigs().get(0); |
530 | } | |
531 | } | |
532 | ||
533 | // TODO: Move to GEF | |
534 | ||
535 | /** | |
536 | * The data from an edge extracted from the PGML before we can guarantee all | |
537 | * the nodes have been constructed. This stores the FigEdge and the id's of | |
538 | * the nodes to connect to later. If the nodes are not known then the ports | |
539 | * are returned instead. | |
540 | */ | |
541 | private class EdgeData { | |
542 | private final FigEdge figEdge; | |
543 | ||
544 | private final String sourcePortFigId; | |
545 | ||
546 | private final String destPortFigId; | |
547 | ||
548 | private final String sourceFigNodeId; | |
549 | ||
550 | private final String destFigNodeId; | |
551 | ||
552 | /** | |
553 | * Constructor | |
554 | * | |
555 | * @param edge The FigEdge | |
556 | * @param sourcePortId The id of the source port | |
557 | * @param destPortId The id of the destination port | |
558 | * @param sourceNodeId The id of the source node | |
559 | * @param destNodeId The id of the destination node | |
560 | */ | |
561 | public EdgeData(FigEdge edge, String sourcePortId, String destPortId, | |
562 | 0 | String sourceNodeId, String destNodeId) { |
563 | 0 | if (sourcePortId == null || destPortId == null) { |
564 | 0 | throw new IllegalArgumentException( |
565 | "source port and dest port must not be null" | |
566 | + " source = " + sourcePortId + " dest = " | |
567 | + destPortId + " figEdge = " + edge); | |
568 | } | |
569 | 0 | this.figEdge = edge; |
570 | 0 | this.sourcePortFigId = sourcePortId; |
571 | 0 | this.destPortFigId = destPortId; |
572 | 0 | this.sourceFigNodeId = sourceNodeId != null ? sourceNodeId |
573 | : sourcePortId; | |
574 | 0 | this.destFigNodeId = destNodeId != null ? destNodeId : destPortId; |
575 | 0 | } |
576 | ||
577 | /** | |
578 | * Get the id of the destination FigNode | |
579 | * | |
580 | * @return the id | |
581 | */ | |
582 | public String getDestFigNodeId() { | |
583 | 0 | return destFigNodeId; |
584 | } | |
585 | ||
586 | /** | |
587 | * Get the id of the destination port | |
588 | * | |
589 | * @return the id | |
590 | */ | |
591 | public String getDestPortFigId() { | |
592 | 0 | return destPortFigId; |
593 | } | |
594 | ||
595 | /** | |
596 | * Get the FigEdge | |
597 | * | |
598 | * @return the FigEdge | |
599 | */ | |
600 | public FigEdge getFigEdge() { | |
601 | 0 | return figEdge; |
602 | } | |
603 | ||
604 | /** | |
605 | * Get the id of the source FigNode | |
606 | * | |
607 | * @return the id | |
608 | */ | |
609 | public String getSourceFigNodeId() { | |
610 | 0 | return sourceFigNodeId; |
611 | } | |
612 | ||
613 | /** | |
614 | * Get the id of the source port | |
615 | * | |
616 | * @return the id | |
617 | */ | |
618 | public String getSourcePortFigId() { | |
619 | 0 | return sourcePortFigId; |
620 | } | |
621 | } | |
622 | ||
623 | /** | |
624 | * Construct a new instance of the named Fig with the owner represented by | |
625 | * the given href and the bounds parsed from the PGML file. We look for | |
626 | * constructors of the form Fig(Object owner, Rectangle bounds, | |
627 | * DiagramSettings settings) which is typically used for subclasses of | |
628 | * FigNodeModelElement, then Fig(Object owner, DiagramSettings settings) | |
629 | * which is used for subclasses of FigEdgeModelElement. | |
630 | * <p> | |
631 | * If we fail to find any of the constructors that we know about, we'll call | |
632 | * GEF's version of this method to see if it can find a constructor. | |
633 | * | |
634 | * @param className fully qualified name of class to instantiate | |
635 | * @param href string representing UUID of owning element | |
636 | * @param bounds position and size of figure | |
637 | * @return | |
638 | * @throws SAXException | |
639 | * @see org.tigris.gef.persistence.pgml.PGMLStackParser#constructFig(java.lang.String, | |
640 | * java.lang.String, java.awt.Rectangle) | |
641 | */ | |
642 | @Override | |
643 | protected Fig constructFig(final String className, final String href, | |
644 | final Rectangle bounds, final Attributes attributes) | |
645 | throws SAXException { | |
646 | ||
647 | 0 | final DiagramSettings diagramSettings = ((ArgoDiagram) getDiagram()) |
648 | .getDiagramSettings(); | |
649 | ||
650 | 0 | Fig f = null; |
651 | try { | |
652 | 0 | Class figClass = Class.forName(className); |
653 | ||
654 | 0 | final Constructor[] constructors = figClass.getConstructors(); |
655 | ||
656 | // We are looking first to match with 3 different constructor | |
657 | // types. We would not expect a Fig to have any mix of these. | |
658 | // Any constructor other than these should be deprecated so we | |
659 | // look for these first. | |
660 | // Fig(DiagramEdgeSettings, DiagramSettings) | |
661 | // Fig(Object, Rectangle, DiagramSettings) | |
662 | // Fig(Rectangle, DiagramSettings) | |
663 | 0 | for (Constructor constructor : constructors) { |
664 | 0 | Class[] parameterTypes = constructor.getParameterTypes(); |
665 | 0 | if (parameterTypes.length == 3 |
666 | && parameterTypes[0].equals(Object.class) | |
667 | && parameterTypes[1].equals(Rectangle.class) | |
668 | && parameterTypes[2].equals(DiagramSettings.class)) { | |
669 | // FigNodeModelElements should match here | |
670 | 0 | final Object parameters[] = new Object[3]; |
671 | 0 | final Object owner = getOwner(className, href); |
672 | 0 | if (owner == null) { |
673 | 0 | return null; |
674 | } | |
675 | 0 | parameters[0] = owner; |
676 | 0 | parameters[1] = bounds; |
677 | 0 | parameters[2] = diagramSettings; |
678 | ||
679 | 0 | constructor.setAccessible(true); |
680 | 0 | f = (Fig) constructor.newInstance(parameters); |
681 | 0 | } else if (parameterTypes.length == 2 |
682 | && parameterTypes[0].equals(DiagramEdgeSettings.class) | |
683 | && parameterTypes[1].equals(DiagramSettings.class)) { | |
684 | // FigEdgeModelElements should match here (they have no | |
685 | // bounds) | |
686 | 0 | final Object parameters[] = new Object[2]; |
687 | 0 | final Object owner = getOwner(className, href); |
688 | 0 | if (owner == null) { |
689 | 0 | return null; |
690 | } | |
691 | ||
692 | 0 | String sourceUuid = attributes.getValue("sourceConnector"); |
693 | 0 | String destinationUuid = attributes |
694 | .getValue("destConnector"); | |
695 | ||
696 | final Object source; | |
697 | final Object destination; | |
698 | 0 | if (sourceUuid != null && destinationUuid != null) { |
699 | 0 | source = findOwner(sourceUuid); |
700 | 0 | destination = findOwner(destinationUuid); |
701 | } else { | |
702 | 0 | source = null; |
703 | 0 | destination = null; |
704 | } | |
705 | ||
706 | 0 | DiagramEdgeSettings settings = new DiagramEdgeSettings( |
707 | owner, source, destination); | |
708 | 0 | parameters[0] = settings; |
709 | 0 | parameters[1] = diagramSettings; |
710 | ||
711 | 0 | constructor.setAccessible(true); |
712 | 0 | f = (Fig) constructor.newInstance(parameters); |
713 | 0 | } else if (parameterTypes.length == 2 |
714 | && parameterTypes[0].equals(Rectangle.class) | |
715 | && parameterTypes[1].equals(DiagramSettings.class)) { | |
716 | // A FigNodeModelElement with no owner should match here | |
717 | // TODO: This is a temporary solution due to FigPool | |
718 | // extending | |
719 | // FigNodeModelElement when in fact it should not do so. | |
720 | 0 | Object parameters[] = new Object[2]; |
721 | 0 | parameters[0] = bounds; |
722 | 0 | parameters[1] = diagramSettings; |
723 | ||
724 | 0 | constructor.setAccessible(true); |
725 | 0 | f = (Fig) constructor.newInstance(parameters); |
726 | } | |
727 | } | |
728 | 0 | if (f == null) { |
729 | // If no Fig was created by the code above then we must go | |
730 | // look for the old style constructor that should have fallen | |
731 | // into disuse by now. | |
732 | // Fig(Object, Rectangle, DiagramSettings) | |
733 | // All of these constructors should have been deprecated | |
734 | // at least and replaced with the new signature. This is | |
735 | // here for paranoia only until all Figs have been reviewed. | |
736 | 0 | for (Constructor constructor : constructors) { |
737 | 0 | Class[] parameterTypes = constructor.getParameterTypes(); |
738 | 0 | if (parameterTypes.length == 2 |
739 | && parameterTypes[0].equals(Object.class) | |
740 | && parameterTypes[1].equals(DiagramSettings.class)) { | |
741 | 0 | Object parameters[] = new Object[2]; |
742 | ||
743 | 0 | final Object owner = getOwner(className, href); |
744 | // currently FigEdgeNote can be passed null | |
745 | // if (owner == null) { | |
746 | // return null; | |
747 | // } | |
748 | 0 | parameters[0] = owner; |
749 | 0 | parameters[1] = diagramSettings; |
750 | ||
751 | 0 | constructor.setAccessible(true); |
752 | 0 | f = (Fig) constructor.newInstance(parameters); |
753 | 0 | LOG.warn("Fig created by old style constructor " |
754 | + f.getClass().getName()); | |
755 | 0 | break; |
756 | } | |
757 | } | |
758 | } | |
759 | 0 | } catch (ClassNotFoundException e) { |
760 | 0 | throw new SAXException(e); |
761 | 0 | } catch (IllegalAccessException e) { |
762 | 0 | throw new SAXException(e); |
763 | 0 | } catch (InstantiationException e) { |
764 | 0 | throw new SAXException(e); |
765 | 0 | } catch (InvocationTargetException e) { |
766 | 0 | throw new SAXException(e); |
767 | 0 | } |
768 | ||
769 | // Fall back to GEF's handling if we couldn't find an appropriate | |
770 | // constructor | |
771 | 0 | if (f == null) { |
772 | 0 | LOG.warn("No ArgoUML constructor found for " + className |
773 | + " falling back to GEF's default constructors"); | |
774 | 0 | f = super.constructFig(className, href, bounds, attributes); |
775 | } | |
776 | ||
777 | 0 | return f; |
778 | } | |
779 | ||
780 | /** | |
781 | * Given the href extracted from the PGML return the model element with that | |
782 | * uuid. | |
783 | * | |
784 | * @param className Used only for logging should the href not be found | |
785 | * @param href The href | |
786 | * @return | |
787 | */ | |
788 | private Object getOwner(String className, String id) { | |
789 | 0 | if (id == null) { |
790 | 0 | LOG.warn("There is no href attribute provided for a " + className |
791 | + " so the diagram element is ignored on load"); | |
792 | 0 | return null; |
793 | } | |
794 | 0 | final Object owner = findOwner(id); |
795 | 0 | if (owner == null) { |
796 | 0 | LOG.warn("The href " + id + " is not found for a " + className |
797 | + " so the diagram element is ignored on load"); | |
798 | 0 | return null; |
799 | } | |
800 | 0 | return owner; |
801 | } | |
802 | ||
803 | /** | |
804 | * Save the newly created Diagram for use by the parser. We take the | |
805 | * opportunity to attach our default diagram settings to it so we'll have | |
806 | * them if needed when constructing Figs. | |
807 | * <p> | |
808 | * Diagrams are created in GEF's PGMLHandler.initDiagram() which is private | |
809 | * and can't be overridden. Initialization sequence is: | |
810 | * <ul> | |
811 | * <li>load diagram class using name in PGML file | |
812 | * <li>instantiate using 0-arg constructor | |
813 | * <li>invoke this method (setDiagram(<newDiagramInstance)) | |
814 | * <li>invoke diagram's initialize(Object owner) method | |
815 | * <li>diagram.setName() | |
816 | * <li>diagram.setScale() | |
817 | * <li>diagram.setShowSingleMultiplicity() (?!why does GEF care about | |
818 | * multiplicity?!) | |
819 | * </ul> | |
820 | * | |
821 | * @param diagram the new diagram | |
822 | * @see org.tigris.gef.persistence.pgml.PGMLStackParser#setDiagram(org.tigris.gef.base.Diagram) | |
823 | */ | |
824 | @Override | |
825 | public void setDiagram(Diagram diagram) { | |
826 | // TODO: We could generalize this to initialize more stuff if needed | |
827 | 0 | ((ArgoDiagram) diagram).setDiagramSettings(getDiagramSettings()); |
828 | 0 | super.setDiagram(diagram); |
829 | 0 | } |
830 | ||
831 | public DiagramSettings getDiagramSettings() { | |
832 | 0 | return diagramSettings; |
833 | } | |
834 | } |