Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ExplorerTreeModel |
|
| 3.782608695652174;3.783 | ||||
ExplorerTreeModel$ExplorerUpdater |
|
| 3.782608695652174;3.783 |
1 | /* $Id: ExplorerTreeModel.java 17843 2010-01-12 19:23:29Z 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 | * bobtarling | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 1996-2006 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.ui.explorer; | |
40 | ||
41 | import java.awt.EventQueue; | |
42 | import java.awt.event.ItemEvent; | |
43 | import java.awt.event.ItemListener; | |
44 | import java.util.ArrayList; | |
45 | import java.util.Collection; | |
46 | import java.util.Collections; | |
47 | import java.util.Comparator; | |
48 | import java.util.Enumeration; | |
49 | import java.util.HashMap; | |
50 | import java.util.HashSet; | |
51 | import java.util.Iterator; | |
52 | import java.util.LinkedList; | |
53 | import java.util.List; | |
54 | import java.util.Map; | |
55 | import java.util.Set; | |
56 | ||
57 | import javax.swing.tree.DefaultMutableTreeNode; | |
58 | import javax.swing.tree.DefaultTreeModel; | |
59 | import javax.swing.tree.MutableTreeNode; | |
60 | import javax.swing.tree.TreeNode; | |
61 | import javax.swing.tree.TreePath; | |
62 | ||
63 | import org.apache.log4j.Logger; | |
64 | import org.argouml.kernel.Project; | |
65 | import org.argouml.kernel.ProjectManager; | |
66 | import org.argouml.model.InvalidElementException; | |
67 | import org.argouml.ui.explorer.rules.PerspectiveRule; | |
68 | ||
69 | /** | |
70 | * The model for the Explorer tree view of the uml model. | |
71 | * | |
72 | * provides: | |
73 | * - receives events from the uml model and updates itself and the tree ui. | |
74 | * - responds to changes in perspective and ordering. | |
75 | * | |
76 | * @author alexb | |
77 | * @since 0.15.2 | |
78 | */ | |
79 | 231 | public class ExplorerTreeModel extends DefaultTreeModel |
80 | implements TreeModelUMLEventListener, ItemListener { | |
81 | /** | |
82 | * Logger. | |
83 | */ | |
84 | 900 | private static final Logger LOG = |
85 | Logger.getLogger(ExplorerTreeModel.class); | |
86 | ||
87 | /** | |
88 | * an array of | |
89 | * {@link org.argouml.ui.explorer.rules.PerspectiveRule PerspectiveRules}, | |
90 | * that determine the tree view. | |
91 | */ | |
92 | private List<PerspectiveRule> rules; | |
93 | ||
94 | /** | |
95 | * a map used to resolve model elements to tree nodes when determining | |
96 | * what effect a model event will have on the tree. | |
97 | */ | |
98 | private Map<Object, Set<ExplorerTreeNode>> modelElementMap; | |
99 | ||
100 | /** | |
101 | * the global order for siblings in the tree. | |
102 | */ | |
103 | private Comparator order; | |
104 | ||
105 | /** | |
106 | * The children currently being updated. | |
107 | */ | |
108 | 900 | private List<ExplorerTreeNode> updatingChildren = |
109 | new ArrayList<ExplorerTreeNode>(); | |
110 | ||
111 | /** | |
112 | * A Runnable object that when executed does update some | |
113 | * currently pending nodes. | |
114 | */ | |
115 | 900 | private ExplorerUpdater nodeUpdater = new ExplorerUpdater(); |
116 | ||
117 | private ExplorerTree tree; | |
118 | ||
119 | /** | |
120 | * Help class to semi-lazily update nodes in the tree. | |
121 | * This class is thread safe. | |
122 | */ | |
123 | 900 | class ExplorerUpdater implements Runnable { |
124 | /** | |
125 | * The set of nodes pending being updated. | |
126 | */ | |
127 | 900 | private LinkedList<ExplorerTreeNode> pendingUpdates = |
128 | new LinkedList<ExplorerTreeNode>(); | |
129 | ||
130 | /** | |
131 | * Is this object currently waiting to be run. | |
132 | */ | |
133 | private boolean hot; | |
134 | ||
135 | /** | |
136 | * The maximum number of nodes to update in one chunk. | |
137 | */ | |
138 | public static final int MAX_UPDATES_PER_RUN = 100; | |
139 | ||
140 | /** | |
141 | * Schedule this object to run on AWT-EventQueue-0 at some later time. | |
142 | */ | |
143 | private synchronized void schedule() { | |
144 | 425 | if (hot) { |
145 | 194 | return; |
146 | } | |
147 | 231 | hot = true; |
148 | 231 | EventQueue.invokeLater(this); |
149 | 231 | } |
150 | ||
151 | /** | |
152 | * Schedule updateChildren to be called on node at some later time. | |
153 | * Does nothing if there already is a pending update of node. | |
154 | * | |
155 | * @param node The ExplorerTreeNode to be updated. | |
156 | * @throws NullPointerException If node is null. | |
157 | */ | |
158 | public synchronized void schedule(ExplorerTreeNode node) { | |
159 | 814 | if (node.getPending()) { |
160 | 389 | return; |
161 | } | |
162 | ||
163 | 425 | pendingUpdates.add(node); |
164 | 425 | node.setPending(true); |
165 | 425 | schedule(); |
166 | 425 | } |
167 | ||
168 | /** | |
169 | * Call updateChildren for some pending nodes. Will call at most | |
170 | * MAX_UPDATES_PER_RUN each time. Should there still be pending | |
171 | * updates after that then it will reschedule itself.<p> | |
172 | * | |
173 | * This method should not be called explicitly, instead schedule | |
174 | * should be called and this method will be called automatically. | |
175 | */ | |
176 | public void run() { | |
177 | 231 | boolean done = false; |
178 | ||
179 | 656 | for (int i = 0; i < MAX_UPDATES_PER_RUN; i++) { |
180 | 656 | ExplorerTreeNode node = null; |
181 | 656 | synchronized (this) { |
182 | 656 | if (!pendingUpdates.isEmpty()) { |
183 | 425 | node = pendingUpdates.removeFirst(); |
184 | 425 | node.setPending(false); |
185 | } else { | |
186 | 231 | done = true; |
187 | 231 | hot = false; |
188 | 231 | break; |
189 | } | |
190 | 425 | } |
191 | ||
192 | 425 | updateChildren(new TreePath(getPathToRoot(node))); |
193 | } | |
194 | ||
195 | 231 | if (!done) { |
196 | 0 | schedule(); |
197 | } else { | |
198 | // TODO: This seems like a brute force workaround (and a very | |
199 | // indirect one at that). It appears to be needed though until | |
200 | // we fix the problem properly. - tfm 20070904 | |
201 | /* This solves issue 2287. */ | |
202 | 231 | tree.refreshSelection(); |
203 | } | |
204 | 231 | } |
205 | } | |
206 | ||
207 | /** | |
208 | * The constructor of ExplorerTreeModel. | |
209 | * | |
210 | * @param root an object to place at the root | |
211 | * @param myTree the tree | |
212 | */ | |
213 | public ExplorerTreeModel(Object root, ExplorerTree myTree) { | |
214 | 900 | super(new DefaultMutableTreeNode()); |
215 | ||
216 | 900 | tree = myTree; |
217 | 900 | setRoot(new ExplorerTreeNode(root, this)); |
218 | 900 | setAsksAllowsChildren(false); |
219 | 900 | modelElementMap = new HashMap<Object, Set<ExplorerTreeNode>>(); |
220 | ||
221 | 900 | ExplorerEventAdaptor.getInstance() |
222 | .setTreeModelUMLEventListener(this); | |
223 | ||
224 | 900 | order = new TypeThenNameOrder(); |
225 | 900 | } |
226 | ||
227 | /* | |
228 | * @see org.argouml.ui.explorer.TreeModelUMLEventListener#modelElementChanged(java.lang.Object) | |
229 | */ | |
230 | public void modelElementChanged(Object node) { | |
231 | 1811 | traverseModified((TreeNode) getRoot(), node); |
232 | 1811 | } |
233 | ||
234 | /* | |
235 | * @see org.argouml.ui.explorer.TreeModelUMLEventListener#modelElementAdded(java.lang.Object) | |
236 | */ | |
237 | public void modelElementAdded(Object node) { | |
238 | 597 | traverseModified((TreeNode) getRoot(), node); |
239 | 597 | } |
240 | ||
241 | /** | |
242 | * Traverses the children, finds those affected by the given node, | |
243 | * and notifies them that they are modified. | |
244 | * | |
245 | * @param start the node to start from | |
246 | * @param node the given node | |
247 | */ | |
248 | private void traverseModified(TreeNode start, Object node) { | |
249 | 13372 | Enumeration children = start.children(); |
250 | 24296 | while (children.hasMoreElements()) { |
251 | 10924 | TreeNode child = (TreeNode) children.nextElement(); |
252 | 10924 | traverseModified(child, node); |
253 | 10924 | } |
254 | ||
255 | 13372 | if (start instanceof ExplorerTreeNode) { |
256 | 13372 | ((ExplorerTreeNode) start).nodeModified(node); |
257 | } | |
258 | 13372 | } |
259 | ||
260 | /* | |
261 | * @see org.argouml.ui.explorer.TreeModelUMLEventListener#modelElementRemoved(java.lang.Object) | |
262 | */ | |
263 | public void modelElementRemoved(Object node) { | |
264 | for (ExplorerTreeNode changeNode | |
265 | 40 | : new ArrayList<ExplorerTreeNode>(findNodes(node))) { |
266 | 38 | if (changeNode.getParent() != null) { |
267 | 38 | removeNodeFromParent(changeNode); |
268 | } | |
269 | } | |
270 | ||
271 | 40 | traverseModified((TreeNode) getRoot(), node); |
272 | 40 | } |
273 | ||
274 | /* | |
275 | * the model structure has changed significantly, eg a new project. | |
276 | * @see org.argouml.ui.explorer.TreeModelUMLEventListener#structureChanged() | |
277 | */ | |
278 | public void structureChanged() { | |
279 | // remove references for gc | |
280 | 7516 | if (getRoot() instanceof ExplorerTreeNode) { |
281 | 7516 | ((ExplorerTreeNode) getRoot()).remove(); |
282 | } | |
283 | ||
284 | // This should only be helpful for old garbage collectors. | |
285 | 7516 | for (Collection nodes : modelElementMap.values()) { |
286 | 27158 | nodes.clear(); |
287 | } | |
288 | 7516 | modelElementMap.clear(); |
289 | ||
290 | // This is somewhat inconsistent with the design of the constructor | |
291 | // that receives the root object by argument. If this is okay | |
292 | // then there may be no need for a constructor with that argument. | |
293 | 7516 | modelElementMap = new HashMap<Object, Set<ExplorerTreeNode>>(); |
294 | 7516 | Project proj = ProjectManager.getManager().getCurrentProject(); |
295 | 7516 | ExplorerTreeNode rootNode = new ExplorerTreeNode(proj, this); |
296 | ||
297 | 7516 | addToMap(proj, rootNode); |
298 | 7516 | setRoot(rootNode); |
299 | 7516 | } |
300 | ||
301 | /** | |
302 | * updates next level of the explorer tree for a given tree path. | |
303 | * | |
304 | * @param path the path to the node whose children to update. | |
305 | * @throws IllegalArgumentException if node has a child that is not a | |
306 | * (descendant of) DefaultMutableTreeNode. | |
307 | */ | |
308 | public void updateChildren(TreePath path) { | |
309 | 28073 | ExplorerTreeNode node = (ExplorerTreeNode) path.getLastPathComponent(); |
310 | 28073 | Object modelElement = node.getUserObject(); |
311 | ||
312 | // Avoid doing this too early in the initialization process | |
313 | 28073 | if (rules == null) { |
314 | 900 | return; |
315 | } | |
316 | ||
317 | // Avoid recursively updating the same child | |
318 | 27173 | if (updatingChildren.contains(node)) { |
319 | 11540 | return; |
320 | } | |
321 | 15633 | updatingChildren.add(node); |
322 | ||
323 | 15633 | List children = reorderChildren(node); |
324 | ||
325 | 15633 | List newChildren = new ArrayList(); |
326 | 15633 | Set deps = new HashSet(); |
327 | 15633 | collectChildren(modelElement, newChildren, deps); |
328 | ||
329 | 15633 | node.setModifySet(deps); |
330 | ||
331 | 15633 | mergeChildren(node, children, newChildren); |
332 | ||
333 | 15633 | updatingChildren.remove(node); |
334 | 15633 | } |
335 | ||
336 | /** | |
337 | * Sorts the child nodes of node using the current ordering.<p> | |
338 | * | |
339 | * Note: UserObject is only available from descendants of | |
340 | * DefaultMutableTreeNode, so any other children couldn't be sorted. | |
341 | * Thus these are currently forbidden. But currently no such node is | |
342 | * ever inserted into the tree. | |
343 | * | |
344 | * @param node the node whose children to sort | |
345 | * @return the UserObjects of the children, in the same order as the | |
346 | * children. | |
347 | * @throws IllegalArgumentException if node has a child that is not a | |
348 | * (descendant of) DefaultMutableTreeNode. | |
349 | */ | |
350 | private List<Object> reorderChildren(ExplorerTreeNode node) { | |
351 | 15633 | List<Object> childUserObjects = new ArrayList<Object>(); |
352 | 15633 | List<ExplorerTreeNode> reordered = new ArrayList<ExplorerTreeNode>(); |
353 | ||
354 | // Enumerate the current children of node to find out which now sorts | |
355 | // in different order, since these must be moved | |
356 | 15633 | Enumeration enChld = node.children(); |
357 | 15633 | Object lastObj = null; |
358 | 16481 | while (enChld.hasMoreElements()) { |
359 | 848 | Object child = enChld.nextElement(); |
360 | 848 | if (child instanceof ExplorerTreeNode) { |
361 | 848 | Object obj = ((ExplorerTreeNode) child).getUserObject(); |
362 | 848 | if (lastObj != null && order.compare(lastObj, obj) > 0) { |
363 | /* | |
364 | * If a node to be moved is currently selected, | |
365 | * move its predecessors instead so don't lose selection. | |
366 | * This fixes issue 3249. | |
367 | * NOTE: this does not deal with the case where | |
368 | * multiple nodes are selected and they are out | |
369 | * of order with respect to each other, but I | |
370 | * don't think more than one node is ever reordered | |
371 | * at a time - tfm | |
372 | */ | |
373 | 0 | if (!tree.isPathSelected(new TreePath( |
374 | getPathToRoot((ExplorerTreeNode) child)))) { | |
375 | 0 | reordered.add((ExplorerTreeNode) child); |
376 | } else { | |
377 | 0 | ExplorerTreeNode prev = |
378 | (ExplorerTreeNode) ((ExplorerTreeNode) child) | |
379 | .getPreviousSibling(); | |
380 | while (prev != null | |
381 | 0 | && (order.compare(prev.getUserObject(), obj) |
382 | >= 0)) { | |
383 | 0 | reordered.add(prev); |
384 | 0 | childUserObjects.remove(childUserObjects.size() - 1); |
385 | 0 | prev = (ExplorerTreeNode) prev.getPreviousSibling(); |
386 | } | |
387 | 0 | childUserObjects.add(obj); |
388 | 0 | lastObj = obj; |
389 | 0 | } |
390 | } else { | |
391 | 848 | childUserObjects.add(obj); |
392 | 848 | lastObj = obj; |
393 | } | |
394 | 848 | } else { |
395 | 0 | throw new IllegalArgumentException( |
396 | "Incomprehencible child node " + child.toString()); | |
397 | } | |
398 | 848 | } |
399 | ||
400 | 15633 | for (ExplorerTreeNode child : reordered) { |
401 | // Avoid our deinitialization here | |
402 | // The node will be added back to the tree again | |
403 | 0 | super.removeNodeFromParent(child); |
404 | } | |
405 | ||
406 | // For each reordered node, find it's new position among the current | |
407 | // children and move it there | |
408 | 15633 | for (ExplorerTreeNode child : reordered) { |
409 | 0 | Object obj = child.getUserObject(); |
410 | 0 | int ip = Collections.binarySearch(childUserObjects, obj, order); |
411 | ||
412 | 0 | if (ip < 0) { |
413 | 0 | ip = -(ip + 1); |
414 | } | |
415 | ||
416 | // Avoid our initialization here | |
417 | 0 | super.insertNodeInto(child, node, ip); |
418 | 0 | childUserObjects.add(ip, obj); |
419 | 0 | } |
420 | ||
421 | 15633 | return childUserObjects; |
422 | } | |
423 | ||
424 | /** | |
425 | * Collects the set of children modelElement should have at this point in | |
426 | * time. The children are added to newChildren.<p> | |
427 | * | |
428 | * Note: Both newChildren and deps are modified by this function, it | |
429 | * is in fact it's primary purpose to modify these collections. It is your | |
430 | * responsibility to make sure that they are empty when it is called, or | |
431 | * to know what you are doing if they are not. | |
432 | * | |
433 | * @param modelElement the element to collect children for. | |
434 | * @param newChildren the new children of modelElement. | |
435 | * @param deps the set of objects that should be monitored for changes | |
436 | * since these could affect this list. | |
437 | * @throws UnsupportedOperationException if add is not supported by | |
438 | * newChildren or addAll isn't supported by deps. | |
439 | * @throws NullPointerException if newChildren or deps is null. | |
440 | * @throws ClassCastException if newChildren or deps rejects some element. | |
441 | * @throws IllegalArgumentException if newChildren or deps rejects some | |
442 | * element. | |
443 | */ | |
444 | private void collectChildren(Object modelElement, List newChildren, | |
445 | Set deps) { | |
446 | 15633 | if (modelElement == null) { |
447 | 3600 | return; |
448 | } | |
449 | ||
450 | // Collect the current set of objects that should be children to | |
451 | // this node | |
452 | 12033 | for (PerspectiveRule rule : rules) { |
453 | ||
454 | // TODO: A better implementation would be to batch events into | |
455 | // logical groups and update the tree one time for the entire | |
456 | // group, synchronizing access to the model repository so that | |
457 | // it stays consistent during the query. This would likely | |
458 | // require doing the updates in a different thread than the | |
459 | // event delivery thread to prevent deadlocks, so for right now | |
460 | // we protect ourselves with try/catch blocks. | |
461 | 503418 | Collection children = Collections.emptySet(); |
462 | try { | |
463 | 503418 | children = rule.getChildren(modelElement); |
464 | 0 | } catch (InvalidElementException e) { |
465 | 0 | LOG.debug("InvalidElementException in ExplorerTree : " |
466 | + e.getStackTrace()); | |
467 | 503418 | } |
468 | ||
469 | 503418 | for (Object child : children) { |
470 | 31803 | if (child == null) { |
471 | 0 | LOG.warn("PerspectiveRule " + rule + " wanted to " |
472 | + "add null to the explorer tree!"); | |
473 | 31803 | } else if (!newChildren.contains(child)) { |
474 | 27842 | newChildren.add(child); |
475 | } | |
476 | } | |
477 | ||
478 | ||
479 | try { | |
480 | 503418 | Set dependencies = rule.getDependencies(modelElement); |
481 | 503418 | deps.addAll(dependencies); |
482 | 0 | } catch (InvalidElementException e) { |
483 | 0 | LOG.debug("InvalidElementException in ExplorerTree : " |
484 | + e.getStackTrace()); | |
485 | 503418 | } |
486 | ||
487 | 503418 | } |
488 | ||
489 | // Order the new children, the dependencies cannot and | |
490 | // need not be ordered | |
491 | 12033 | Collections.sort(newChildren, order); |
492 | 12033 | deps.addAll(newChildren); |
493 | 12033 | } |
494 | ||
495 | /** | |
496 | * Returns a Set of current children to remove and modifies newChildren | |
497 | * to only contain the children not already in children and not subsumed | |
498 | * by any WeakExplorerNode in children.<p> | |
499 | * | |
500 | * Note: newChildren will be modified by this call.<p> | |
501 | * | |
502 | * Note: It is expected that a WeakExplorerNode will not be reused and | |
503 | * thus they will always initially be slated for removal, and only those | |
504 | * nodes are in fact used to check subsumption of new nodes. New nodes | |
505 | * are not checked among themselves for subsumtion. | |
506 | * | |
507 | * @param children is the list of current children. | |
508 | * @param newChildren is the list of expected children. | |
509 | * @return the Set of current children to remove. | |
510 | * @throws UnsupportedOperationException if newChildren doesn't support | |
511 | * remove or removeAll. | |
512 | * @throws NullPointerException if either argument is null. | |
513 | */ | |
514 | private Set prepareAddRemoveSets(List children, List newChildren) { | |
515 | 15633 | Set removeSet = new HashSet(); |
516 | 15633 | Set commonObjects = new HashSet(); |
517 | 15633 | if (children.size() < newChildren.size()) { |
518 | 11734 | commonObjects.addAll(children); |
519 | 11734 | commonObjects.retainAll(newChildren); |
520 | } else { | |
521 | 3899 | commonObjects.addAll(newChildren); |
522 | 3899 | commonObjects.retainAll(children); |
523 | } | |
524 | 15633 | newChildren.removeAll(commonObjects); |
525 | 15633 | removeSet.addAll(children); |
526 | 15633 | removeSet.removeAll(commonObjects); |
527 | ||
528 | // Handle WeakExplorerNodes | |
529 | 15633 | Iterator it = removeSet.iterator(); |
530 | 15633 | List weakNodes = null; |
531 | 15633 | while (it.hasNext()) { |
532 | 0 | Object obj = it.next(); |
533 | 0 | if (!(obj instanceof WeakExplorerNode)) { |
534 | 0 | continue; |
535 | } | |
536 | 0 | WeakExplorerNode node = (WeakExplorerNode) obj; |
537 | ||
538 | 0 | if (weakNodes == null) { |
539 | 0 | weakNodes = new LinkedList(); |
540 | 0 | Iterator it2 = newChildren.iterator(); |
541 | 0 | while (it2.hasNext()) { |
542 | 0 | Object obj2 = it2.next(); |
543 | 0 | if (obj2 instanceof WeakExplorerNode) { |
544 | 0 | weakNodes.add(obj2); |
545 | } | |
546 | 0 | } |
547 | } | |
548 | ||
549 | 0 | Iterator it3 = weakNodes.iterator(); |
550 | 0 | while (it3.hasNext()) { |
551 | 0 | Object obj3 = it3.next(); |
552 | 0 | if (node.subsumes(obj3)) { |
553 | // Remove the node from removeSet | |
554 | 0 | it.remove(); |
555 | // Remove obj3 from weakNodes and newChildren | |
556 | 0 | newChildren.remove(obj3); |
557 | 0 | it3.remove(); |
558 | 0 | break; |
559 | } | |
560 | 0 | } |
561 | 0 | } |
562 | ||
563 | 15633 | return removeSet; |
564 | } | |
565 | ||
566 | /** | |
567 | * Merges the current children with the new children removing children no | |
568 | * longer present and adding new children in the right place. | |
569 | * | |
570 | * @param node the TreeNode were merging lists for. | |
571 | * @param children the current child UserObjects, in order. | |
572 | * @param newChildren the expected child UserObjects, in order. | |
573 | * @throws UnsupportedOperationException if the Iterator returned by | |
574 | * newChildren doesn't support the remove operation, or if | |
575 | * newChildren itself doesn't support remove or removeAll. | |
576 | * @throws NullPointerException if node, children or newChildren are null. | |
577 | */ | |
578 | private void mergeChildren(ExplorerTreeNode node, List children, | |
579 | List newChildren) { | |
580 | 15633 | Set removeObjects = prepareAddRemoveSets(children, newChildren); |
581 | // Remember that children are not TreeNodes but UserObjects | |
582 | 15633 | List<ExplorerTreeNode> actualNodes = new ArrayList<ExplorerTreeNode>(); |
583 | 15633 | Enumeration childrenEnum = node.children(); |
584 | 16481 | while (childrenEnum.hasMoreElements()) { |
585 | 848 | actualNodes.add((ExplorerTreeNode) childrenEnum.nextElement()); |
586 | } | |
587 | ||
588 | 15633 | int position = 0; |
589 | 15633 | Iterator childNodes = actualNodes.iterator(); |
590 | 15633 | Iterator newNodes = newChildren.iterator(); |
591 | 15633 | Object firstNew = newNodes.hasNext() ? newNodes.next() : null; |
592 | 16481 | while (childNodes.hasNext()) { |
593 | 848 | Object childObj = childNodes.next(); |
594 | 848 | if (!(childObj instanceof ExplorerTreeNode)) { |
595 | 0 | continue; |
596 | } | |
597 | ||
598 | 848 | ExplorerTreeNode child = (ExplorerTreeNode) childObj; |
599 | 848 | Object userObject = child.getUserObject(); |
600 | ||
601 | 848 | if (removeObjects.contains(userObject)) { |
602 | 0 | removeNodeFromParent(child); |
603 | } else { | |
604 | while (firstNew != null | |
605 | 930 | && order.compare(firstNew, userObject) < 0) { |
606 | 82 | insertNodeInto(new ExplorerTreeNode(firstNew, this), |
607 | node, | |
608 | position); | |
609 | 82 | position++; |
610 | 82 | firstNew = newNodes.hasNext() ? newNodes.next() : null; |
611 | } | |
612 | 848 | position++; |
613 | } | |
614 | 848 | } |
615 | ||
616 | // Add any remaining nodes | |
617 | 42545 | while (firstNew != null) { |
618 | 26912 | insertNodeInto(new ExplorerTreeNode(firstNew, this), |
619 | node, | |
620 | position); | |
621 | 26912 | position++; |
622 | 26912 | firstNew = newNodes.hasNext() ? newNodes.next() : null; |
623 | } | |
624 | 15633 | } |
625 | ||
626 | /* | |
627 | * @see javax.swing.tree.DefaultTreeModel#insertNodeInto(javax.swing.tree.MutableTreeNode, javax.swing.tree.MutableTreeNode, int) | |
628 | */ | |
629 | @Override | |
630 | public void insertNodeInto(MutableTreeNode newChild, | |
631 | MutableTreeNode parent, int index) { | |
632 | 26994 | super.insertNodeInto(newChild, parent, index); |
633 | 26994 | if (newChild instanceof ExplorerTreeNode) { |
634 | 26994 | addNodesToMap((ExplorerTreeNode) newChild); |
635 | } | |
636 | 26994 | } |
637 | ||
638 | /* | |
639 | * @see javax.swing.tree.DefaultTreeModel#removeNodeFromParent(javax.swing.tree.MutableTreeNode) | |
640 | */ | |
641 | @Override | |
642 | public void removeNodeFromParent(MutableTreeNode node) { | |
643 | 38 | if (node instanceof ExplorerTreeNode) { |
644 | 38 | removeNodesFromMap((ExplorerTreeNode) node); |
645 | 38 | ((ExplorerTreeNode) node).remove(); |
646 | } | |
647 | 38 | super.removeNodeFromParent(node); |
648 | 38 | } |
649 | ||
650 | /** | |
651 | * Map all nodes in the subtree rooted at node. | |
652 | * | |
653 | * @param node the node to be added | |
654 | */ | |
655 | private void addNodesToMap(ExplorerTreeNode node) { | |
656 | 26994 | Enumeration children = node.children(); |
657 | 26994 | while (children.hasMoreElements()) { |
658 | 0 | ExplorerTreeNode child = (ExplorerTreeNode) children.nextElement(); |
659 | 0 | addNodesToMap(child); |
660 | 0 | } |
661 | 26994 | addToMap(node.getUserObject(), node); |
662 | 26994 | } |
663 | ||
664 | /** | |
665 | * Unmap all nodes in the subtree rooted at the given node. | |
666 | * | |
667 | * @param node the given node | |
668 | */ | |
669 | private void removeNodesFromMap(ExplorerTreeNode node) { | |
670 | 38 | Enumeration children = node.children(); |
671 | 38 | while (children.hasMoreElements()) { |
672 | 0 | ExplorerTreeNode child = (ExplorerTreeNode) children.nextElement(); |
673 | 0 | removeNodesFromMap(child); |
674 | 0 | } |
675 | 38 | removeFromMap(node.getUserObject(), node); |
676 | 38 | } |
677 | ||
678 | /** | |
679 | * Adds a new tree node and model element to the map. | |
680 | * nodes are removed from the map when a {@link #modelElementRemoved(Object) | |
681 | * modelElementRemoved} event is received. | |
682 | * | |
683 | * @param modelElement the modelelement to be added | |
684 | * @param node the node to be added | |
685 | */ | |
686 | private void addToMap(Object modelElement, ExplorerTreeNode node) { | |
687 | 34510 | Set<ExplorerTreeNode> nodes = modelElementMap.get(modelElement); |
688 | ||
689 | 34510 | if (nodes != null) { |
690 | 0 | nodes.add(node); |
691 | } else { | |
692 | 34510 | nodes = new HashSet<ExplorerTreeNode>(); |
693 | 34510 | nodes.add(node); |
694 | 34510 | modelElementMap.put(modelElement, nodes); |
695 | } | |
696 | 34510 | } |
697 | ||
698 | /** | |
699 | * removes a new tree node and model element from the map. | |
700 | * | |
701 | * @param modelElement the modelelement to be removed | |
702 | * @param node the node to be removed | |
703 | */ | |
704 | private void removeFromMap(Object modelElement, ExplorerTreeNode node) { | |
705 | 38 | Collection<ExplorerTreeNode> nodes = modelElementMap.get(modelElement); |
706 | 38 | if (nodes != null) { |
707 | 38 | nodes.remove(node); |
708 | 38 | if (nodes.isEmpty()) { |
709 | 38 | modelElementMap.remove(modelElement); |
710 | } | |
711 | } | |
712 | 38 | } |
713 | ||
714 | /** | |
715 | * Node lookup for a given model element. | |
716 | * | |
717 | * @param modelElement the given modelelement | |
718 | * @return the nodes sought | |
719 | */ | |
720 | private Collection<ExplorerTreeNode> findNodes(Object modelElement) { | |
721 | 40 | Collection<ExplorerTreeNode> nodes = modelElementMap.get(modelElement); |
722 | ||
723 | 40 | if (nodes == null) { |
724 | 2 | return Collections.EMPTY_LIST; |
725 | } | |
726 | 38 | return nodes; |
727 | } | |
728 | ||
729 | /** | |
730 | * Updates the explorer for new perspectives / orderings. | |
731 | * | |
732 | * {@inheritDoc} | |
733 | */ | |
734 | public void itemStateChanged(ItemEvent e) { | |
735 | 918 | if (e.getSource() instanceof PerspectiveComboBox) { |
736 | 918 | rules = ((ExplorerPerspective) e.getItem()).getList(); |
737 | } else { // it is the combo for "order" | |
738 | 0 | order = (Comparator) e.getItem(); |
739 | } | |
740 | 918 | structureChanged(); |
741 | // TODO: temporary - let tree expand implicitly - tfm | |
742 | 918 | tree.expandPath(tree.getPathForRow(1)); |
743 | 918 | } |
744 | ||
745 | /** | |
746 | * @return Returns the nodeUpdater. | |
747 | */ | |
748 | ExplorerUpdater getNodeUpdater() { | |
749 | 814 | return nodeUpdater; |
750 | } | |
751 | ||
752 | /** | |
753 | * The UID. | |
754 | */ | |
755 | private static final long serialVersionUID = 3132732494386565870L; | |
756 | } | |
757 |