Coverage Report - org.argouml.ui.explorer.ExplorerTreeModel
 
Classes in this File Line Coverage Branch Coverage Complexity
ExplorerTreeModel
74%
167/223
60%
60/100
3.783
ExplorerTreeModel$ExplorerUpdater
96%
28/29
80%
8/10
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