Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ClassdiagramLayouter |
|
| 3.28;3.28 | ||||
ClassdiagramLayouter$NodeRow |
|
| 3.28;3.28 |
1 | /* $Id: ClassdiagramLayouter.java 17863 2010-01-12 20:07:22Z linus $ | |
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 | * tfmorris | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 1996,2009 The Regents of the University of California. All | |
17 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
18 | // software and its documentation without fee, and without a written | |
19 | // agreement is hereby granted, provided that the above copyright notice | |
20 | // and this paragraph appear in all copies. This software program and | |
21 | // documentation are copyrighted by The Regents of the University of | |
22 | // California. The software program and documentation are supplied "AS | |
23 | // IS", without any accompanying services from The Regents. The Regents | |
24 | // does not warrant that the operation of the program will be | |
25 | // uninterrupted or error-free. The end-user understands that the program | |
26 | // was developed for research purposes and is advised not to rely | |
27 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
28 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
29 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
30 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
31 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
32 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
33 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
35 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
36 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
37 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
38 | ||
39 | package org.argouml.uml.diagram.static_structure.layout; | |
40 | ||
41 | import java.awt.Dimension; | |
42 | import java.awt.Point; | |
43 | import java.util.ArrayList; | |
44 | import java.util.HashMap; | |
45 | import java.util.Iterator; | |
46 | import java.util.List; | |
47 | import java.util.TreeSet; | |
48 | ||
49 | import org.apache.log4j.Logger; | |
50 | import org.argouml.uml.diagram.ArgoDiagram; | |
51 | import org.argouml.uml.diagram.layout.LayoutedObject; | |
52 | import org.argouml.uml.diagram.layout.Layouter; | |
53 | import org.tigris.gef.presentation.Fig; | |
54 | ||
55 | /** | |
56 | * This class implements a layout algorithm for class diagrams.<p> | |
57 | * | |
58 | * The layout process is performed in a row by row way. The position of the | |
59 | * nodes in a row are set using the sequence given by the <em>natural order | |
60 | * </em> of the nodes.<p> | |
61 | * | |
62 | * The resulting layout sequence: | |
63 | * <ol> | |
64 | * <li>Standalone (i.e. without links) nodes first, followed by linked nodes | |
65 | * <li>Ordered by node-typ: package, interface, class, <em>other</em> | |
66 | * <li>Increasing level in link-hierarchy - root elements first | |
67 | * <li>Decreasing amount of weighted links | |
68 | * <li>Ascending name of model object | |
69 | * </ol> | |
70 | * | |
71 | * @see ClassdiagramNode#compareTo(Object) | |
72 | * | |
73 | */ | |
74 | 0 | public class ClassdiagramLayouter implements Layouter { |
75 | // TODO: make the "magic numbers" configurable | |
76 | /** | |
77 | * This class keeps all the nodes in one row together and provides basic | |
78 | * functionality for them. | |
79 | * | |
80 | * @author David Gunkel | |
81 | */ | |
82 | private class NodeRow implements Iterable<ClassdiagramNode> { | |
83 | /** | |
84 | * Keeps all nodes of this row. | |
85 | */ | |
86 | 0 | private List<ClassdiagramNode> nodes = |
87 | new ArrayList<ClassdiagramNode>(); | |
88 | ||
89 | /** | |
90 | * The row number of this row. | |
91 | */ | |
92 | private int rowNumber; | |
93 | ||
94 | /** | |
95 | * Construct an empty NodeRow with the given row number. | |
96 | * | |
97 | * @param aRowNumber The row number of this row. | |
98 | */ | |
99 | 0 | public NodeRow(int aRowNumber) { |
100 | 0 | rowNumber = aRowNumber; |
101 | 0 | } |
102 | ||
103 | /** | |
104 | * Add a node to this NodeRow. | |
105 | * | |
106 | * @param node The node to be added | |
107 | */ | |
108 | public void addNode(ClassdiagramNode node) { | |
109 | 0 | node.setRank(rowNumber); |
110 | 0 | node.setColumn(nodes.size()); |
111 | 0 | nodes.add(node); |
112 | 0 | } |
113 | ||
114 | /** | |
115 | * Splittable are packages and standalone-nodes. A split is performed, | |
116 | * if the maximum width is reached or when a type change occurs (from | |
117 | * package to not-package, from standalone to not-standalone). | |
118 | * | |
119 | * <ul> | |
120 | * <li>packages | |
121 | * <li>After standalone | |
122 | * </ul> | |
123 | * | |
124 | * Split this row into two, if | |
125 | * <ul> | |
126 | * <li>at least one standalone node is available | |
127 | * <li>and the given maximum row width is exceeded | |
128 | * <li>or a non-standalone element is detected. | |
129 | * </ul> | |
130 | * | |
131 | * Return the new NodeRow or null if this row is not split. | |
132 | * | |
133 | * @param maxWidth | |
134 | * The maximum allowed row width | |
135 | * @param gap | |
136 | * The horizontal gab between two nodes | |
137 | * @return NodeRow | |
138 | */ | |
139 | public NodeRow doSplit(int maxWidth, int gap) { | |
140 | 0 | TreeSet<ClassdiagramNode> ts = new TreeSet<ClassdiagramNode>(nodes); |
141 | 0 | if (ts.size() < 2) { |
142 | 0 | return null; |
143 | } | |
144 | 0 | ClassdiagramNode firstNode = ts.first(); |
145 | 0 | if (!firstNode.isStandalone()) { |
146 | 0 | return null; |
147 | } | |
148 | 0 | ClassdiagramNode lastNode = ts.last(); |
149 | 0 | if (firstNode.isStandalone() && lastNode.isStandalone() |
150 | && (firstNode.isPackage() == lastNode.isPackage()) | |
151 | && getWidth(gap) <= maxWidth) { | |
152 | 0 | return null; |
153 | } | |
154 | 0 | boolean hasPackage = firstNode.isPackage(); |
155 | ||
156 | 0 | NodeRow newRow = new NodeRow(rowNumber + 1); |
157 | 0 | ClassdiagramNode split = null; |
158 | 0 | int width = 0; |
159 | 0 | int count = 0; |
160 | 0 | for (Iterator<ClassdiagramNode> iter = ts.iterator(); |
161 | 0 | iter.hasNext() && (width < maxWidth || count < 2);) { |
162 | 0 | ClassdiagramNode node = iter.next(); |
163 | // split = | |
164 | // (split == null || split.isStandalone()) ? node : split; | |
165 | 0 | split = |
166 | (split == null | |
167 | || (hasPackage && split.isPackage() == hasPackage) | |
168 | || split.isStandalone()) | |
169 | ? node | |
170 | : split; | |
171 | 0 | width += node.getSize().width + gap; |
172 | 0 | count++; |
173 | 0 | } |
174 | 0 | nodes = new ArrayList<ClassdiagramNode>(ts.headSet(split)); |
175 | 0 | for (ClassdiagramNode n : ts.tailSet(split)) { |
176 | 0 | newRow.addNode(n); |
177 | } | |
178 | 0 | if (LOG.isDebugEnabled()) { |
179 | 0 | LOG.debug("Row split. This row width: " + getWidth(gap) |
180 | + " next row(s) width: " + newRow.getWidth(gap)); | |
181 | } | |
182 | 0 | return newRow; |
183 | } | |
184 | ||
185 | /** | |
186 | * @return Returns the nodes. | |
187 | */ | |
188 | public List<ClassdiagramNode> getNodeList() { | |
189 | 0 | return nodes; |
190 | } | |
191 | ||
192 | /** | |
193 | * @return Returns the rowNumber. | |
194 | */ | |
195 | public int getRowNumber() { | |
196 | 0 | return rowNumber; |
197 | } | |
198 | ||
199 | ||
200 | /** | |
201 | * Get the width for this row using the given horizontal gap between | |
202 | * nodes. | |
203 | * | |
204 | * @param gap The horizontal gap between nodes. | |
205 | * @return The width of this row | |
206 | */ | |
207 | public int getWidth(int gap) { | |
208 | 0 | int result = 0; |
209 | 0 | for (ClassdiagramNode node : nodes) { |
210 | 0 | result += node.getSize().width + gap; |
211 | } | |
212 | 0 | if (LOG.isDebugEnabled()) { |
213 | 0 | LOG.debug("Width of row " + rowNumber + ": " + result); |
214 | } | |
215 | 0 | return result; |
216 | } | |
217 | ||
218 | /** | |
219 | * Set the row number of this row. | |
220 | * | |
221 | * @param rowNum The rowNumber to set. | |
222 | */ | |
223 | public void setRowNumber(int rowNum) { | |
224 | 0 | this.rowNumber = rowNum; |
225 | 0 | adjustRowNodes(); |
226 | 0 | } |
227 | ||
228 | /** | |
229 | * Adjust the properties for all nodes in this row: rank, | |
230 | * column, offset for edges. | |
231 | */ | |
232 | private void adjustRowNodes() { | |
233 | 0 | int col = 0; |
234 | 0 | int numNodesWithDownlinks = 0; |
235 | 0 | List<ClassdiagramNode> list = new ArrayList<ClassdiagramNode>(); |
236 | 0 | for (ClassdiagramNode node : this ) { |
237 | 0 | node.setRank(rowNumber); |
238 | 0 | node.setColumn(col++); |
239 | 0 | if (!node.getDownNodes().isEmpty()) { |
240 | 0 | numNodesWithDownlinks++; |
241 | 0 | list.add(node); |
242 | } | |
243 | } | |
244 | 0 | int offset = -numNodesWithDownlinks * E_GAP / 2; |
245 | 0 | for (ClassdiagramNode node : list ) { |
246 | 0 | node.setEdgeOffset(offset); |
247 | 0 | offset += E_GAP; |
248 | } | |
249 | 0 | } |
250 | ||
251 | /** | |
252 | * @return an Iterator for the nodes of this row, sorted by their | |
253 | * natural order. | |
254 | * @see java.lang.Iterable#iterator() | |
255 | */ | |
256 | public Iterator<ClassdiagramNode> iterator() { | |
257 | 0 | return (new TreeSet<ClassdiagramNode>(nodes)).iterator(); |
258 | } | |
259 | } | |
260 | ||
261 | /** | |
262 | * Gap to be left between edges. | |
263 | */ | |
264 | private static final int E_GAP = 5; | |
265 | ||
266 | /** | |
267 | * Horizontal gap between nodes. | |
268 | */ | |
269 | private static final int H_GAP = 80; | |
270 | ||
271 | 0 | private static final Logger LOG = |
272 | Logger.getLogger(ClassdiagramLayouter.class); | |
273 | ||
274 | /** | |
275 | * The maximum row width. | |
276 | */ | |
277 | // TODO: this should be a configurable property | |
278 | private static final int MAX_ROW_WIDTH = 1200; | |
279 | ||
280 | /** | |
281 | * Vertical gap between nodes. | |
282 | */ | |
283 | private static final int V_GAP = 80; | |
284 | ||
285 | /** | |
286 | * The diagram that is being laid out. | |
287 | */ | |
288 | private ArgoDiagram diagram; | |
289 | ||
290 | /** | |
291 | * HashMap with figures as key and Nodes as elements. | |
292 | */ | |
293 | 0 | private HashMap<Fig, ClassdiagramNode> figNodes = |
294 | new HashMap<Fig, ClassdiagramNode>(); | |
295 | ||
296 | /** | |
297 | * layoutedClassNodes is a convenience which holds a subset of | |
298 | * layoutedObjects (only ClassNodes). | |
299 | */ | |
300 | 0 | private List<ClassdiagramNode> layoutedClassNodes = |
301 | new ArrayList<ClassdiagramNode>(); | |
302 | ||
303 | /** | |
304 | * Holds all edges - subset of layoutedObjects. | |
305 | */ | |
306 | 0 | private List<ClassdiagramEdge> layoutedEdges = |
307 | new ArrayList<ClassdiagramEdge>(); | |
308 | ||
309 | /** | |
310 | * List of objects to lay out. | |
311 | */ | |
312 | 0 | private List<LayoutedObject> layoutedObjects = |
313 | new ArrayList<LayoutedObject>(); | |
314 | ||
315 | /** | |
316 | * List of NodeRows in the diagram. | |
317 | */ | |
318 | 0 | private List<NodeRow> nodeRows = new ArrayList<NodeRow>(); |
319 | ||
320 | /** | |
321 | * Base X position to use a starting point for next node. | |
322 | */ | |
323 | private int xPos; | |
324 | ||
325 | /** | |
326 | * Base Y position for the row currently being laid out. | |
327 | */ | |
328 | private int yPos; | |
329 | ||
330 | /** | |
331 | * Constructor for the layouter. Takes a diagram as input to extract all | |
332 | * LayoutedObjects, which will be layouted. | |
333 | * | |
334 | * @param theDiagram The diagram to layout. | |
335 | */ | |
336 | 0 | public ClassdiagramLayouter(ArgoDiagram theDiagram) { |
337 | 0 | diagram = theDiagram; |
338 | 0 | for (Fig fig : diagram.getLayer().getContents()) { |
339 | 0 | if (fig.getEnclosingFig() == null) { |
340 | 0 | add(ClassdiagramModelElementFactory.SINGLETON.getInstance(fig)); |
341 | } | |
342 | } | |
343 | 0 | } |
344 | ||
345 | /** | |
346 | * Add an object to layout. | |
347 | * | |
348 | * @param obj represents the object to layout. | |
349 | */ | |
350 | public void add(LayoutedObject obj) { | |
351 | // TODO: check for duplicates (is this possible???) | |
352 | 0 | layoutedObjects.add(obj); |
353 | 0 | if (obj instanceof ClassdiagramNode) { |
354 | 0 | layoutedClassNodes.add((ClassdiagramNode) obj); |
355 | 0 | } else if (obj instanceof ClassdiagramEdge) { |
356 | 0 | layoutedEdges.add((ClassdiagramEdge) obj); |
357 | } | |
358 | 0 | } |
359 | ||
360 | /** | |
361 | * Get the horizontal gap between nodes. | |
362 | * | |
363 | * @return The horizontal gap between nodes. | |
364 | */ | |
365 | private int getHGap() { | |
366 | 0 | return H_GAP; |
367 | } | |
368 | ||
369 | /** | |
370 | * Return the minimum diagram size after the layout process. | |
371 | * | |
372 | * @return The minimum diagram size after the layout process. | |
373 | */ | |
374 | public Dimension getMinimumDiagramSize() { | |
375 | 0 | int width = 0, height = 0; |
376 | 0 | int hGap2 = getHGap() / 2; |
377 | 0 | int vGap2 = getVGap() / 2; |
378 | 0 | for (ClassdiagramNode node : layoutedClassNodes) { |
379 | 0 | width = |
380 | Math.max(width, | |
381 | node.getLocation().x | |
382 | + (int) node.getSize().getWidth() + hGap2); | |
383 | 0 | height = |
384 | Math.max(height, | |
385 | node.getLocation().y | |
386 | + (int) node.getSize().getHeight() + vGap2); | |
387 | } | |
388 | 0 | return new Dimension(width, height); |
389 | } | |
390 | ||
391 | /** | |
392 | * Return the object with a given index from the layouter. | |
393 | * | |
394 | * @param index | |
395 | * represents the index of this object in the layouter. | |
396 | * @return The LayoutedObject for the given index. | |
397 | */ | |
398 | public LayoutedObject getObject(int index) { | |
399 | 0 | return layoutedObjects.get(index); |
400 | } | |
401 | ||
402 | /** | |
403 | * Return all the objects currently participating in | |
404 | * the layout process. | |
405 | * | |
406 | * @return An array holding all the object in the layouter. | |
407 | */ | |
408 | public LayoutedObject[] getObjects() { | |
409 | 0 | return (LayoutedObject[]) layoutedObjects.toArray(); |
410 | } | |
411 | ||
412 | /** | |
413 | * Get the vertical gap between nodes. | |
414 | * | |
415 | * @return The vertical gap between nodes. | |
416 | */ | |
417 | private int getVGap() { | |
418 | 0 | return V_GAP; |
419 | } | |
420 | ||
421 | /** | |
422 | * Lay out the current diagram. | |
423 | */ | |
424 | public void layout() { | |
425 | 0 | long s = System.currentTimeMillis(); |
426 | 0 | setupLinks(); |
427 | 0 | rankAndWeightNodes(); |
428 | 0 | placeNodes(); |
429 | 0 | placeEdges(); |
430 | 0 | LOG.debug("layout duration: " + (System.currentTimeMillis() - s)); |
431 | 0 | } |
432 | ||
433 | /** | |
434 | * All layoutedObjects of type "Edge" are placed using an | |
435 | * edge-type specific layout algorithm. The offset from a | |
436 | * <em>centered</em> edge is taken from the parent node to avoid | |
437 | * overlaps. | |
438 | * | |
439 | * @see ClassdiagramEdge | |
440 | */ | |
441 | private void placeEdges() { | |
442 | 0 | ClassdiagramEdge.setVGap(getVGap()); |
443 | 0 | ClassdiagramEdge.setHGap(getHGap()); |
444 | 0 | for (ClassdiagramEdge edge : layoutedEdges) { |
445 | 0 | if (edge instanceof ClassdiagramInheritanceEdge) { |
446 | 0 | ClassdiagramNode parent = figNodes.get(edge.getDestFigNode()); |
447 | 0 | ((ClassdiagramInheritanceEdge) edge).setOffset(parent |
448 | .getEdgeOffset()); | |
449 | } | |
450 | 0 | edge.layout(); |
451 | ||
452 | } | |
453 | 0 | } |
454 | ||
455 | /** | |
456 | * Set the placement coordinate for a given node. | |
457 | * | |
458 | * @param node To be placed. | |
459 | */ | |
460 | private void placeNode(ClassdiagramNode node) { | |
461 | 0 | List<ClassdiagramNode> uplinks = node.getUpNodes(); |
462 | 0 | List<ClassdiagramNode> downlinks = node.getDownNodes(); |
463 | 0 | int width = node.getSize().width; |
464 | 0 | double xOffset = width + getHGap(); |
465 | 0 | int bumpX = getHGap() / 2; // (xOffset - curW) / 2; |
466 | 0 | int xPosNew = |
467 | Math.max(xPos + bumpX, | |
468 | uplinks.size() == 1 ? node.getPlacementHint() : -1); | |
469 | 0 | node.setLocation(new Point(xPosNew, yPos)); |
470 | 0 | if (LOG.isDebugEnabled()) { |
471 | 0 | LOG.debug("placeNode - Row: " + node.getRank() + " Col: " |
472 | + node.getColumn() + " Weight: " + node.getWeight() | |
473 | + " Position: (" + xPosNew + "," + yPos + ") xPos: " | |
474 | + xPos + " hint: " + node.getPlacementHint()); | |
475 | } | |
476 | // If there's only a single child (and we're it's only parent), | |
477 | // set a hint for where to place it when we get to its row | |
478 | 0 | if (downlinks.size() == 1) { |
479 | 0 | ClassdiagramNode downNode = downlinks.get(0); |
480 | 0 | if (downNode.getUpNodes().get(0).equals(node)) { |
481 | 0 | downNode.setPlacementHint(xPosNew); |
482 | } | |
483 | } | |
484 | 0 | xPos = (int) Math.max(node.getPlacementHint() + width, xPos + xOffset); |
485 | 0 | } |
486 | ||
487 | /** | |
488 | * Place the NodeRows in the diagram. | |
489 | */ | |
490 | private void placeNodes() { | |
491 | // TODO: place comments near connected classes | |
492 | // TODO: place from middle towards outer edges? (or place largest | |
493 | // groups first) | |
494 | 0 | int xInit = 0; |
495 | 0 | yPos = getVGap() / 2; |
496 | 0 | for (NodeRow row : nodeRows) { |
497 | 0 | xPos = xInit; |
498 | 0 | int rowHeight = 0; |
499 | 0 | for (ClassdiagramNode node : row) { |
500 | 0 | placeNode(node); |
501 | 0 | rowHeight = Math.max(rowHeight, node.getSize().height); |
502 | } | |
503 | 0 | yPos += rowHeight + getVGap(); |
504 | ||
505 | 0 | } |
506 | 0 | centerParents(); |
507 | 0 | } |
508 | ||
509 | /** | |
510 | * Center parents over their children, working from bottom to top. | |
511 | */ | |
512 | private void centerParents() { | |
513 | 0 | for (int i = nodeRows.size() - 1; i >= 0; i--) { |
514 | 0 | for (ClassdiagramNode node : nodeRows.get(i)) { |
515 | 0 | List<ClassdiagramNode> children = node.getDownNodes(); |
516 | 0 | if (children.size() > 0) { |
517 | 0 | node.setLocation(new Point(xCenter(children) |
518 | - node.getSize().width / 2, node.getLocation().y)); | |
519 | } | |
520 | 0 | } |
521 | // TODO: Make another pass to deal with overlaps? | |
522 | } | |
523 | 0 | } |
524 | ||
525 | /** | |
526 | * Compute the horizontal center of a list of nodes. | |
527 | * @param nodes the list of nodes | |
528 | * @return the computed X coordinate | |
529 | */ | |
530 | private int xCenter(List<ClassdiagramNode> nodes) { | |
531 | 0 | int left = 9999999; |
532 | 0 | int right = 0; |
533 | 0 | for (ClassdiagramNode node : nodes) { |
534 | 0 | int x = node.getLocation().x; |
535 | 0 | left = Math.min(left, x); |
536 | 0 | right = Math.max(right, x + node.getSize().width); |
537 | 0 | } |
538 | 0 | return (right + left) / 2; |
539 | } | |
540 | ||
541 | /** | |
542 | * Rank the nodes depending on their level (position in hierarchy) and set | |
543 | * their weight to achieve a proper node-sequence for the layout. Rows | |
544 | * exceeding the maximum row width are split, if standalone nodes are | |
545 | * available. | |
546 | * <p> | |
547 | * Weight the other nodes to determine their columns. | |
548 | * <p> | |
549 | * TODO: Weighting doesn't appear to be working as intended because multiple | |
550 | * groups of children/specializations get intermixed in name order rather | |
551 | * than being grouped by their parent/generalization. - tfm - 20070314 | |
552 | */ | |
553 | private void rankAndWeightNodes() { | |
554 | 0 | List<ClassdiagramNode> comments = new ArrayList<ClassdiagramNode>(); |
555 | 0 | nodeRows.clear(); |
556 | 0 | TreeSet<ClassdiagramNode> nodeTree = |
557 | new TreeSet<ClassdiagramNode>(layoutedClassNodes); | |
558 | // boolean hasPackages = false; | |
559 | // TODO: move "package in row" to NodeRow | |
560 | 0 | for (ClassdiagramNode node : nodeTree) { |
561 | // if (node.isPackage()) { | |
562 | // hasPackages = true; | |
563 | // } else if (hasPackages) { | |
564 | // hasPackages = false; | |
565 | // currentRank = -1; | |
566 | // } | |
567 | 0 | if (node.isComment()) { |
568 | 0 | comments.add(node); |
569 | } else { | |
570 | 0 | int rowNum = node.getRank(); |
571 | 0 | for (int i = nodeRows.size(); i <= rowNum; i++) { |
572 | 0 | nodeRows.add(new NodeRow(rowNum)); |
573 | } | |
574 | 0 | nodeRows.get(rowNum).addNode(node); |
575 | 0 | } |
576 | } | |
577 | 0 | for (ClassdiagramNode node : comments) { |
578 | 0 | int rowInd = |
579 | node.getUpNodes().isEmpty() | |
580 | ? 0 | |
581 | : ((node.getUpNodes().get(0)).getRank()); | |
582 | ||
583 | 0 | nodeRows.get(rowInd).addNode(node); |
584 | 0 | } |
585 | 0 | for (int row = 0; row < nodeRows.size();) { |
586 | 0 | NodeRow diaRow = nodeRows.get(row); |
587 | 0 | diaRow.setRowNumber(row++); |
588 | 0 | diaRow = diaRow.doSplit(MAX_ROW_WIDTH, H_GAP); |
589 | 0 | if (diaRow != null) { |
590 | 0 | nodeRows.add(row, diaRow); |
591 | } | |
592 | 0 | } |
593 | 0 | } |
594 | ||
595 | /** | |
596 | * Remove an object from the layout process. | |
597 | * | |
598 | * @param obj represents the object to remove. | |
599 | */ | |
600 | public void remove(LayoutedObject obj) { | |
601 | 0 | layoutedObjects.remove(obj); |
602 | 0 | } |
603 | ||
604 | /** | |
605 | * Set the up- and downlinks for each node based on the edges which are | |
606 | * shown in the diagram. | |
607 | */ | |
608 | private void setupLinks() { | |
609 | 0 | figNodes.clear(); |
610 | 0 | HashMap<Fig, List<ClassdiagramInheritanceEdge>> figParentEdges = |
611 | new HashMap<Fig, List<ClassdiagramInheritanceEdge>>(); | |
612 | 0 | for (ClassdiagramNode node : layoutedClassNodes) { |
613 | 0 | node.getUpNodes().clear(); |
614 | 0 | node.getDownNodes().clear(); |
615 | 0 | figNodes.put(node.getFigure(), node); |
616 | } | |
617 | 0 | for (ClassdiagramEdge edge : layoutedEdges) { |
618 | 0 | Fig parentFig = edge.getDestFigNode(); |
619 | 0 | ClassdiagramNode child = figNodes.get(edge.getSourceFigNode()); |
620 | 0 | ClassdiagramNode parent = figNodes.get(parentFig); |
621 | 0 | if (edge instanceof ClassdiagramInheritanceEdge) { |
622 | 0 | if (parent != null && child != null) { |
623 | 0 | parent.addDownlink(child); |
624 | 0 | child.addUplink(parent); |
625 | 0 | List<ClassdiagramInheritanceEdge> edgeList = |
626 | figParentEdges.get(parentFig); | |
627 | 0 | if (edgeList == null) { |
628 | 0 | edgeList = new ArrayList<ClassdiagramInheritanceEdge>(); |
629 | 0 | figParentEdges.put(parentFig, edgeList); |
630 | } | |
631 | 0 | edgeList.add((ClassdiagramInheritanceEdge) edge); |
632 | 0 | } else { |
633 | 0 | LOG.error("Edge with missing end(s): " + edge); |
634 | } | |
635 | 0 | } else if (edge instanceof ClassdiagramNoteEdge) { |
636 | 0 | if (parent.isComment()) { |
637 | 0 | parent.addUplink(child); |
638 | 0 | } else if (child.isComment()) { |
639 | 0 | child.addUplink(parent); |
640 | } else { | |
641 | 0 | LOG.error("Unexpected parent/child constellation for edge: " |
642 | + edge); | |
643 | } | |
644 | 0 | } else if (edge instanceof ClassdiagramAssociationEdge) { |
645 | // Associations not supported, yet | |
646 | // TODO: Create appropriate ClassdiagramEdge | |
647 | } else { | |
648 | 0 | LOG.error("Unsupported edge type"); |
649 | } | |
650 | ||
651 | 0 | } |
652 | 0 | } |
653 | } |