1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License occured 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.exec;
  28 
  29 import java.awt.EventQueue;
  30 import java.util.Collections;
  31 import java.util.Enumeration;
  32 import java.util.Hashtable;
  33 import java.util.HashSet;
  34 import java.util.Iterator;
  35 import java.util.LinkedList;
  36 import java.util.Set;
  37 
  38 import javax.swing.tree.TreeModel;
  39 import javax.swing.tree.TreeNode;
  40 import javax.swing.tree.TreePath;
  41 import javax.swing.event.TreeModelEvent;
  42 import javax.swing.event.TreeModelListener;
  43 
  44 import com.sun.javatest.JavaTestError;
  45 import com.sun.javatest.Parameters;
  46 import com.sun.javatest.TRT_TreeNode;
  47 import com.sun.javatest.TestFilter;
  48 import com.sun.javatest.TestResult;
  49 import com.sun.javatest.TestResultTable;
  50 import com.sun.javatest.TestSuite;
  51 import com.sun.javatest.WorkDirectory;
  52 import com.sun.javatest.tool.UIFactory;
  53 import com.sun.javatest.util.Debug;
  54 import com.sun.javatest.util.DynamicArray;
  55 import com.sun.javatest.util.StringArray;
  56 
  57 import java.util.ArrayList;
  58 import java.util.Arrays;
  59 import java.util.Comparator;
  60 import java.util.logging.Logger;
  61 
  62 /**
  63  * This is the data model bridge between JT Harness core and the GUI.
  64  */// NOTE: if you are going to request the worker lock and the htLock, you
  65 //       MUST do it in that order.  Be mindful of holding the htLock and
  66 //       calling a synchronized method, that is illegal and you must
  67 //       release the htLock before making the method call.
  68 class TestTreeModel implements TreeModel, TestResultTable.TreeObserver {
  69 
  70     TestTreeModel() {
  71     }
  72 
  73     TestTreeModel(Parameters p, FilterSelectionHandler filterHandler, UIFactory uif) {
  74         this.filterHandler = filterHandler;
  75         this.uif = uif;
  76 
  77         cache = new Hashtable<>();
  78         cacheQueue = new LinkedList<>();
  79         suspendedQueue = new LinkedList<>();
  80 
  81         cacheWorker = new CacheWorker();
  82         cacheWorker.setPriority(Thread.MIN_PRIORITY + 1);
  83         cacheWorker.start();
  84 
  85         setParameters(p);
  86 
  87         watcher = new FilterWatcher();
  88         filterHandler.addObserver(watcher);
  89         lastFilter = filterHandler.getActiveFilter();
  90     }
  91 
  92     synchronized void dispose() {
  93         disposed = true;
  94 
  95         if (trt != null) {
  96             trt.removeObserver(this);
  97             trt.dispose();
  98             trt = null;
  99         }
 100 
 101         filterHandler.removeObserver(watcher);
 102 
 103         if (cacheWorker != null) {
 104             cacheWorker.interrupt();
 105             cacheWorker = null;
 106         }
 107 
 108         params = null;
 109     }
 110 
 111     TestFilter getTestFilter() {
 112         return filterHandler.getActiveFilter();
 113     }
 114 
 115     Parameters getParameters() {
 116         return params;
 117     }
 118 
 119     // TreeModel methods
 120     public void addTreeModelListener(TreeModelListener l) {
 121         treeModelListeners = DynamicArray.append(treeModelListeners, l);
 122     }
 123 
 124     public Object getChild(Object parent, int index) {
 125         if (parent instanceof TT_BasicNode) {
 126             return ((TT_BasicNode) parent).getChildAt(index);
 127         } else {
 128             return null;
 129         }
 130     }
 131 
 132     public int getChildCount(Object parent) {
 133         if (parent instanceof TreeNode) {
 134             return ((TreeNode) parent).getChildCount();
 135         } else {
 136             return -1;
 137         }
 138     }
 139 
 140     public int getIndexOfChild(Object parent, Object child) {
 141         if (parent instanceof TT_BasicNode) {
 142             return ((TT_BasicNode) parent).getIndex((TT_TreeNode) child);
 143         } else {
 144             return -1;
 145         }
 146     }
 147 
 148     public Object getRoot() {
 149         if (disposed) {
 150             if (debug > 0) {
 151                 Debug.println("TTM - getRoot() ignored, model has been disposed.");
 152             }
 153             return null;
 154         }
 155 
 156         // trt is never supposed to be null
 157         if (root == null) {
 158             root = new TT_BasicNode(null, (TRT_TreeNode) (trt.getRoot()), trt.getTestFinder().getComparator());
 159         }
 160         return root;
 161     }
 162 
 163     public boolean isLeaf(Object node) {
 164         if (node == null) {
 165             return true;
 166         }
 167 
 168         if (node instanceof TT_BasicNode) {
 169             return false;
 170         } else if (node instanceof TT_TestNode) {
 171             return true;
 172         } else {
 173             throw new IllegalArgumentException(uif.getI18NString("tree.badType"));
 174         }
 175     }
 176 
 177     public void removeTreeModelListener(TreeModelListener l) {
 178         treeModelListeners = DynamicArray.remove(treeModelListeners, l);
 179     }
 180 
 181     public void valueForPathChanged(TreePath path, Object newValue) {
 182         // XXX
 183         System.err.println(getClass().getName() + ": VFPC");
 184     }
 185 
 186     // TRT TreeObserver methods
 187     public void nodeInserted(final TestResultTable.TreeNode[] path,
 188             final Object what, final int index) {
 189 
 190         if (!EventQueue.isDispatchThread()) {
 191             Runnable t = new Runnable() {
 192 
 193                 public void run() {
 194                     nodeInserted0(path, what, index);
 195                 }
 196             };  // Runnable
 197 
 198             EventQueue.invokeLater(t);
 199         }
 200         else {
 201             nodeInserted0(path, what, index);
 202         }
 203     }
 204 
 205 
 206     private synchronized void nodeInserted0(final TestResultTable.TreeNode[] path,
 207             final Object what, final int index) {
 208         if (disposed)
 209             return;
 210 
 211         Object[] nodes = {what};
 212 
 213         TreeModelEvent tme;
 214         TT_BasicNode[] transPath = translatePath(path, true);
 215 
 216         if (transPath == null) {
 217             return; // error condition
 218         }
 219 
 220         // accomplish the primary task - insertion
 221         if (what instanceof TestResultTable.TreeNode) {
 222             /*
 223             if (!relevantNodes.contains(path[path.length - 1])) {
 224                 return;
 225             }*/
 226             TestResultTable.TreeNode tn = (TestResultTable.TreeNode)what;
 227             int[] newPositions = transPath[transPath.length-1].addNodes(new TestResultTable.TreeNode[] {tn});
 228 
 229             if (debug > 0) {
 230                 Debug.println("TTM - Node " + what + " inserting, path len=" + path.length);
 231             }
 232             if (path == null || path.length == 0) // root
 233             {
 234                 tme = new TreeModelEvent(this, new TreePath(getRoot()));
 235             } else {
 236                 tme = new TreeModelEvent(this, transPath, newPositions, nodes);
 237             }
 238             //if (cacheWorker != null && !cacheWorker.isPaused()) {
 239                 notifyModelListeners(tme, Notifier.INS);
 240             //}
 241         } else {            // test result
 242             TestResult tr = (TestResult) what;
 243 
 244             // BK remove first part of if - should not happen
 245             if (transPath != null /* && relevantNodes.contains(transPath[transPath.length - 1])*/) {
 246                 if (debug > 0) {
 247                     Debug.println("TTM - Node " + what + " inserted, path len=" + path.length);
 248                     Debug.println("   -> inserting " + tr.getTestName());
 249                     Debug.println("   -> mutable " + tr.isMutable());
 250                     Debug.println("   -> status " + tr.getStatus().getType());
 251                     Debug.println("   -> Thread: " + Thread.currentThread());
 252                 }
 253 
 254 //                if (cacheWorker != null && !cacheWorker.isPaused()) {
 255                     // BK optimize reuse of transPath var above
 256                     // notifyInserted(makeEvent(path, what, index));
 257                     processInsert(transPath, tr);
 258 //                }
 259             }
 260             else {
 261             }
 262 
 263             // send updates to parent nodes for book keeping
 264             for (int i = transPath.length - 1; i >= 0; i--) {
 265                 // walk path thru cache
 266                 TT_NodeCache ni = null;
 267 
 268                 synchronized (htLock) {
 269                     ni = cache.get(transPath[i].getTableNode());
 270                     //ni = getNodeInfo(transPath[i].getTableNode(), false);
 271                 }   // sync
 272 
 273                 // notify cache nodes
 274                 // most of the time ni would probably be null
 275                 if (ni != null) {
 276                     boolean result = false;
 277                     synchronized (ni.getNode()) {
 278                         result = ni.add(path, (TestResult) what, index);
 279                     }   // sync
 280 
 281                     if (result || relevantNodes.contains(transPath[i])) {
 282                         // if change detected, and relevant, then broadcast
 283                         // update of summary numbers is passive because it retrieves
 284                         // updates itself.
 285                         // we need to tell the tree when to update though
 286                         if (transPath[i].isRoot()) {
 287                             tme = new TreeModelEvent(this, new TreePath(transPath[i]));
 288                         } else {
 289                             tme = makeEvent(TestResultTable.getObjectPath(path[i - 1]), path[i], path[i - 1].getIndex(path[i]));
 290                         }
 291                         if (cacheWorker != null && !cacheWorker.isPaused()) {
 292                             notifyModelListeners(tme, Notifier.CHANGE);
 293                         }
 294                     }
 295                 }
 296             }   // for
 297         }
 298     }
 299 
 300     public void nodeChanged(final TestResultTable.TreeNode[] path,
 301             final Object what, final int index, final Object old) {
 302         if (!EventQueue.isDispatchThread()) {
 303             Runnable t = new Runnable() {
 304 
 305                 public void run() {
 306                     nodeChanged0(path, what, index, old);
 307                 }
 308             };  // Runnable
 309 
 310             EventQueue.invokeLater(t);
 311         }
 312         else {
 313             nodeChanged0(path, what, index, old);
 314         }
 315     }
 316 
 317     private synchronized void nodeChanged0(TestResultTable.TreeNode[] path, Object what,
 318             int index, Object old) {
 319         if (disposed)
 320             return;
 321 
 322         if (what instanceof TestResultTable.TreeNode) {
 323             // check relevance
 324             if (relevantNodes.contains(path[path.length - 1])) {
 325                 notifyModelListeners(makeEvent(path, what, index), Notifier.CHANGE);
 326                 notifyModelListeners(new TreeModelEvent(this, path, null, null), Notifier.STRUCT);
 327             }
 328         } else {            // test result
 329             if (relevantTests.contains(old) && old != what) {
 330                 relevantTests.remove(old);
 331                 relevantTests.add((TestResult) what);
 332             }
 333 
 334             TT_BasicNode[] transPath = translatePath(path, false);
 335             if (transPath != null) {
 336                 TreeModelEvent where = transPath[transPath.length-1].replaceTest((TestResult)what, false);
 337                 if (where == null && debug > 0) {
 338                     Debug.println("No test insertion pt for " + ((TestResult)what).getTestName());
 339                 }
 340             }
 341             else {
 342                 // ignored.  should log it.
 343             }
 344 
 345             for (int i = path.length - 1; i >= 0; i--) {
 346                 // walk path thru cache
 347                 TT_NodeCache ni = null;
 348 
 349                 synchronized (htLock) {
 350                     ni = cache.get(path[i]);
 351                 }   // sync
 352 
 353                 // notify cache nodes
 354                 if (ni != null) {
 355                     boolean result = ni.replace(path, (TestResult) what, index, (TestResult) old);
 356 
 357                     /* dead code
 358                     if (result && relevantNodes.contains(transPath[i])) {
 359                     // if change detected, and relevant, then broadcast
 360                     //notifyChanged(makeEvent(path, what, index));
 361                     }*/
 362                 }
 363             }   // for
 364         }
 365     }
 366 
 367     public void nodeRemoved(final TestResultTable.TreeNode[] path,
 368             final Object what, final int index) {
 369 
 370         if (!EventQueue.isDispatchThread()) {
 371             Runnable t = new Runnable() {
 372 
 373                 public void run() {
 374                     nodeRemoved0(path, what, index);
 375                 }
 376             };  // Runnable
 377 
 378             EventQueue.invokeLater(t);
 379         }
 380         else {
 381             nodeRemoved0(path, what, index);
 382         }
 383     }
 384 
 385     private synchronized void nodeRemoved0(TestResultTable.TreeNode[] path, Object what, int index) {
 386         if (disposed)
 387             return;
 388         if (what instanceof TestResultTable.TreeNode) {
 389             //if (true || relevantNodes.contains(path[path.length - 1])) {
 390             if (debug > 0) {
 391                 Debug.println("TTM - Node " + what + " removed, path len=" + path.length);
 392             }
 393 
 394             TT_BasicNode[] transPath = translatePath(path, false);
 395             TestResultTable.TreeNode node = (TestResultTable.TreeNode)what;
 396             TT_TreeNode tn = transPath[transPath.length-1].findByName(node.getName());
 397             int where = transPath[transPath.length-1].removeNode(tn);
 398 
 399             if (where >= 0) {
 400                 TreeModelEvent tme = new TreeModelEvent(this, transPath, new int[] {where},
 401                         new Object[] {tn});
 402                 notifyModelListeners(tme, Notifier.DEL);
 403             }
 404         } else {            // test result
 405             TestResult tr = (TestResult) what;
 406             TT_BasicNode[] transPath = translatePath(path,false);
 407 
 408             relevantTests.remove(tr);
 409 
 410             //if (debug > 0 && relevantNodes.contains(transPath[transPath.length - 1])) {
 411             if (debug > 0) {
 412                 //if (debug > 0) {
 413                 Debug.println("TTM - Node " + what + " removed, path len=" + path.length);
 414                 Debug.println("    -> Removing " + tr.getTestName());
 415                 Debug.println("    -> Thread: " + Thread.currentThread());
 416             }
 417 
 418             processRemove(transPath, tr, index);
 419 
 420             for (int i = path.length - 1; i >= 0; i--) {
 421                 // walk path thru cache
 422                 TT_NodeCache ni = null;
 423 
 424                 synchronized (htLock) {
 425                     ni = cache.get(path[i]);
 426                 }   // sync
 427 
 428                 // notify cache nodes
 429                 if (ni != null) {
 430                     // need to lock node before locking the cache node
 431                     boolean result = false;
 432                     synchronized (ni.getNode()) {
 433                         result = ni.remove(path, (TestResult) what, index);
 434                     }   // sync
 435                 }
 436             }   // for
 437         }
 438     }
 439 
 440     //  ----- private -----
 441     private void processRemove(final TT_BasicNode[] path, final TestResult tr,
 442             final int index) {
 443         if (!EventQueue.isDispatchThread()) {
 444             Runnable t = new Runnable() {
 445 
 446                 public void run() {
 447                     processRemove0(path, tr, index);
 448                 }
 449             };  // Runnable
 450 
 451             EventQueue.invokeLater(t);
 452         }
 453         else {
 454             processRemove0(path, tr, index);
 455         }
 456     }
 457 
 458     private void processRemove0(TT_BasicNode[] path, TestResult tr, int index) {
 459         //TT_TestNode tn = path[path.length - 1].findByName(tr);
 460         TreeModelEvent tme = path[path.length - 1].removeTest(tr);
 461         //if (tn == null || pos < 0) {
 462         if (tme == null) {
 463             return;
 464         }
 465 
 466         for (int i = 0; i < treeModelListeners.length; i++) {
 467             treeModelListeners[i].treeNodesRemoved(tme);
 468         }
 469     }
 470 
 471     private void processInsert(final TT_BasicNode[] path, final TestResult tr) {
 472         if (!EventQueue.isDispatchThread()) {
 473             Runnable t = new Runnable() {
 474 
 475                 public void run() {
 476                     processInsert0(path, tr);
 477                 }
 478             };  // Runnable
 479 
 480             EventQueue.invokeLater(t);
 481         } else {
 482             processInsert0(path, tr);
 483         }
 484     }
 485 
 486     private void processInsert0(TT_BasicNode[] path, TestResult tr) {
 487         TT_TestNode newNode = new TT_TestNode(path[path.length - 1], tr);
 488         //int pos = path[path.length - 1].addTest(newNode, sortComparator);
 489         TreeModelEvent tme = path[path.length - 1].addTest(newNode, sortComparator);
 490         if (tme == null) return;
 491 
 492         for (int i = 0; i < treeModelListeners.length; i++) {
 493             treeModelListeners[i].treeNodesInserted(tme);
 494         }
 495 
 496     }
 497 
 498     TreePath resolveUrl(String path) {
 499         if (path == null || path.length() == 0 || root == null) {
 500             return null;
 501         }
 502         ArrayList<TT_TreeNode> al = new ArrayList<>();
 503         al.add(root);
 504         TT_BasicNode spot = root;
 505 
 506         StringBuffer sb = new StringBuffer(path);
 507 
 508         while (sb.length() > 0) {
 509             int slash = sb.indexOf("/");
 510             String current = null;
 511             if (slash < 0) {
 512                 current = sb.toString();
 513                 sb.setLength(0);
 514             } else {
 515                 current = sb.substring(0, slash);
 516                 sb = sb.delete(0, slash+1);
 517             }
 518 
 519             TT_TreeNode node = spot.findByName(current);
 520 
 521             if (node == null) {
 522                 return null;
 523             } else if (node instanceof TT_BasicNode) {
 524                 al.add(node);
 525                 spot = (TT_BasicNode) node;
 526             } else if (node instanceof TT_TestNode) {
 527                 al.add(node);
 528                 sb.setLength(0);
 529             }
 530         }
 531 
 532         return new TreePath(al.toArray());
 533     }
 534 
 535     void addRelevantNode(TT_TreeNode node) {
 536         relevantNodes.add(node);
 537     }
 538 
 539     void removeRelevantNode(TT_TreeNode node) {
 540         relevantNodes.remove(node);
 541     }
 542 
 543     void addRelevantTest(TestResult tr) {
 544         relevantTests.add(tr);
 545     }
 546 
 547     void removeRelevantTest(TestResult tr) {
 548         if (relevantTests != null && tr != null) {
 549             relevantTests.remove(tr);
 550         }
 551     }
 552 
 553     /**
 554      * Hint that the given node is of more importance than others.
 555      */
 556     void setActiveNode(TT_BasicNode node) {
 557         TT_NodeCache ni = null;
 558 
 559         synchronized (htLock) {
 560             ni = cache.get(node.getTableNode());
 561         }
 562 
 563         if (ni != null) {
 564             cacheWorker.requestActiveNode(ni);
 565         } else {
 566             // uh...why don't we have a node?
 567             // create and schedule?
 568         }
 569     }
 570 
 571     /**
 572      * Create an event from the raw TRT data, so it can be passed to the GUI tree.
 573      */
 574     private TreeModelEvent makeEvent(TestResultTable.TreeNode[] path, Object target, int index) {
 575         if (getRoot() == null) {
 576             return null;
 577         }
 578         int[] inds = {index};
 579         // per the TreeModelEvent javadoc, the path is supposed to lead to the parent of the
 580         // changed node
 581 
 582         TT_BasicNode[] transPath = translatePath(path, false);
 583 
 584         TT_TreeNode[] transTarget = new TT_TreeNode[1];
 585         if (target instanceof TestResult &&
 586                 transPath[transPath.length - 1] instanceof TT_BasicNode) {
 587             TT_TestNode mtn = transPath[transPath.length - 1].findByName((TestResult) target);
 588             if (mtn != null) {
 589                 transTarget[0] = mtn;
 590             } else {
 591                 return null;    // no matching test
 592             }
 593         } else {
 594             TT_TreeNode mtn = transPath[transPath.length - 1].findByName(
 595                     ((TestResultTable.TreeNode) target).getName());
 596             if (mtn != null) {
 597                 transTarget[0] = mtn;
 598             } else {
 599                 return null;    // no matching test
 600             }
 601         }
 602 
 603         if (debug > 1) {
 604             Debug.println("TTM Broadcasing " + target + " change message...");
 605             //Debug.println("   -> Status = " + ((TestResult)target).getStatus().toString());
 606             Debug.println("   -> Path len=" + transPath.length);
 607             Debug.println("   -> Index = " + index);
 608             Debug.println("   -> target = " + Arrays.toString(transTarget));
 609         }
 610 
 611         return new TreeModelEvent(this, transPath, inds, transTarget);
 612     }
 613 
 614     TT_BasicNode[] translatePath(TestResultTable.TreeNode[] path, boolean create) {
 615         if (path == null)
 616             return null;
 617 
 618         TT_BasicNode location = (TT_BasicNode)getRoot();
 619         TT_BasicNode[] transPath = new TT_BasicNode[path.length];
 620         transPath[0] = location;
 621 
 622         for (int i = 1; i < path.length; i++) {
 623             TT_TreeNode mnode = location.findByName(path[i].getName());
 624             if (create && mnode == null) {
 625                 location.addNodes(new TestResultTable.TreeNode[] {path[i]});
 626                 mnode = location.findByName(path[i].getName());
 627             }
 628             else if (!(mnode instanceof TT_BasicNode)) {
 629                 return null; // ignore event
 630             }
 631             transPath[i] = (TT_BasicNode) mnode;
 632             location = transPath[i];
 633         }
 634 
 635         return transPath;
 636     }
 637 
 638     String[] pathsToStrings(TreePath[] paths) {
 639         if (paths == null || paths.length == 0 ||
 640             !(getRoot() instanceof TT_BasicNode))
 641             return null;
 642 
 643         String[] result = new String[paths.length];
 644         for (int i = 0; i < paths.length; i++) {
 645             TT_TreeNode tn = (TT_TreeNode)(paths[i].getLastPathComponent());
 646             result[i] = tn.getLongPath();
 647         }
 648 
 649         return result;
 650     }
 651 
 652     TreePath[] urlsToPaths(String[] urls) {
 653         ArrayList<TreePath> result = new ArrayList<>();
 654 
 655         for (int i = 0; i < urls.length; i++) {
 656             TreePath thisOne = urlToPath(urls[i]);
 657             if (thisOne == null)
 658                 continue;   // skipped for some reason
 659             else
 660                 result.add(thisOne);
 661         }
 662 
 663         TreePath[] res = new TreePath[result.size()];
 664         result.toArray(res);
 665         return res;
 666     }
 667 
 668     TreePath urlToPath(String url) {
 669         if (url == null)
 670             return null;
 671 
 672         String[] urlPath = StringArray.splitList(url, "/");
 673         TT_TreeNode[] transPath = new TT_TreeNode[urlPath.length+1];
 674         TT_BasicNode location = (TT_BasicNode)getRoot();
 675         transPath[0] = location;
 676 
 677         for (int i = 0; i < urlPath.length; i++) {
 678             if (location == null)
 679                 return null;    // special exit condition for the loop
 680 
 681             TT_TreeNode mnode = location.findByName(urlPath[i]);
 682             if (mnode == null) {
 683                 return null;
 684             }
 685 
 686             transPath[i+1] = mnode;
 687             location = (transPath[i+1] instanceof TT_BasicNode ?
 688                         (TT_BasicNode)transPath[i+1] : null);
 689         }
 690 
 691         return new TreePath(transPath);
 692     }
 693 
 694     void setParameters(Parameters p) {
 695         if (p != null) {
 696             params = p;
 697             init();
 698         } else {
 699             TestResultTable dummy = new TestResultTable();
 700             setTestResultTable(dummy);
 701 
 702             if (debug > 0) {
 703                 Debug.println("TTM - dummy TRT, root = " + dummy.getRoot());
 704             }
 705         }
 706     }
 707 
 708     synchronized private void init() {
 709         if (params == null) {
 710             return;
 711         }
 712         WorkDirectory wd = params.getWorkDirectory();
 713         TestSuite ts = params.getTestSuite();
 714 
 715         if (wd != null) {       // full config w/TS and WD
 716             if (debug > 0) {
 717                 Debug.println("TTM - initializing with workdir");
 718             }
 719             log = getLog(wd);
 720             TestResultTable newTrt = wd.getTestResultTable();
 721             try {
 722                 newTrt.getLock().lock();
 723                 setTestResultTable(newTrt);
 724             } finally {
 725                 newTrt.getLock().unlock();
 726             }
 727 
 728             sortComparator = newTrt.getTestFinder().getComparator();
 729         } else if (ts != null) {        // TS, but no workdir
 730 
 731             if (trt != null && trt.getTestFinder() == null) {
 732                 // configure a finder on the temporary TRT, this allows us to
 733                 // populate the table before we have a workdir
 734                 try {
 735                     trt.getLock().lock();
 736                     trt.setTestFinder(ts.getTestFinder());
 737                     setTestResultTable(trt);
 738                 } finally {
 739                     trt.getLock().unlock();
 740                     sortComparator = trt.getTestFinder().getComparator();
 741 
 742                 }
 743 
 744                 if (debug > 0) {
 745                     Debug.println("TTM - params set, no WD; setting finder on temp. TRT");                //notifyFullStructure();
 746                 }
 747             } else {
 748                 // already have a finder
 749                 if (debug > 0) {
 750                     Debug.println("TTM - temp. TRT already has finder");
 751                 }
 752             }
 753 
 754 
 755         } // no WD or TS
 756         else {
 757             if (debug > 0) {
 758                 Debug.println("TTM - params set, no WD or TS");            // should we explicitly reset the model?
 759             }
 760         }
 761     }
 762 
 763     void pauseWork() {
 764         cacheWorker.setPaused(true);
 765         // should consider synchronizing with cacheWorker before checking
 766         // condition
 767         while (cacheWorker != null && cacheWorker.isAlive() && !cacheWorker.stopping && !cacheWorker.isReallyPaused) {
 768             try {
 769                 synchronized (cacheWorker) {
 770                     cacheWorker.notifyAll();
 771                     cacheWorker.wait();
 772                 }
 773             } catch (InterruptedException e) {
 774             }
 775         }
 776     }
 777 
 778     void unpauseWork() {
 779         if (cacheWorker == null) {
 780             return;
 781         }
 782 
 783         invalidateNodeInfo();
 784         cacheWorker.setPaused(false);
 785         synchronized (cacheWorker) {
 786             cacheWorker.notifyAll();
 787         }
 788     }
 789 
 790     boolean isWorkPaused() {
 791         return cacheWorker.isPaused();
 792     }
 793 
 794     /**
 795      * This method will attempt to swap the table if compatible to avoid a full JTree
 796      * swap.
 797      */
 798     private void setTestResultTable(TestResultTable newTrt) {
 799         // NOTE: may want to run this on when we figure out how important params are
 800         // see note in setParameters()
 801         //if (params == null)
 802         //    throw IllegalStateException();
 803 
 804         // assumptions:
 805         // - a model must have a non-null TRT
 806         if (isCompatible(trt, newTrt)) {
 807             swapTables(newTrt);
 808         } else {
 809             if (trt != null) {
 810                 trt.removeObserver(this);
 811             }
 812             trt = newTrt;
 813             trt.addObserver(this);
 814         }
 815 
 816         root = new TT_BasicNode(null, (TRT_TreeNode) (trt.getRoot()),
 817                 (trt.getTestFinder() == null ? null : trt.getTestFinder().getComparator()));
 818 
 819         // prime relevant nodes with root and first level
 820         relevantNodes = Collections.synchronizedSet(new HashSet<TT_TreeNode>());
 821         relevantTests = Collections.synchronizedSet(new HashSet<TestResult>());
 822 
 823         addRelevantNode((TT_TreeNode) getRoot());
 824         TT_BasicNode tn = ((TT_BasicNode) getRoot());
 825         for (int i = 0; i < ((TT_BasicNode) getRoot()).getChildCount(); i++) {
 826             addRelevantNode((TT_TreeNode) (tn.getChildAt(i)));
 827         }
 828         notifyFullStructure();
 829 
 830         if (debug > 0) {
 831             Debug.println("TTM - Model watching " + trt);
 832             if (trt.getWorkDir() != null) {
 833                 Debug.println("   -> Workdir=" + trt.getWorkDir());
 834                 Debug.println("   -> Workdir path=" + trt.getWorkDir().getPath());
 835                 Debug.println("   -> root = " + trt.getRoot());
 836             }
 837         }
 838     }
 839 
 840     static boolean isCompatible(TestResultTable t1, TestResultTable t2) {
 841         // assumptions:
 842         // - a TRT must have a finder and testsuite root to be comparable
 843         // - the finder class objects must be equal for TRTs to be compatible
 844         // - testsuite root paths must be equal
 845         if (t1 == null || t2 == null ||
 846                 t1.getTestSuiteRoot() == null || t2.getTestSuiteRoot() == null ||
 847                 t1.getTestFinder() == null || t2.getTestFinder() == null) {
 848             if (debug > 1) {
 849                 Debug.println("TTM - isCompatible() false because one or both TRTs are incomplete.");
 850                 if (t1 != null && t2 != null) {
 851                     Debug.println("t1 root = " + t1.getTestSuiteRoot());
 852                     Debug.println("t2 root = " + t2.getTestSuiteRoot());
 853                     Debug.println("t1 finder= " + t1.getTestFinder());
 854                     Debug.println("t2 finder= " + t2.getTestFinder());
 855                 }
 856             }
 857 
 858             return false;
 859         } else if (!t1.getTestSuiteRoot().getPath().equals(t2.getTestSuiteRoot().getPath())) {
 860             if (debug > 1) {
 861                 Debug.println("TTM - isCompatible() failed because testsuite paths differ.");
 862             }
 863             return false;
 864         } else if (t1.getTestFinder() != t2.getTestFinder()) {
 865             if (debug > 1) {
 866                 Debug.println("TTM - isCompatible() failed because TestFinders differ.");
 867             }
 868             return false;
 869         } else {
 870             return true;
 871         }
 872     }
 873 
 874     TestResultTable getTestResultTable() {
 875         return trt;
 876     }
 877 
 878     /**
 879      * Retrieve the info about the given node.  The returned info object may be
 880      * either a running thread or a finished thread, depending on whether recent
 881      * data is available.
 882      * @param node The node to get information for.
 883      * @param highPriority Should the task of retrieving this information be
 884      *        higher than normal.
 885      */
 886     TT_NodeCache getNodeInfo(TestResultTable.TreeNode node, boolean highPriority) {
 887         TT_NodeCache ni = null;
 888         boolean wakeWorker = false;
 889 
 890         // this line is outside the synchronized zone to prevent a
 891         // deadlock when a filter change occurs and the cache is
 892         // invalidated (on the event thread)
 893         TestFilter activeFilter = filterHandler.getActiveFilter();
 894 
 895         synchronized (htLock) {
 896             ni = cache.get(node);
 897 
 898             if (ni == null) {
 899                 ni = new TT_NodeCache(node, activeFilter, log);
 900                 cache.put(node, ni);
 901 
 902                 if (!highPriority) // high pri. case below
 903                 {
 904                     cacheQueue.addFirst(ni);
 905                 }
 906                 wakeWorker = true;      // because this is a new node
 907             } else if (highPriority) {
 908                 // reorder for high priority
 909                 // could cancel current task, but that would complicate things
 910                 int index = cacheQueue.indexOf(ni);
 911 
 912                 if (index >= 0) {
 913                     cacheQueue.remove(index);
 914                 }
 915             }
 916         }   // synchronized
 917 
 918         // this is done outside the htLock block to ensure proper locking
 919         // order.
 920         if (highPriority) {
 921             cacheWorker.requestActiveNode(ni);
 922             wakeWorker = true;
 923         }
 924 
 925         // forward info for persistent storage and later use...
 926         if (!statsForwarded && node.isRoot() && ni.isComplete()) {
 927             int[] stats = ni.getStats();
 928             int total = 0;
 929 
 930             for (int i = 0; i < stats.length; i++) {
 931                 total += stats[i];
 932             }
 933             total += ni.getRejectCount();
 934 
 935             if (params != null) {
 936                 WorkDirectory wd = params.getWorkDirectory();
 937 
 938                 if (wd != null) {
 939                     wd.setTestSuiteTestCount(total);
 940                     statsForwarded = true;
 941                 }
 942             }
 943         }
 944 
 945         if (wakeWorker) {
 946             synchronized (cacheWorker) {
 947                 cacheWorker.notify();
 948             }
 949         }
 950         return ni;
 951     }
 952 
 953     /**
 954      * Invalidate any collected data about any of the nodes on the given path.
 955      * This operation includes stopping any threads scanning for info and
 956      * removing the entries from the cache.
 957      *
 958      * @param path Nodes to invalidate in the cache, must not be null.
 959      *             May be length zero.
 960      */
 961     void invalidateNodeInfo(TestResultTable.TreeNode[] path) {
 962         // do this as a batch
 963         // we can reverse the sync and for if stalls are noticeable
 964         synchronized (htLock) {
 965             for (int i = 0; i < path.length; i++) {
 966                 TT_NodeCache info = cache.get(path[i]);
 967 
 968                 if (info != null) {
 969                     if (debug > 1) {
 970                         Debug.println("TTM - halting thread and removed from node cache");
 971                         Debug.println("   -> " + path[i]);
 972                         Debug.println("   -> " + info);
 973                     }
 974 
 975                     info.halt();
 976                     cache.remove(info.getNode());
 977                     boolean wasInQueue = cacheQueue.remove(info);
 978 
 979                     info = new TT_NodeCache(info.getNode(), filterHandler.getActiveFilter(), log);
 980                     cache.put(info.getNode(), info);
 981                     cacheQueue.addFirst(info);
 982                 }
 983             }   // for
 984         }   // synchronized
 985 
 986         // wake up the worker
 987         synchronized (cacheWorker) {
 988             cacheWorker.notify();
 989         }
 990     }
 991 
 992     /**
 993      * Invalidate all collected node information.
 994      * This is likely used when the internal contents of the parameters have
 995      * changed.  If the cache is not invalidated at certain points, rendering
 996      * of the tree may be incorrect.
 997      */
 998     void invalidateNodeInfo() {
 999         synchronized (htLock) {
1000             Enumeration<TestResultTable.TreeNode> e = cache.keys();
1001             while (e.hasMoreElements()) {
1002                 (cache.get(e.nextElement())).invalidate();
1003             }   // while
1004 
1005             cache = new Hashtable<>();
1006             cacheQueue = new LinkedList<>();
1007             suspendedQueue = new LinkedList<>();
1008         }
1009 
1010         // reprocess any needed nodes
1011         Iterator<TT_TreeNode> it = relevantNodes.iterator();
1012         while (it.hasNext()) {
1013             TT_TreeNode tn = it.next();
1014             if (tn instanceof TT_BasicNode) {
1015                 getNodeInfo(((TT_BasicNode) tn).getTableNode(), false);
1016             }
1017         }       // while
1018     }
1019 
1020     /**
1021      * Invalidate any collected data about the given node.
1022      * This operation includes stopping any thread scanning for info and
1023      * removing the entry from the cache.
1024      *
1025      * @param node The node to invalidate in the cache.  Must not be null.
1026      * @deprecated The cache will be smart enough to not need this.
1027      */
1028      void invalidateNodeInfo(TestResultTable.TreeNode node) {
1029         invalidateNodeInfo(new TestResultTable.TreeNode[]{node});
1030     }
1031 
1032     /**
1033      * Trusting method which assumes that the given TRT is compatible with the
1034      * current one.  Strange things will happen if it is not!
1035      */
1036     void swapTables(TestResultTable newTrt) {
1037         if (newTrt == trt || newTrt == null) {
1038             return;
1039         }
1040         if (debug > 1) {
1041             Debug.println("Swapping TRTs under the covers.");
1042             Debug.println("   -> OLD=" + trt);
1043             Debug.println("   -> NEW=" + newTrt);
1044         }
1045 
1046         trt.removeObserver(this);
1047         trt = newTrt;
1048         trt.addObserver(this);
1049     }
1050 
1051     private void notifyModelListeners(TreeModelEvent e, int eType) {
1052         if (treeModelListeners != null) {
1053             Notifier n = new Notifier(eType, treeModelListeners, e, uif);
1054             if (!EventQueue.isDispatchThread()) {
1055                 EventQueue.invokeLater(n);
1056             }
1057             else {
1058                 n.run();
1059             }
1060         }
1061     }
1062 
1063 
1064     void notifyFullStructure() {
1065         if (debug > 0) {
1066             Debug.println("TTM - sending full structure change event to model listeners.");
1067         }
1068         invalidateNodeInfo();
1069 
1070         Object[] path = {getRoot()};
1071         TreeModelEvent e = new TreeModelEvent(this, path);
1072         notifyModelListeners(e, Notifier.STRUCT);
1073     }
1074 
1075     protected void finalize() throws Throwable {
1076         super.finalize();
1077 
1078         if (trt != null) {
1079             trt.removeObserver(this);
1080         }
1081     }
1082 
1083     private Logger getLog(WorkDirectory wd) {
1084         Logger log = null;
1085         final String logName = uif.getI18NString("tree.log.name");
1086         try {
1087             log = wd.getTestSuite().createLog(wd, null, logName);
1088         }
1089         catch (TestSuite.DuplicateLogNameFault f) {
1090             try {
1091                 log = wd.getTestSuite().getLog(wd, logName);
1092             }
1093             catch (TestSuite.NoSuchLogFault f2) { }
1094         }
1095         return log;
1096     }
1097 
1098     private TT_BasicNode root;
1099     private UIFactory uif;
1100     private TestResultTable trt;
1101     private Parameters params;
1102     private FilterSelectionHandler filterHandler;
1103     private TestFilter lastFilter;
1104     private Comparator<String> sortComparator;
1105     private TreeModelListener[] treeModelListeners = new TreeModelListener[0];
1106     private boolean statsForwarded;
1107     private boolean disposed;
1108     private CacheWorker cacheWorker;
1109     private FilterWatcher watcher;
1110     private Set<TT_TreeNode> relevantNodes;
1111     private Set<TestResult> relevantTests;  // not used anymore
1112 
1113     private Logger log;
1114     /**
1115      * Stores state info about individual nodes.
1116      * The key is a TestResultTable.TreeNode, and the value is a TT_NodeCache
1117      * object.  Use the htLock when accessing the cache.
1118      */
1119     protected Hashtable<TestResultTable.TreeNode, TT_NodeCache> cache;
1120     /**
1121      * Queue of items to be processed.
1122      * "First" is the most recently added, "last" is the next to be processed.
1123      */
1124     protected LinkedList<TT_NodeCache> cacheQueue;
1125     /**
1126      * Queue of items which are in the middle of being processed.
1127      * "First" is the most recently added, "last" is the next to be processed.
1128      */
1129     protected LinkedList<TT_NodeCache> suspendedQueue;
1130     protected final Object htLock = new Object();
1131     private static final int CACHE_THREAD_PRI = Thread.MIN_PRIORITY;
1132     private static final int CACHE_NOTI_THR_PRI = Thread.MIN_PRIORITY;
1133     protected static int debug = Debug.getInt(TestTreeModel.class);
1134 
1135     // ************** inner classes ***************
1136     private class CacheWorker extends Thread {
1137 
1138         CacheWorker() {
1139             super("Test Tree Cache Worker");
1140         }
1141 
1142         public void run() {
1143             try {
1144                 synchronized (CacheWorker.this) {
1145                     wait();
1146                 }
1147             } catch (InterruptedException e) {
1148                 // we're terminated I guess
1149                 return;
1150             }
1151 
1152             while (!stopping) {
1153                 if (paused) {
1154                     try {
1155                         synchronized (CacheWorker.this) {
1156                             isReallyPaused = true;
1157                             notifyAll();
1158                             wait();
1159                             isReallyPaused = false;
1160                         }
1161                         continue;
1162                     } catch (InterruptedException e) {
1163                         // we're terminated I guess
1164                         stopping = true;
1165                         isReallyPaused = false;
1166                         continue;
1167                     }                // poll the queue for work to do
1168                 }
1169                 currentUnit = getNextUnit();
1170 
1171                 // nothing to do, wait
1172                 if (currentUnit == null) {
1173                     try {
1174                         synchronized (CacheWorker.this) {
1175                             notifyAll();
1176                             wait();
1177                         }
1178                         continue;
1179                     } // try
1180                     catch (InterruptedException e) {
1181                         // we're terminated I guess
1182                         stopping = true;
1183                         continue;
1184                     }   // catch
1185                 } else {
1186                     if (!currentUnit.canRun()) {
1187                         //throw new IllegalStateException("cache malfunction - attempting to re-populate node  " + currentUnit.isPaused() + "   " + currentUnit.isValid() + "  " + currentUnit.isComplete());
1188                         continue;
1189                     }
1190 
1191                     // do the work
1192                     if (debug > 0) {
1193                         Debug.println("TTM cache processing " + currentUnit.getNode().getName());
1194                     }
1195                     currentUnit.run();
1196 
1197                     if (!currentUnit.isPaused() && currentUnit.isValid()) {
1198                         finishJob(currentUnit);
1199                     }
1200                 }
1201             }   // while
1202 
1203             this.currentUnit = null;
1204             this.priorityUnit = null;
1205 
1206         }
1207 
1208         /**
1209          * Only call this to terminate all cache activity.
1210          * There is currently no way to restart the cache.
1211          */
1212         /* should not override Thread.interrupt
1213         public void interrupt() {
1214         stopping = true;
1215         }
1216          */
1217         /**
1218          * Find out which node is currently being processed.
1219          * @return The node which is currently being worked on, null if no work is
1220          *         in progress.
1221          */
1222         public TT_NodeCache getActiveNode() {
1223             return currentUnit;
1224         }
1225 
1226         void setPaused(boolean state) {
1227             synchronized (this) {
1228                 if (state != paused) {
1229                     paused = state;
1230                     if (paused && currentUnit != null) {
1231                         currentUnit.pause();
1232                         suspendedQueue.addFirst(currentUnit);
1233                     }
1234 
1235                     // wake up
1236                     if (!paused) {
1237                         this.notify();
1238                     }
1239                 }
1240             }
1241         }
1242 
1243         synchronized boolean isPaused() {
1244             return paused;
1245         }
1246         // ------- private -------
1247         /**
1248          * Request that the worker process the given unit ASAP.
1249          * This method will first verify that the given node is a candidate for
1250          * work.  This method is synchronized to avoid context switching into
1251          * other parts of this class.
1252          * @param what Which node to give attention to.
1253          */
1254         synchronized void requestActiveNode(TT_NodeCache what) {
1255             if (what != null && currentUnit != what && what.canRun()) {
1256                 // This will cause processing on the worker thread to terminate
1257                 // and it will do selection via getNextUnit().  That will return
1258                 // the priority unit below.
1259                 priorityUnit = what;
1260                 if (currentUnit != null) {
1261                     currentUnit.pause();
1262                     suspendedQueue.addFirst(currentUnit);
1263                 }
1264             }
1265         }
1266 
1267         /**
1268          * From the available queues to be processed, select the most ripe.
1269          */
1270         private TT_NodeCache getNextUnit() {
1271             boolean wasPriority = false;
1272             TT_NodeCache next = null;
1273 
1274             synchronized (htLock) {
1275                 // schedule next node in this priority:
1276                 // 1) a priority unit
1277                 // 2) a previously suspended unit
1278                 // 3) the next unit in the queue
1279                 if (priorityUnit != null) {
1280                     wasPriority = true;
1281                     next = priorityUnit;
1282                     priorityUnit = null;
1283                 } else {
1284                     switch (SCHEDULING_ALGO) {
1285                         case QUEUE:
1286                             next = selectByQueuing();
1287                             break;
1288                         case DEPTH:
1289                             next = selectByDepth();
1290                             break;
1291                         default:
1292                             next = selectByQueuing();
1293                     }
1294                 }
1295 
1296                 // additional selection criteria
1297                 if (next != null && !next.canRun()) // discard unrunnable jobs
1298                 {
1299                     return getNextUnit();
1300                 } else // ok, schedule this one
1301                 {
1302                     return next;
1303                 }
1304             }   // sync
1305         }
1306 
1307         // -------- various scheduling algorithms ----------
1308         /**
1309          * Simply do it in the order the requests came in.
1310          * The root is excluded from selection until the last possible
1311          * time.
1312          */
1313         private TT_NodeCache selectByQueuing() {
1314             TT_NodeCache selection = null;
1315 
1316             if (suspendedQueue.size() > 0) {
1317                 selection = suspendedQueue.removeLast();
1318             } else if (cacheQueue.size() > 0) {
1319                 selection = cacheQueue.removeLast();
1320             }
1321             if (selection != null &&
1322                     selection.getNode().isRoot() && // trying to avoid root
1323                     (cacheQueue.size() > 0 || suspendedQueue.size() > 0)) {
1324                 cacheQueue.addFirst(selection);
1325                 return selectByQueuing();
1326             }
1327 
1328             return selection;
1329         }
1330 
1331         /**
1332          * Select the deepest node in the cacheQueue.
1333          */
1334         private TT_NodeCache selectByDepth() {
1335             // note must already have htLock
1336             // note with this algo., the suspend and cache queues are
1337             //      basically equivalent
1338             TT_NodeCache selected = null;
1339             int depth = -1;
1340             LinkedList<TT_NodeCache> theList = cacheQueue;
1341             boolean notDone = true;
1342             int count = 0;
1343 
1344             if (cacheQueue.size() == 0) {
1345                 if (suspendedQueue.size() == 0) {
1346                     notDone = false;
1347                 } else {
1348                     theList = suspendedQueue;
1349                 }
1350             } else {
1351             }
1352 
1353             while (notDone) {
1354                 TT_NodeCache possible = (theList.get(count));
1355                 int thisDepth = TestResultTable.getObjectPath(possible.getNode()).length;
1356 
1357                 if (thisDepth > depth) {
1358                     theList.remove(count);
1359 
1360                     // requeue the last deepest node found
1361                     if (selected != null) {
1362                         cacheQueue.addFirst(selected);
1363                         // adjust the counter since we just added one
1364                         if (theList == cacheQueue) {
1365                             count++;
1366                         }
1367                     }
1368                     depth = thisDepth;
1369                     selected = possible;
1370                 }
1371 
1372                 count++;
1373                 if (count >= theList.size()) {
1374                     if (theList == suspendedQueue) {
1375                         notDone = false;
1376                     } else if (suspendedQueue.size() != 0) {
1377                         theList = suspendedQueue;
1378                         count = 0;
1379                     } else {
1380                         notDone = false;
1381                     }
1382                 } else {
1383                 }
1384             }   // while
1385 
1386             return selected;
1387         }
1388 
1389         private synchronized void finishJob(TT_NodeCache item) {
1390             // do not send update message if it's not important (onscreen)
1391             /* if (true || !relevantNodes.contains(item.getNode())) {
1392                 return;
1393             }*/
1394             TreeModelEvent e = null;
1395             TestResultTable.TreeNode node = item.getNode();
1396 
1397             // switch event format if the node is the root
1398             if (node.isRoot() && getRoot() != null) {
1399                 e = new TreeModelEvent(this, new Object[]{getRoot()}, null, null);
1400             } else {
1401                 // full path to the node, inclusive
1402                 //TestResultTable.TreeNode[] fp = TestResultTable.getObjectPath(node);
1403 
1404                 // partial path - for JTree event
1405                 //TestResultTable.TreeNode[] pp = new TestResultTable.TreeNode[fp.length - 1];
1406                 //System.arraycopy(fp, 0, pp, 0, fp.length - 1);
1407                 TreePath path = resolveUrl(TestResultTable.getRootRelativePath(node));
1408                 if (path != null) {
1409                     // index in parent of target node - required by JTree
1410                     // ignore this operation if we are at the root (length==1)
1411                     TreePath pp = path.getParentPath();
1412                     TT_BasicNode bn = (TT_BasicNode)(path.getLastPathComponent());
1413                     int index = ((TT_BasicNode)(pp.getLastPathComponent())).getIndex(bn);
1414 
1415                     //e = makeEvent(pp, bn, index);
1416                     e = new TreeModelEvent(this, pp, new int[] {index}, new Object[] {bn});
1417                 }
1418             }
1419 
1420             if (e != null) {
1421                 // dispatch event
1422                 notifyModelListeners(e, Notifier.CHANGE);
1423 
1424                 if (debug > 0) {
1425                     Debug.println("NodeCache done for " + item.getNode().getName());
1426                 }
1427             }
1428         }
1429         private volatile boolean paused;
1430         private volatile boolean stopping;
1431         private volatile TT_NodeCache priorityUnit;
1432         private volatile TT_NodeCache currentUnit;
1433         private volatile boolean isReallyPaused;
1434         private static final int QUEUE = 0;
1435         private static final int DEPTH = 1;
1436         private static final int SCHEDULING_ALGO = DEPTH;
1437     }
1438 
1439     // inner class
1440     /**
1441      * Object used to physically dispatch any model update events onto the event thread.
1442      */
1443     private static class Notifier implements Runnable {
1444 
1445         /**
1446          * Create a event notification object to be scheduled on the GUI event thread.
1447          * The type translates to a switch between the different possible observer
1448          * methods.
1449          *
1450          * @param eType Type of observer message to generate.
1451          *        Must not be greater than zero, see the defined constants.
1452          * @param listeners The listeners to notify.  This is shallow copied immediately.
1453          *        Must not be null.  May be of zero length.
1454          * @param e The event to give to the listeners.
1455          *        Must not be null.
1456          */
1457         Notifier(int eType, TreeModelListener[] listeners, TreeModelEvent e,
1458                 UIFactory uif) {
1459             type = eType;
1460             this.e = e;
1461             this.uif = uif;
1462 
1463             // make shallow copy
1464             TreeModelListener[] copy = new TreeModelListener[listeners.length];
1465             System.arraycopy(listeners, 0, copy, 0, listeners.length);
1466             l = copy;
1467         }
1468 
1469         public void run() {
1470             if (e == null)
1471                 return;
1472 
1473             switch (type) {
1474                 case CHANGE:
1475                     Object[] path = e.getPath();
1476                     if (path != null && path.length > 0 &&
1477                         path[path.length-1] instanceof TT_BasicNode) {
1478                         TT_BasicNode bn = (TT_BasicNode)(path[path.length-1]);
1479                         if (e.getChildIndices() != null &&
1480                             e.getChildIndices().length >= 1) {
1481                             if (bn.getChildCount() <= e.getChildIndices()[0])
1482                                 return;
1483                         }
1484                     }
1485 
1486                     for (int i = 0; i < l.length; i++) {
1487                         l[i].treeNodesChanged(e);
1488                     }
1489                     break;
1490                 case STRUCT:
1491                     for (int i = 0; i < l.length; i++) {
1492                         l[i].treeStructureChanged(e);
1493                     }
1494                     break;
1495                 case INS:
1496                     for (int i = 0; i < l.length; i++) {
1497                         l[i].treeNodesInserted(e);
1498                     }
1499                     break;
1500                 case DEL:
1501                     for (int i = 0; i < l.length; i++) {
1502                         l[i].treeNodesRemoved(e);
1503                     }
1504                     break;
1505                 default:
1506                     throw new JavaTestError(uif.getI18NString("tree.noEType"));
1507             }   // switch
1508         }
1509         TreeModelListener[] l;
1510         int type;
1511         TreeModelEvent e;
1512         UIFactory uif;
1513         static final int CHANGE = 0;
1514         static final int STRUCT = 1;
1515         static final int INS = 2;
1516         static final int DEL = 3;
1517     }
1518 
1519     // FilterSelectionHandler.Observer - may not be on event thread
1520     private class FilterWatcher implements FilterSelectionHandler.Observer {
1521 
1522         public void filterUpdated(TestFilter f) {
1523             //notifyFullStructure();
1524             invalidateNodeInfo();
1525         }
1526 
1527         public void filterSelected(TestFilter f) {
1528             //notifyFullStructure();
1529             if (!lastFilter.equals(f)) {
1530                 invalidateNodeInfo();
1531             }
1532             lastFilter = f;
1533         }
1534 
1535         public void filterAdded(TestFilter f) {
1536             // don't care
1537         }
1538 
1539         public void filterRemoved(TestFilter f) {
1540             // don't care
1541         }
1542     }
1543 }
1544