1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2016, 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 version 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;
  28 
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.text.Collator;
  32 import java.util.*;
  33 import java.util.concurrent.locks.ReentrantLock;
  34 
  35 import com.sun.javatest.finder.TestFinderDecorator;
  36 import com.sun.javatest.tool.Preferences;
  37 import com.sun.javatest.util.Debug;
  38 import com.sun.javatest.util.DynamicArray;
  39 import com.sun.javatest.util.I18NResourceBundle;
  40 import com.sun.javatest.util.StringArray;
  41 import com.sun.javatest.httpd.RootRegistry;
  42 
  43 /**
  44  * Collected results from a test suite.
  45  * The data is represented as TestResult objects, although the test itself
  46  * may not have been run, but just "found" so far.  TestResultTable is
  47  * populated by using a TestFinder, and is subsequently updated as tests are
  48  * run by the harness.
  49  */
  50 
  51 // debug values:
  52 // 1 - info messages, stack traces on
  53 // 2 - search details
  54 // 10 - more scan/insert/remove detail
  55 // 11 - heavy scan/insert/remove detail
  56 // 99 - everything
  57 // NOTE: stack traces are on for all levels above 0
  58 
  59 public class TestResultTable {
  60     public void dispose() {
  61         if (trCache != null) {
  62             trCache.shutdown();
  63         }
  64     }
  65 
  66     /**
  67      * Observer to monitor changes to a TestResultTable.
  68      */
  69     public interface Observer {
  70         /**
  71          * The oldValue has been replaced by the newValue.
  72          *
  73          * @param oldValue Previous value being overwritten.
  74          * @param newValue The new value stored in the TRT.
  75          */
  76         void update(TestResult oldValue, TestResult newValue);
  77 
  78         /**
  79          * The given test was changed, but previously existed in this TRT.
  80          * This is not a guarantee of change, but is the best possible hint.
  81          *
  82          * @param whichTR The test which was altered.
  83          */
  84         void updated(TestResult whichTR);
  85 
  86         /*
  87          void stalled(String reason);
  88          void ready();
  89          */
  90     }
  91 
  92     /**
  93      * Tree-aware observer of the TRT.
  94      *
  95      * @since 3.0
  96      */
  97     public interface TreeObserver {
  98        /**
  99         * A node has been inserted into the tree.
 100         * @param path The path to the node that was inserted.  Does not include
 101         *        the actual object which is new.
 102         * @param what The actual object that changed.  So <code>path</code> plus
 103         *        this is the entire path.  The type will either be TestResult or
 104         *        TreeNode.
 105         * @param index The index in <code>path[path.length-1]</code> where the
 106         *        new node was placed.
 107         */
 108        void nodeInserted(TreeNode[] path, Object what, int index);
 109 
 110        /**
 111         * A node has changed.
 112         * In the case of a test changing, the old object is the test result
 113         * being replaced.  In the case of a branch changing, the old object is
 114         * the same as the what object.
 115         *
 116         * @param path The path to the node that changed.  Does not include
 117         *        the actual object which changed.
 118         * @param what The actual object that changed.  So <code>path</code> plus
 119         *        this is the entire path.  The type will either be TestResult or
 120         *        TreeNode.
 121         * @param index The index in <code>path[path.length-1]</code> that changed.
 122         * @param old The old value at the changed location.
 123         */
 124        void nodeChanged(TreeNode[] path, Object what, int index, Object old);
 125 
 126        /**
 127         * An item has been removed from the tree.
 128         *
 129         * @param path The path to the node that changed.  Does not include
 130         *        the actual object which changed.
 131         * @param what The actual object that was removed.  So <code>path</code> plus
 132         *        this is the entire path.  The type will either be TestResult or
 133         *        TreeNode.
 134         * @param index The index in <code>path[path.length-1]</code> that was
 135         *        removed.
 136         */
 137        void nodeRemoved(TreeNode[] path, Object what, int index);
 138     }
 139 
 140     /**
 141      * Extension to TreeObserver to receive notifications related
 142      * to events happened on tree nodes.
 143      */
 144     public interface TreeEventObserver extends TreeObserver {
 145 
 146         /**
 147          * A refresh has been stared on the node. All children
 148          * will be recursively refreshed but only one notification about the
 149          * original node will be delivered.
 150          *
 151          * @param origin Node the refresh has been originated
 152          *
 153          */
 154         void startRefresh(TreeNode origin);
 155 
 156         /**
 157          * A refresh has been finished on the node. In spite of the all children
 158          * will be recursively refreshed the only one notification about the
 159          * original node will be delivered.
 160          *
 161          * @param origin Node the refresh has been originated
 162          *
 163          */
 164         void finishRefresh(TreeNode origin);
 165     }
 166 
 167     /**
 168      * Observer interface to watch a single tree node.
 169      *
 170      * @since 3.0
 171      */
 172     public interface TreeNodeObserver {
 173         /**
 174          * A TreeNode has been inserted into the given parent node.
 175          *
 176          * @param parent The node which acquired the new node.  This is the same as
 177          *        the object that the observer attached to.
 178          * @param newNode The node which was added.
 179          * @param index The index at which the node was added.
 180          */
 181         public void insertedBranch(TreeNode parent, TreeNode newNode, int index);
 182 
 183         /**
 184          * A TestResult has been inserted into the given parent node.
 185          *
 186          * @param parent The node which acquired the new test.  This is the same as
 187          *        the object that the observer attached to.
 188          * @param test The test which was added.
 189          * @param index The index at which the test was added.
 190          */
 191         public void insertedResult(TreeNode parent, TestResult test, int index);
 192 
 193         /**
 194          * A TestResult has been replaced in the given parent node.
 195          *
 196          * @param parent The node which acquired the new test.  This is the same as
 197          *        the object that the observer attached to.
 198          * @param oldTest The test which was replaced.
 199          * @param newTest The test which took the old test's place.
 200          * @param index The index at which activity occurred.
 201          */
 202         public void replacedResult(TreeNode parent, TestResult oldTest,
 203                                    TestResult newTest, int index);
 204 
 205         /**
 206          * A TreeNode has been removed from the given parent node.
 207          *
 208          * @param parent The node which acquired the new test.  This is the same as
 209          *        the object that the observer attached to.
 210          * @param index The index at which the removed node resided in the parent.
 211          */
 212         public void removedBranch(TreeNode parent, int index);
 213 
 214         /**
 215          * A TestResult has been removed from the given parent node.
 216          *
 217          * @param parent The node which acquired the new test.  This is the same as
 218          *        the object that the observer attached to.
 219          * @param test The test which was removed.
 220          * @param index The index at which the removed test resided in the parent.
 221          */
 222         public void removedResult(TreeNode parent, TestResult test, int index);
 223 
 224         /**
 225          * The statistics counters of the node have changed.
 226          *
 227          * @param node The node whose counters were invalidated.
 228          *        This is the same as the node which this observer attached to.
 229          */
 230         public void countersInvalidated(TreeNode node);
 231     }
 232 
 233 
 234     /**
 235      * Exception class to communicate any special conditions which may occur
 236      * while using this class.
 237      */
 238     public static class Fault extends Exception
 239     {
 240         Fault(I18NResourceBundle i18n, String s) {
 241             super(i18n.getString(s));
 242         }
 243 
 244         Fault(I18NResourceBundle i18n, String s, Object o) {
 245             super(i18n.getString(s, o));
 246         }
 247 
 248         Fault(I18NResourceBundle i18n, String s, Object[] o) {
 249             super(i18n.getString(s, o));
 250         }
 251     }
 252 
 253     /**
 254      * Create a table ready to be occupied.
 255      */
 256     public TestResultTable() {
 257         statusTables = new Map[Status.NUM_STATES];
 258         for (int i = 0; i < statusTables.length; i++)
 259             statusTables[i] = new Hashtable<>();
 260 
 261         root = new TRT_TreeNode(this, null);
 262 
 263         instanceId++;
 264         if (com.sun.javatest.httpd.HttpdServer.isActive()) {
 265             String url = "/trt/" + instanceId;
 266             httpHandle = new TRT_HttpHandler(this, url, instanceId);
 267             RootRegistry.getInstance().addHandler(url, "Test Result Table",
 268                                                   httpHandle);
 269             RootRegistry.associateObject(this, httpHandle);
 270         }
 271 
 272         rtc = new RequestsToCache();
 273             }
 274 
 275 
 276     /**
 277      * Create a table for the tests in a work directory and its
 278      * associated test suite and test finder.
 279      *
 280      * @param wd The work directory to associate with this table.
 281      * @since 3.0
 282      */
 283     public TestResultTable(WorkDirectory wd) {
 284         this();
 285         setWorkDirectory(wd);
 286     }
 287 
 288     /**
 289      * Create a table for the tests in a work directory and its
 290      * associated test suite, overriding the test suite's default test finder.
 291      *
 292      * @param wd The work directory to associate with this table.
 293      * @param tf The finder to use.  Do not use this constructor unless
 294      *        necessary.
 295      * @see #TestResultTable(WorkDirectory)
 296      * @since 3.0
 297      */
 298     public TestResultTable(WorkDirectory wd, TestFinder tf) {
 299         this();
 300         setWorkDirectory(wd, tf);
 301     }
 302 
 303     /**
 304      * Set the test finder for this object.
 305      * It is illegal to call this method once the test finder for a instance
 306      * has been set.  Rather than use this method, it is probably better to
 307      * supply the finder at construction time.
 308      *
 309      * @param tf The finder to use.
 310      * @throws IllegalStateException Thrown if the finder for this object is already set.
 311      * @see #getTestFinder
 312      * @since 3.0
 313      */
 314     public void setTestFinder(TestFinder tf) {
 315         if (finder != null) {
 316             throw new IllegalStateException(i18n.getString("trt.alreadyFinder"));
 317         }
 318 
 319         finder = tf;
 320 
 321         if (trCache == null)
 322             initFinder();
 323     }
 324 
 325     public void updateTestExecutionOrderOnTheFly(){
 326         root = new TRT_TreeNode(TestResultTable.this, null);
 327     }
 328 
 329     /**
 330      * Get the workdir associated with this object.
 331      *
 332      * @return The workdir.  Null if not available.
 333      * @deprecated Use getWorkDirectory
 334      */
 335     public WorkDirectory getWorkDir() {
 336         return getWorkDirectory();
 337     }
 338 
 339     /**
 340      * Get the work directory associated with this object.
 341      *
 342      * @return The work directory, or null if none set.
 343      * @see #setWorkDirectory
 344      */
 345     public WorkDirectory getWorkDirectory() {
 346         return workDir;
 347     }
 348 
 349     /**
 350      * Set the work directory associated with this object.
 351      * Once set, it cannot be changed.
 352      *
 353      * @param wd The work directory, or null if none set.
 354      * @see #getWorkDirectory
 355      */
 356     public void setWorkDirectory(WorkDirectory wd) {
 357         setWorkDirectory(wd, wd.getTestSuite().getTestFinder());
 358     }
 359 
 360     // this method exists to support the obsolete constructor
 361     // TestResultTable(WorkDirectory wd, TestFinder tf).
 362     // When that constructor can be deleted, the main body of this
 363     // method should be merged with setWorkDirectory(WorkDirectory wd)
 364     private void setWorkDirectory(WorkDirectory wd, TestFinder tf) {
 365         if (wd == null)
 366             throw new NullPointerException();
 367 
 368         if (workDir == wd) {
 369             // already set
 370             return;
 371         }
 372 
 373         if (workDir != null && workDir != wd) {
 374             // already set to something else
 375             throw new IllegalStateException();
 376         }
 377 
 378         if (finder != null && finder != tf)
 379             throw new IllegalStateException();
 380 
 381         workDir = wd;
 382         finder = tf;
 383 
 384         //root = new TRT_TreeNode(this, null);
 385         initFinder();
 386 
 387         /*OLD
 388         // do this in the background because of possible high cost
 389         Thread thr = new Thread("TRT background cache init.") {
 390             public void run() {
 391                 try {
 392                     trCache = new TestResultCache(workDir, true);
 393                     trCache.setObserver(updater);
 394                     setCacheInitialized(true);
 395                 }
 396                 catch (TestResultCache.Fault f) {
 397                     if (debug > 0)
 398                         f.printStackTrace(Debug.getWriter());
 399 
 400                     // XXX ack, what can we do?!
 401                     //throw new Fault(i18n, "trt.trcCreate", f.getMessage());
 402                 }   // try
 403             }   // run()
 404         };
 405         thr.setPriority(Thread.MIN_PRIORITY + 2);
 406         thr.start();
 407         */
 408         try {
 409             trCache = new TestResultCache(workDir, updater);
 410         }
 411         catch (IOException e) {
 412             // should consider throwing Fault, but that will destabilize too many APIs for now
 413             updater.error(e);
 414         }
 415     }
 416 
 417     /**
 418      * How many tests have been found so far.
 419      *
 420      * @return A number greater than or equal to zero.
 421      * @since 3.0
 422      */
 423     public int getCurrentTestCount() {
 424         return root.getCurrentSize();
 425     }
 426 
 427     void starting() {
 428         /*OLD
 429         isRunning++;
 430         */
 431     }
 432 
 433     void finished() {
 434         // do on background thread? (OLD suggestion -- now asynchronous)
 435         if (trCache != null) {
 436             if (needsCacheCompress()) {
 437                 if (debug > 0) {
 438                     Debug.print("TRT.finished(), attempting cache compress...");
 439                 }
 440                 trCache.compress();
 441             }
 442             if (debug > 0) {
 443                 Debug.print("TRT.finished(), requesting cache flush...");
 444             }
 445         }
 446     }
 447 
 448     /**
 449      * Update the information in the table with a new test result.
 450      * The supplied TestResult may exist in the table already, it can replace
 451      * an existing test or it can be completely new.  Doing this operation will
 452      * trigger appropriate observer messages.
 453      *
 454      * @param tr The test to insert or update.
 455      * @throws JavaTestError Throws if the result cache throws an error.
 456      */
 457     public void update(TestResult tr) {
 458         update(tr, false);
 459     }
 460 
 461     /**
 462      * Update the information in the table with a new test result.
 463      * The supplied TestResult may exist in the table already, it can replace
 464      * an existing test or it can be completely new.  Doing this operation will
 465      * trigger appropriate observer messages.
 466      *
 467      * @param tr The test to insert or update.
 468      * @param suppressScan Request that test finder activity be suppressed if possible
 469      * @throws JavaTestError Throws if the result cache throws an error.
 470      */
 471     public void update(TestResult tr, boolean suppressScan) {
 472         TestResult prev = insert(tr, suppressScan);
 473         updateNotify(tr, prev);
 474     }
 475 
 476     /**
 477      * Complete the notification process for insertion of a new result.
 478      * This method adds it to the cache, sends notifications and does other
 479      * bookkeeping only.
 480      *
 481      * This method was introduced to allow updates to occur from non-root
 482      * branches (bottom up updates) or updates from insertions into the table
 483      * which are usual during test execution (top down).
 484      * @param tr Result inserted.
 485      * @param prev Result replaced (if any).
 486      */
 487     void updateNotify(TestResult tr, TestResult prev) {
 488 
 489         if (tr != prev) {
 490             tr.shareStatus(statusTables);
 491 
 492             for (int i = 0; i < observers.length; i++)
 493                 observers[i].update(prev, tr);
 494         }
 495         else {
 496             // tests are the same, we are probably changing the status
 497             for (int i = 0; i < observers.length; i++)
 498                 observers[i].updated(tr);
 499         }
 500 
 501         testsInUpdate.add(tr);
 502 
 503         // things which haven't been run are not put in cache
 504         // doing so will cause problems in the cache and possibly other
 505         // places because there is never a JTR file to reload the result from
 506         if (trCache!= null && !updateInProgress && tr.getStatus().getType() != Status.NOT_RUN) {
 507             trCache.insert(tr);
 508         }
 509 
 510         testsInUpdate.remove(tr);
 511     }
 512 
 513     /**
 514      * This method blocks until the work directory data has been completely
 515      * synchronized.  It is recommended that you use this before creating and
 516      * using an iterator to ensure that you get consistent and up to date
 517      * data.  It would also be advisable to do this before forcing the VM to
 518      * exit.
 519      * @return Always true.  Reserved for later use.
 520      *
 521      * @since 3.0.1
 522      */
 523     public synchronized boolean waitUntilReady() {
 524         while ( ((workDir != null) && !cacheInitialized) ||
 525                   updateInProgress) {
 526             try {
 527                 wait();
 528             }
 529             catch (InterruptedException e) {
 530                 if (debug > 0)
 531                     e.printStackTrace(Debug.getWriter());
 532             }
 533         }
 534 
 535         if (trCache != null){
 536             trCache.requestFullUpdate();
 537         }
 538 
 539         return true;
 540     }
 541 
 542     /**
 543      * Determine the update status of the table.
 544      *
 545      * @return True if the table is in a consistent state.
 546      * @see #waitUntilReady
 547      */
 548     public synchronized boolean isReady() {
 549         return (cacheInitialized && !updateInProgress);
 550     }
 551 
 552     /**
 553      * Find a specific instance of a test result.
 554      *
 555      * @param td The test description which corresponds to the target result.
 556      * @return The requested test result object, null if not found.
 557      */
 558     public TestResult lookup(TestDescription td) {
 559         // transforms to JTR path style to do lookup
 560         return lookup(TestResult.getWorkRelativePath(td.getRootRelativeURL()));
 561     }
 562 
 563     /**
 564      * Find a specific instance of a test result.
 565      * If you only have the test URL, use TestResult.getWorkRelativePath() to get
 566      * the jtrPath parameter for this method.
 567      *
 568      * @param jtrPath The work relative path of the test result file.
 569      *                Output from TestResult.getWorkRelativePath() is
 570      *                the best source of this info.
 571      * @return The requested test result object, null if not found.
 572      */
 573     public TestResult lookup(String jtrPath) {
 574         // no tree yet
 575         if (root == null) return null;
 576 
 577         return findTest(root, jtrPath, jtrPath);
 578     }
 579 
 580     /**
 581      * Take a URL and find the node in the current test tree to which it refers
 582      * to.  The resulting node may be:
 583      * <ul>
 584      * <li>A folder (non-leaf) node.  Type <code>TreeNode</code>
 585      * <li>A test (leaf) node.  Type <code>TestResult</code>
 586      * </ul>
 587      * @param url A forward-slash separated path referring to node names along a
 588      *        path originating at the root node.
 589      * @return The nodes that the given URL refers to.  Null if no match.
 590      * @since 3.2
 591      */
 592     public Object resolveUrl(String url) {
 593         return lookupNode(root, url);
 594     }
 595 
 596     /**
 597      * Validate that a path is valid in this TestResultTable according to the
 598      * rules for initial URLs.
 599      *
 600      * @param path The path to validate.  Should be internal URL format (forward
 601      *        slashes, relative to test root, ...)  Null is acceptable but will
 602      *        immediately result in null being returned.
 603      * @return True if the given path is valid in this instance, false otherwise.
 604      * @since 3.0.2
 605      */
 606     public boolean validatePath(String path) {
 607         if (path == null)
 608             return false;
 609 
 610         Object[] result = lookupInitURL(root, path);
 611 
 612         if (result != null && result.length > 0)
 613             return true;        // ok
 614         else
 615             return false;       // no matches for path
 616     }
 617 
 618     /**
 619      * Find the specified test, recording the path from the root.
 620      *
 621      * @since 3.0
 622      * @param target The test to generate the path for.
 623      * @return The path to the root of the enclosing TRT.  Null if the operation
 624      *      could not be completed.  The target node is not included in the
 625      *      returned array.  Index 0 will be the TRT root.
 626      */
 627     public static TreeNode[] getObjectPath(TestResult target) {
 628         if (target == null)
 629             return null;
 630 
 631         ArrayList<TreeNode> path = new ArrayList<>();
 632         TreeNode loc = target.getParent();
 633         while (loc != null) {
 634             path.add(0, loc);
 635             loc = loc.getParent();
 636             // getParent() will be null for the root node
 637         }   // while
 638 
 639         TreeNode[] result = new TreeNode[path.size()];
 640         path.toArray(result);
 641 
 642         if (debug == 2 || debug == 99) {
 643             Debug.println("TRT - getObjectPath() results:");
 644             Debug.println("   -> target: " + target.getTestName());
 645             Debug.println("   -> resulting path length: " + result.length);
 646         }
 647 
 648         if (result == null || result.length == 0)
 649             return null;
 650         else
 651             return result;
 652     }
 653 
 654     /**
 655      * Find the specified test, recording the path from the root.
 656      *
 657      * @since 3.0
 658      * @param target The node to generate the path for.
 659      * @return The path to the root of the enclosing TRT.  Null if the operation
 660      *      could not be completed.  The target node is included in the returned array
 661      *      as the last element, index 0 will be the TRT root.
 662      */
 663     public static TreeNode[] getObjectPath(TreeNode target) {
 664         if (target == null)
 665             return null;
 666 
 667         ArrayList<TreeNode> path = new ArrayList<>();
 668         TreeNode loc = target;
 669         while (loc != null) {
 670             path.add(0, loc);
 671             loc = loc.getParent();      // getParent() will be null for the root node
 672         }   // while
 673 
 674         TreeNode[] result = new TreeNode[path.size()];
 675         path.toArray(result);
 676 
 677         if (debug == 2 || debug == 99) {
 678             Debug.println("TRT - getObjectPath() results:");
 679             Debug.println("   -> target RRP: " + TestResultTable.getRootRelativePath(target));
 680             Debug.println("   -> resulting path length: " + result.length);
 681         }
 682 
 683         return result;
 684     }
 685 
 686     /**
 687      * List all the tests in the tree.
 688      *
 689      * @return An iterator which returns all tests in the tree.
 690      * @since 3.0
 691      */
 692     public TreeIterator getIterator() {
 693         if (root == null)
 694             return NullEnum.getInstance();
 695         else
 696             return getIterator(root);
 697     }
 698 
 699     /**
 700      * List all the tests in the tree.
 701      *
 702      * @return An enumerator which returns all tests in the tree.
 703      * @see #getIterator()
 704      * @since 3.0
 705      */
 706     public Enumeration<TestResult> elements() {
 707         return getIterator();
 708     }
 709 
 710     /**
 711      * List all the tests in the tree subject to the given filters.
 712      *
 713      * @param filters The Filters to run tests through before "selecting"
 714      *        them for iteration.  May be null.
 715      * @return An iterator which returns all tests in the tree after removing
 716      *         those filtered out by the filters.
 717      * @since 3.0
 718      */
 719     public TreeIterator getIterator(TestFilter[] filters) {
 720         if (root == null)
 721             return NullEnum.getInstance();
 722         else
 723             return getIterator(root, filters);
 724     }
 725 
 726     /**
 727      * Same description as getIterator() method with same args.
 728      *
 729      * @param filters The Filters to run tests through before "selecting"
 730      *        them for iteration.  May be null.
 731      * @return An enumerator which returns all tests in the tree after removing
 732      *         those filtered out by the filters.
 733      * @since 3.0
 734      * @see #getIterator()
 735      */
 736     public Enumeration<TestResult> elements(TestFilter[] filters) {
 737         return getIterator(filters);
 738     }
 739 
 740     /**
 741      * List all the tests under this node.
 742      *
 743      * @param node The tree node to being the iteration at.
 744      * @return An iterator which return all the tests below the given node.
 745      * @since 3.0
 746      */
 747     public static TreeIterator getIterator(TreeNode node) {
 748         if (node == null)
 749             return NullEnum.getInstance();
 750         else
 751             return new TRT_Iterator(node);
 752     }
 753 
 754     /**
 755      * List all the tests under this node.
 756      *
 757      * @param node The tree node to being the iteration at.
 758      * @return An enumerator which return all the tests below the given node.
 759      * @see #getIterator()
 760      * @since 3.0
 761      */
 762     public static Enumeration<TestResult> elements(TreeNode node) {
 763         return getIterator(node);
 764     }
 765 
 766     /**
 767      * Get an iterator capable of producing a filtered view of the test suite.
 768      * If the node parameter is null, an iterator with no items will be returned.
 769      * An empty or null set of filters is acceptable and will result in unfiltered
 770      * iteration.
 771      *
 772      * @param node The tree node to being the iteration at.  May be null.
 773      * @param filter The filter to run tests through before "selecting"
 774      *        them for iteration.
 775      * @return An iterator which returns test below the given node after
 776      *         removing any tests which the filter rejects.
 777      * @since 3.0
 778      */
 779     public static TreeIterator getIterator(TreeNode node, TestFilter filter) {
 780         if (node == null)
 781             return NullEnum.getInstance();
 782         else {
 783             TestFilter[] filters = new TestFilter[] {filter};
 784             return new TRT_Iterator(node, filters);
 785         }
 786     }
 787 
 788     /**
 789      * Same description as getIterator() method with same args.
 790      *
 791      * @param node The tree node to being the enumeration at.  May be null.
 792      * @param filter The filter to run tests through before "selecting"
 793      *        them for enumeration.  May be null.
 794      * @return An enumerator which returns test below the given node after
 795      *         removing any tests which the filter rejects.
 796      * @see #getIterator()
 797      * @since 3.0
 798      */
 799     public static Enumeration<TestResult> elements(TreeNode node, TestFilter filter) {
 800         return getIterator(node, filter);
 801     }
 802 
 803     /**
 804      * Get an iterator capable of producing a filtered view of the test suite.
 805      * If the node parameter is null, an iterator with no items will be returned.
 806      * An empty or null set of filters is acceptable and will result in unfiltered
 807      * iteration.
 808      *
 809      * @param node The tree node to begin enumerating at.  May be null.
 810      * @param filters The test filters to apply to any tests found.
 811      * @return An iterator which returns test below the given node after
 812      *         removing any tests which the filters reject.
 813      * @since 3.0
 814      */
 815     public static TreeIterator getIterator(TreeNode node, TestFilter[] filters) {
 816         if (node == null)
 817             return NullEnum.getInstance();
 818         else
 819             return new TRT_Iterator(node, filters);
 820     }
 821 
 822     /**
 823      * Same description as getIterator() method with same args.
 824      *
 825      * @param node The tree node to begin enumerating at.  May be null.
 826      * @param filters The test filters to apply to any tests found.
 827      * @return An enumerator which returns test below the given node after
 828      *         removing any tests which the filters reject.
 829      * @see #getIterator()
 830      * @since 3.0
 831      */
 832     public static Enumeration<TestResult> elements(TreeNode node, TestFilter[] filters) {
 833         return getIterator(node, filters);
 834     }
 835 
 836     /**
 837      * Get an enumerator capable of producing a filtered view of the test
 838      * suite.  This can be used to obtain a view of the test suite based on an
 839      * initial URL selection.  The URL can specify a folder/directory, a
 840      * specific test, or a file which contains one or more tests.  If the given
 841      * URL parameter is null, an iterator with no elements will be returned.
 842      * An empty or null set of filters is acceptable and will result in
 843      * unfiltered iteration.
 844      *
 845      * @param url The test URL to scan.  This value should have already be
 846      *            normalized to a '/' file separator.  May be null.
 847      * @param filters The test filters to apply to any tests found.  May be null.
 848      * @return An enumerator which returns test below the given location after
 849      *         removing any tests which the filters reject.
 850      * @see #getIterator()
 851      * @since 3.0
 852      */
 853     public Enumeration<TestResult> elements(String url, TestFilter[] filters) {
 854         if (url == null)
 855             return NullEnum.getInstance();
 856         else {
 857             String[] urls = {url};
 858             return elements(urls, filters);
 859         }
 860     }
 861 
 862     /**
 863      * Get an iterator capable of producing a filtered view of the test suite.
 864      * This can be used to obtain a view of the test suite based on an initial
 865      * URL selection.  The URL can specify a folder/directory, a specific test,
 866      * or a file which contains one or more tests.  If the initial urls are
 867      * null or zero length, a filtered iterator of the root will be returned.
 868      * An empty or null set of filters is acceptable and will result in
 869      * unfiltered iteration.
 870      *
 871      * @param tests The set of files base the iterator on.  May be null.
 872      *        If this set is not empty, the contents should have already been
 873      *        validated using the validatePath() method.
 874      * @param filters The test filters to apply to any tests found.  May be null.
 875      * @return An iterator which return the union of tests specified by the
 876      *         initial files but not removed by the filters.
 877      * @throws TestResultTable.Fault Thrown if the given initialUrls are invalid.
 878      * @since 3.0
 879      * @see #validatePath
 880      */
 881     public TreeIterator getIterator(File[] tests, TestFilter[] filters) throws Fault {
 882         String[] urls = preProcessInitFiles(tests);
 883 
 884         if (urls != null && urls.length > 0)
 885             return getIterator(urls, filters);
 886         else
 887             return getIterator(filters);
 888     }
 889 
 890     /**
 891      * Same as getIterator() with the same args.
 892      *
 893      * @param tests The set of files base the enumerator on.  May be null.
 894      * @param filters The test filters to apply to any tests found.  May be null.
 895      * @return An enumerator which return the union of tests specified by the
 896      *         initial files but not removed by the filters.
 897      * @throws TestResultTable.Fault Thrown if the given initialUrls are invalid.
 898      * @see #getIterator()
 899      * @since 3.0
 900      */
 901     public Enumeration<TestResult> elements(File[] tests, TestFilter[] filters) throws Fault {
 902         return getIterator(tests, filters);
 903     }
 904 
 905     /**
 906      * Get an iterator capable of producing a filtered view of the test suite.
 907      * This can be used to obtain a view of the test suite based on an initial
 908      * URL selection.  An empty or null set of filters is acceptable and will
 909      * result in unfiltered iteration.
 910      *
 911      * @param paths The test URLs to scan.  Values should have already be normalized to a
 912      *            '/' file separator.  May not be null.
 913      * @param filters The test filters to apply to any tests found.  May be null.
 914      * @return An iterator which return the union of tests specified by the
 915      *         URLs but not removed by the filters.
 916      * @since 3.0
 917      */
 918     public TreeIterator getIterator(String[] paths, TestFilter[] filters) {
 919         LinkedList<TreeNode> initNodes = new LinkedList<TreeNode>();
 920         LinkedList<TestResult> initTests = new LinkedList<TestResult>();
 921 
 922         String[] urls = sortByName(paths); // sorting in any case to improve performance of distilling
 923         urls = distillUrls(urls);
 924 
 925         if (!Boolean.parseBoolean(
 926             Preferences.access().getPreference("javatest.sortExecution", "true"))) {
 927             // need to exclude extra elements from initial array
 928             if (urls.length != paths.length) {
 929                 urls = removeMissing(paths, urls);
 930             } else {
 931                 // nothing was distilled - just taking paths
 932                 urls = paths;
 933             }
 934         }
 935 
 936         for (int i = 0; i < urls.length; i++) {
 937             Object[] objs = lookupInitURL(root, urls[i]);
 938             if (debug == 1 || debug == 99)
 939                 Debug.println("TRT.lookupInitURL gave back " + Arrays.toString(objs));
 940 
 941             if (objs == null) {   // no match
 942             }
 943             else if (objs instanceof TreeNode[]) {
 944                 // don't add duplicates
 945                 if (!initNodes.contains(objs[0]))
 946                     initNodes.add((TreeNode) objs[0]);
 947             }
 948             else if (objs instanceof TestResult[]) {
 949                 initTests.addAll(Arrays.asList((TestResult[]) objs));
 950                 // XXX should uniquify
 951             }
 952             else {
 953                 // XXX should this be more friendly?
 954                 //     or maybe it should be ignored
 955                 throw new IllegalArgumentException(i18n.getString("trt.invalidIURL", urls[i]));
 956             }
 957         }   // for
 958 
 959         if ((initNodes == null || initNodes.size() == 0) &&
 960             (initTests == null || initTests.size() == 0)) {
 961             if (debug == 1 || debug == 99)
 962                 Debug.println("None of the initial URLs could be looked up.");
 963 
 964             return NullEnum.getInstance();
 965         }
 966 
 967         if (initTests.size() > 0) {
 968             if (debug == 1 || debug == 99)
 969                 Debug.println("Using combo TreeIterator, " + initTests.size() +
 970                             " tests, " + initNodes.size() + " nodes.");
 971             return new TRT_Iterator(initNodes.toArray(new TreeNode[0]), initTests.toArray(new TestResult[0]), filters);
 972         }
 973         else
 974             return new TRT_Iterator(initNodes.toArray(new TreeNode[0]), filters);
 975     }
 976 
 977     /**
 978      * This method is the same as getIterator() with the same params.
 979      *
 980      * @param urls The test URLs to scan.  Values should have already be normalized to a
 981      *            '/' file separator.
 982      * @param filters The test filters to apply to any tests found.
 983      * @return An enumerator which return the union of tests specified by the
 984      *         URLs but not removed by the filters.
 985      * @see #getIterator()
 986      * @since 3.0
 987      */
 988     public Enumeration<TestResult> elements(String[] urls, TestFilter[] filters) {
 989         return getIterator(urls, filters);
 990     }
 991 
 992     /**
 993      * Find out the size of the entire tree.  This is a high overhead call, use
 994      * with caution.
 995      *
 996      * @return The number of tests in the tree.
 997      */
 998     public int size() {
 999         if (root == null)
1000             return 0;
1001         else
1002             return root.getSize();
1003     }
1004 
1005     /**
1006      * Insert the given test into the tree.
1007      *
1008      * @param tr The test to insert.  A null value is ignored.
1009      * @return The old value.  Null if none or the parameter was null.
1010      */
1011     TestResult insert(TestResult tr) {
1012         return insert(tr, false);
1013     }
1014 
1015     /**
1016      * Insert the given test into the tree.
1017      *
1018      * @param tr The test to insert.  A null value is ignored.
1019      * @param suppressScan Request that test finder activity be suppressed.
1020      * @return The old value.  Null if none or the parameter was null.
1021      */
1022     TestResult insert(TestResult tr, boolean suppressScan) {
1023         if (tr == null)
1024             return null;
1025 
1026         String key = tr.getWorkRelativePath();
1027         //maxDepth = 0;
1028 
1029         TRT_TreeNode[] path = new TRT_TreeNode[0];
1030 
1031         return insert(root, key, tr, path, suppressScan);
1032     }
1033 
1034     /**
1035      * Insert the given test and indicate that test's previous status.
1036      *
1037      * @param tr The test to insert, may or may not already exist in the table.
1038      *           A null value is ignored.
1039      * @param oldStatus The previous status of the given test.
1040      * @return The old result which was replaced.  Null if the parameter was null or
1041      *         there was no previous value.
1042      */
1043     TestResult insert(TestResult tr, Status oldStatus) {
1044         if (tr == null)
1045             return null;
1046 
1047         String key = tr.getWorkRelativePath();
1048         //maxDepth = 0;
1049 
1050         TRT_TreeNode[] path = new TRT_TreeNode[0];
1051 
1052         return insert(root, key, tr, path, false);
1053     }
1054 
1055     /**
1056      * Get the root TreeNode of this result table.
1057      *
1058      * @return The root of the tree.
1059      */
1060     public TestResultTable.TreeNode getRoot() {
1061         return root;
1062     }
1063 
1064     /**
1065      * Get the root URL of the test suite.
1066      * This may not match that given by the environment if the environment's
1067      * URL is partially invalid for some reason.
1068      *
1069      * @return A file representing the path to the root of the testsuite.
1070      */
1071     public File getTestSuiteRoot() {
1072         return suiteRoot;
1073     }
1074 
1075     /**
1076      * Get the finder that TRT is using to read the test suite.
1077      * Unless the TRT was constructed using a TestFinder, this value will
1078      * most likely be null.
1079      *
1080      * @return The active test finder.  Null if no finder is being used.
1081      * @see #setTestFinder
1082      * @since 3.0
1083      */
1084     public TestFinder getTestFinder() {
1085         return finder;
1086     }
1087 
1088     /**
1089      * Get the path to this node, relative to the root of the testsuite.
1090      * The returned URL does not have a trailing slash, nor does it begin
1091      * with one.
1092      * @param node The node to find the path to.
1093      * @return The URL to the given node, with '/' as the path separator.
1094      *         Zero length string if the node is a root.
1095      * @since 3.0
1096      */
1097     public static String getRootRelativePath(TreeNode node) {
1098         if (node.isRoot()) return "";
1099 
1100         StringBuilder name = new StringBuilder(node.getName());
1101         node = node.getParent();
1102 
1103         while (node != null && !node.isRoot()) {
1104             name.insert(0, '/');
1105             name.insert(0, node.getName());
1106             node = node.getParent();
1107         }
1108 
1109         return name.toString();
1110     }
1111 
1112     /**
1113      * Used to find a branch node somewhere in the tree based on a path.
1114      * If the <tt>path</tt> string is of zero length (the empty string), the
1115      * <tt>node</tt> parameter is returned.  This is desirable for proper operation
1116      * because the path to the root is the empty path.
1117      *
1118      * @param node Where to start the search
1119      * @param path The work relative position of the JTR (TestResult.getWorkRelativePath())
1120      * @return The node with the given path relative to the given node.  Null if not found.
1121      * @throws IllegalArgumentException If the starting node or path is null.
1122      * @since 3.0
1123      */
1124     public static TreeNode findNode(TreeNode node, String path) {
1125         if (node == null)
1126             throw new IllegalArgumentException(i18n.getString("trt.nodeNull"));
1127         if (path == null)
1128             throw new IllegalArgumentException(i18n.getString("trt.pathNull"));
1129 
1130         // special case, should only happen on first call of this method
1131         if (path.length() == 0)
1132             return node;
1133 
1134         String dir = getDirName(path);
1135         TreeNode tn = null;
1136 
1137         if (debug > 9)
1138             Debug.println("TRT.findNode() looking for " + path + " in " + node.getName());
1139 
1140         if (dir.equals(path)) { // last element of the path
1141             tn = ((TRT_TreeNode)(node)).getTreeNode(path, false);
1142         } else {                        // recurse
1143             TreeNode next = ((TRT_TreeNode)node).getTreeNode(dir, false);
1144             if (next != null)
1145                 tn = findNode(next, behead(path));
1146             else { // not found
1147             }
1148         }
1149 
1150         return tn;
1151     }
1152 
1153     /**
1154      * Add a general purpose observer.
1155      *
1156      * @param o The observer to attach.  Must never be null.
1157      */
1158     public synchronized void addObserver(Observer o) {
1159         if (o == null)
1160             throw new NullPointerException();
1161         observers = DynamicArray.append(observers, o);
1162     }
1163 
1164     /**
1165      * Remove a general purpose observer.
1166      * Removing an observer which is not attached has no effect.
1167      *
1168      * @param o The observer to remove.
1169      */
1170     public synchronized void removeObserver(Observer o) {
1171         observers = DynamicArray.remove(observers, o);
1172     }
1173 
1174     /**
1175      * Add a tree-aware observer.
1176      *
1177      * @param obs The observer to attach.  Must never be null.
1178      */
1179     public void addObserver(TreeObserver obs) {
1180         treeObservers = DynamicArray.append(treeObservers, obs);
1181     }
1182 
1183     /**
1184      * Remove a tree-aware observer.
1185      * Removing an observer which is not attached has no effect.
1186      *
1187      * @param obs The observer to remove.
1188      */
1189     public void removeObserver(TreeObserver obs) {
1190         if (treeObservers != null)
1191             treeObservers = DynamicArray.remove(treeObservers, obs);
1192     }
1193 
1194     /**
1195      * This method purges the given test, including attempting to delete the
1196      * associated JTR file, then replaces it with a basic <code>NOT_RUN</code>
1197      * test of the same name.  This operation has no effect if the given test
1198      * is not in the tree.
1199      * <p>
1200      * Matching objects for removal is done only by reference.  The operation
1201      * may fail (return <code>null</code>) if the test exists, but is not the
1202      * same object.  If you really want to remove a test by name, you can use
1203      * <code>resetTest(String)</code>.
1204      * <p>
1205      * NOTE: This method will cause waitUntilReady() to block.
1206      *
1207      * @param tr The test to find, purge and replace.
1208      * @return The new <code>NOT_RUN</code> test.  Null if the operation fails
1209      *         because the test could not be found.
1210      * @see #resetTest(String)
1211      * @since 3.0
1212      */
1213     public synchronized TestResult resetTest(TestResult tr) {
1214         TestResult newTest = null;
1215 
1216         workDir.log(i18n, "trt.rstTest", tr.getTestName());
1217         TreeNode[] location = getObjectPath(tr);
1218 
1219         if (location == null) {    // operation failed
1220             // do nothing
1221             // result = null
1222             newTest = lookup(tr.getWorkRelativePath());
1223             if (debug > 0)
1224                 Debug.println("Recovered test by replacement (1). " + newTest);
1225         }
1226         else {
1227             TRT_TreeNode targetNode = (TRT_TreeNode)(location[location.length-1]);
1228             int index = targetNode.getIndex(tr, false);
1229             if (index >= 0) {
1230                 newTest = targetNode.resetTest(index, tr);
1231                 if (newTest == null && debug > 0)
1232                     Debug.println("reset of test " + tr.getTestName() + " failed.");
1233                 else {
1234                     /*OLD
1235                     try {
1236                     */
1237                         // Insert into cache?
1238                         // this will cause a cache error on flush because it can't reload the
1239                         // result, but that should get it out of the cache
1240                         if (trCache != null) {
1241                             testsInUpdate.add(newTest);
1242                             trCache.insert(newTest);
1243                             testsInUpdate.remove(newTest);
1244                         }
1245                         notifyRemoveLeaf(location, tr, index);
1246                         notifyNewLeaf(location, newTest, index);
1247                     /*OLD
1248                     }   // try
1249                     catch (TestResultCache.Fault f) {
1250                         if (debug > 0)
1251                             f.printStackTrace(Debug.getWriter());
1252                         else { }
1253 
1254                         throw new JavaTestError(i18n, "trt.trcFault", f);
1255                     }   // catch
1256                     */
1257                 }   // inner else
1258             }   // middle if
1259             else {
1260                 newTest = lookup(tr.getWorkRelativePath());
1261                 if (debug > 0)
1262                     Debug.println("Recovered test by replacement (2). " + newTest);
1263             }   // middle else
1264         }   // else
1265 
1266         return newTest;
1267     }
1268 
1269     /**
1270      * This method purges the given test, including attempting to delete the
1271      * associated JTR file, then replaces it with a basic <code>NOT_RUN</code>
1272      * test of the same name.  This operation has no effect if the given test
1273      * is not in the tree.  The <code>resetTest(TestResult)</code> method is
1274      * more efficient than this one, use it if you already have the object.
1275      * <p>
1276      * NOTE: This method may cause waitUntilReady() to block.
1277      *
1278      * @param testName The test to find, purge and replace.  This is of the form
1279      *        given by TestResult.getTestName().
1280      * @return The new <code>NOT_RUN</code> test.  Null if the given test name
1281      *         could not be found.
1282      * @see com.sun.javatest.TestResult#getTestName
1283      * @since 3.0
1284      */
1285     public synchronized TestResult resetTest(String testName) {
1286         TestResult tr = findTest(root, TestResult.getWorkRelativePath(testName), testName);
1287         if (tr == null)
1288             return null;
1289         else
1290             return resetTest(tr);
1291     }
1292 
1293     /**
1294      * Refresh a test if the files on disk have been modified since the test was read.
1295      * @param test The path for the test to be refreshed
1296      * @return true if a refresh was needed, false otherwise.
1297      * @throws TestResultTable.Fault if the test indicated cannot be located for
1298      *         refreshing.
1299      */
1300     public synchronized boolean refreshIfNeeded(String test) throws Fault {
1301         TestResult tr = lookup(TestResult.getWorkRelativePath(test));
1302 
1303         if (tr == null)
1304             throw new Fault(i18n, "trt.refreshNoTest", test);
1305 
1306         TreeNode[] path = getObjectPath(tr);
1307 
1308         if (path == null)
1309             return false;
1310 
1311         TRT_TreeNode tn = (TRT_TreeNode)path[path.length-1];
1312         TestResult newTr = tn.refreshIfNeeded(tr);
1313 
1314         if (newTr != tr)
1315             notifyChangeLeaf(TestResultTable.getObjectPath(tn),
1316                              newTr, tn.getTestIndex(newTr, false), tr);
1317 
1318         return false;
1319     }
1320 
1321     /**
1322      * Refresh a folder if the files on disk have been modified since the
1323      * folder was read. Notifies observers of the refresh happened.
1324      * @param node the node representing the folder to be refreshed
1325      * @return true if any refreshing was needed, false otherwise.
1326      * @throws TestResultTable.Fault if the node indicated cannot be located for
1327      *         refreshing.
1328      */
1329     public synchronized boolean refreshIfNeeded(TreeNode node) throws Fault {
1330         if (node.getEnclosingTable() != this)
1331             throw new IllegalStateException("refresh requested for node not owned by this table");
1332 
1333         notifyStartRefresh(node);
1334         try {
1335             return recursiveRefresh((TRT_TreeNode)node);
1336         } finally {
1337             notifyFinishRefresh(node);
1338         }
1339     }
1340 
1341     public synchronized boolean prune() throws Fault {
1342         if (root == null)
1343             return false;
1344 
1345         boolean changes = false;
1346 
1347         root.scanIfNeeded();
1348         TreeNode[] nodes = root.getTreeNodes();
1349 
1350         if (nodes == null)
1351             return changes;
1352 
1353         for (int i = 0; i < nodes.length; i++) {
1354             changes = (changes || prune(nodes[i]));
1355         }   // for
1356 
1357         return changes;
1358     }
1359 
1360     /**
1361      * Removes empty nodes (things with no tests below them).
1362      * @param node The node to perform the prune operation upon.
1363      * @return True if some nodes were pruned, false otherwise.
1364      */
1365     synchronized public boolean prune(TreeNode node) throws Fault {
1366         TRT_TreeNode parent = ((TRT_TreeNode)(node.getParent()));
1367 
1368         if (node.getChildCount() == 0) {
1369             int index = parent.rmChild((TRT_TreeNode)node);
1370 
1371             if (index != -1)
1372                 notifyRemoveLeaf(getObjectPath(parent), node, index);
1373 
1374             return (index != -1 ? true : false);
1375         }
1376 
1377         TreeNode[] nodes = node.getTreeNodes();
1378 
1379         if (nodes == null)
1380             return false; // must mean there are tests in this node
1381 
1382         for (int i = 0; i < nodes.length; i++) {
1383             prune(nodes[i]);
1384         }   // for
1385 
1386         if (node.getChildCount() == 0) {
1387             // prune
1388             int index = parent.rmChild((TRT_TreeNode)node);
1389             if (index != -1)
1390                 notifyRemoveLeaf(getObjectPath(parent), node, index);
1391 
1392             return (index != -1 ? true : false);
1393         }
1394 
1395         return false;
1396     }
1397 
1398     // ------- Private methods begin --------
1399 
1400     /**
1401      * Temporary method to suppress finder activity.
1402      * Called from Harness when in batch mode.  You should
1403      * <b>not</b> change this after it has been set. It
1404      * should be set before iteration and insertion of
1405      * work directory information takes place.
1406      */
1407     void suppressFinderScan(boolean state) {
1408         if (state == false)
1409             suppressFinderScan = false;
1410         else if (workDir != null) {
1411             TestSuite ts = workDir.getTestSuite();
1412             if (ts != null && (
1413                 ts.getTestRefreshBehavior(TestSuite.DELETE_NONTEST_RESULTS) ||
1414                 ts.getTestRefreshBehavior(TestSuite.REFRESH_ON_RUN) ||
1415                 ts.getTestRefreshBehavior(TestSuite.CLEAR_CHANGED_TEST)) ) {
1416                 suppressFinderScan = false;
1417             }
1418             else
1419                 suppressFinderScan = state;     // i.e. true
1420         }
1421         else
1422             suppressFinderScan = state;     // i.e. true
1423 
1424     }
1425 
1426     boolean isFinderScanSuppressed() {
1427         return suppressFinderScan;
1428     }
1429 
1430     /**
1431      * Determine if the path represented by the file is a branch or a leaf.
1432      * This is the semantic equivalent of File.isDirectory(), but shielded
1433      * behind this method for finders which do not use the filesystem.
1434      * @param f The file to check.  May not be null.
1435      */
1436     boolean isBranchFile(File f) {
1437         return finder.isFolder(f);
1438     }
1439 
1440     /**
1441      * Determine the last logical time that a file was modified.
1442      * This is the semantic equivalent of File.lastModified(), but shielded
1443      * behind this method for finders which do not use the filesystem.
1444      * @param f The file to check.  May not be null.
1445      */
1446     long getLastModifiedTime(File f) {
1447         // this must be upgraded for binary test finder scanning to work without
1448         // the actual files in the tests directory
1449         return finder.lastModified(f);
1450     }
1451 
1452     private class DisassembledUrl {
1453         private String[] data;
1454         private String initStr;
1455 
1456         public DisassembledUrl(String str) {
1457             data = StringArray.splitList(str, "/");
1458             initStr = str;
1459         }
1460     }
1461 
1462     private static class SortingComparator implements Comparator<DisassembledUrl> {
1463         Comparator<String> c;
1464 
1465         public SortingComparator(Comparator<String> c) {
1466             this.c = c;
1467         }
1468 
1469         public int compare(DisassembledUrl o1, DisassembledUrl o2) {
1470             String[] s1 = o1.data, s2 = o2.data;
1471             for (int i = 0; i < s1.length; i++) {
1472                 if (i >= s2.length)
1473                     return 1;
1474 
1475                 int comp = c.compare(s1[i], s2[i]);
1476                 if (comp == 0)
1477                     continue;
1478                 else
1479                     return comp;
1480             }
1481 
1482             // this case means that all s1 body exists in s2
1483             if (s2.length > s1.length)
1484                 return -1;
1485 
1486             // s1 is exact copy of s2. Should not happen really.
1487             return 0;
1488         }
1489     }
1490 
1491     /**
1492      * Sort test and folder URLs by their ordering the tree, which is ultimately
1493      * determined by the <code>TestFinder</code>.
1494      */
1495     private String[] sortByName(String[] in) {
1496         if (in == null || in.length <= 1)
1497             return in;
1498 
1499         Comparator<String> c = finder.getComparator();
1500 
1501         if (c == null) {
1502             // show warning message?
1503             c = TestFinder.getDefaultComparator();
1504         }
1505 
1506         DisassembledUrl[] elements = new DisassembledUrl[in.length];
1507         for (int i = 0; i < in.length; i++) {
1508             elements[i] = new DisassembledUrl(in[i]);
1509         }
1510 
1511         Arrays.sort(elements, new SortingComparator(c)); // using standard way to sort the array
1512 
1513         String result[] = new String[elements.length];
1514         int i = 0;
1515         for(DisassembledUrl s: elements)
1516             result[i++] = s.initStr;
1517 
1518         return result;
1519     }
1520 
1521     private int compareStringArrays(Comparator<String> c, String[] s1, String[] s2) {
1522         // loop until names become unequal
1523         for (int i = 0; i < s1.length; i++) {
1524             if (i >= s2.length)
1525                 return 1;
1526 
1527             int comp = c.compare(s1[i], s2[i]);
1528             if (comp == 0)
1529                 continue;
1530             else
1531                 return comp;
1532         }
1533 
1534         return 0;
1535     }
1536 
1537     /**
1538      * Removes from sorted array all overlapping entries.
1539      * E.g. {"root/a/b.html", "root/a/b.html#a", "root/a/b.htmls", "root/c", "root/c/d"}
1540      * would be {"root/a/b.html", "root/a/b.htmls", "root/c"}
1541      * complexity: n
1542      * complexity including quicksort: n*n*ln(n)
1543      *
1544      * @param urls A sorted list of test urls
1545      */
1546     public static String[] distillUrls(String[] urls) {
1547         // this method should guarantee that no one test would be runned twice.
1548         // testcases (in brackets are paths that should be thrown out):
1549         // 1) root, [root/a/b/c]; root/a, root/b, [root/b/c]; root/a.html, root/a.htmls; root/a.html, [root/a.html#boo]; root, [root/a], [root/b]
1550 
1551         // no need to process in these cases
1552         if (urls == null || urls.length <= 1)
1553             return urls;
1554 
1555         LinkedList<String> result = new LinkedList<String>();
1556         result.add(urls[0]);
1557 
1558         // as the array is expected to be sorted, it's known that foo/boo.html is just before foo/boo.html#anything
1559 
1560         String prev = urls[0]; // should not be testcase (should not contain '#' chars)
1561         if (prev.contains("#"))
1562             prev = "//##//"; // can't be start of testname
1563         for (int i = 1; i < urls.length; i++) {
1564             if (isPartOf(urls[i], prev)) {
1565                 continue;
1566             }
1567 
1568             if (!urls[i].contains("#"))
1569                 prev = urls[i];
1570             result.add(urls[i]);
1571         }
1572 
1573 
1574         if (result.size() == urls.length)
1575             // Nothing was thrown out. No need to reconstruct array.
1576             return urls;
1577         else {
1578             String[] s = new String[result.size()];
1579             return result.toArray(s);
1580         }
1581     }
1582 
1583     private static boolean isPartOf(String part, String of) {
1584         // if S2 is a part of S1 (e.g. S1="root/a", S2="root/a/t.html#2"), then S2 = S1 + diff, where diff can only start with '#' or '/'
1585         if (part.length() <= of.length())
1586             return false;
1587 
1588         if (part.startsWith(of) && (part.charAt(of.length()) == '#' || part.charAt(of.length()) == '/'))
1589             return true;
1590 
1591         return false;
1592     }
1593 
1594     /**
1595      * Creates a new array which contains all elements from missingIn ordered as in removeFrom
1596      *
1597      * @param removeFrom unsorted not distilled array
1598      * @param missingIn sorted and distilled array
1599      * @return a new array which contains all elements from missingIn ordered as in removeFrom
1600      */
1601     private String[] removeMissing(String[] removeFrom, String missingIn[]) {
1602         String[] result = new String[missingIn.length];
1603         int i = 0;
1604         outer:
1605         for (String path: removeFrom) {
1606             for (String url: missingIn) {
1607                 // if a path is in distilled array - save it and continue with next
1608                 if (path == url) {
1609                     result[i++] = path;
1610                     continue outer;
1611                 }
1612             }
1613         }
1614         return result;
1615     }
1616 
1617     private synchronized void updateFromCache(Map<String, TestResult> m) {
1618         updateInProgress = true;
1619 
1620         // this method could use further optimization to take advantage
1621         //     of its sorted nature
1622         cachedResults = m;
1623 
1624         if (rtc.getRequests() != null) {
1625             for (TestDescription td : rtc.getRequests()) {
1626                 String testRes = TestResult.getWorkRelativePath(td.getRootRelativeURL());
1627                 TestResult tr = m.get(testRes);
1628                 if (tr != null) {
1629                     tr.setTestDescription(td);
1630                     update(tr, suppressFinderScan);
1631                 }
1632             }
1633         }
1634         rtc.clear();
1635 
1636         if (!cacheInitialized)
1637             cacheInitialized = true;
1638 
1639         updateInProgress = false;
1640 
1641         notifyAll();
1642     }
1643 
1644     /**
1645      * Recursively Insert the given test into the tree, recording the insertion
1646      * path along the way.  This is <em>not</em> a general purpose method.
1647      *
1648      * @param path Remaining part of the path.  Must not be null.
1649      *        The expected format is: foo/bar/baz.html#bear
1650      * @param tr   The test result object we are storing
1651      * @return The test result which was replaced by this operation, null if no
1652      *         previous entry existed.
1653      */
1654     synchronized TestResult insert(TRT_TreeNode node, String path, TestResult tr,
1655                                    TRT_TreeNode[] rec) {
1656         return insert(node, path, tr, rec, false);
1657     }
1658 
1659     /**
1660      * Recursively Insert the given test into the tree, recording the insertion
1661      * path along the way.  This is <em>not</em> a general purpose method.
1662      *
1663      * @param path Remaining part of the path.  Must not be null.
1664      *        The expected format is: foo/bar/baz.html#bear
1665      * @param tr   The test result object we are storing
1666      * @param suppressScan Request that test finder activity be suppressed.
1667      * @return The test result which was replaced by this operation, null if no
1668      *         previous entry existed.
1669      */
1670     synchronized TestResult insert(TRT_TreeNode node, String path, TestResult tr,
1671                                    TRT_TreeNode[] rec, boolean suppressScan) {
1672         if (debug > 9)
1673             Debug.println("TRT Beginning insert " + path);
1674 
1675         String newPath = behead(path);
1676 
1677         if (path == newPath) {
1678             // this should be the test name, make it a leaf
1679 
1680             // last parameter allows the TR to be dropped if it does not exist
1681             // in the test suite.
1682             TestResult oldTR = node.addChild(tr, suppressScan, !cacheInitialized);
1683             //tr.setParent(node);   // now done in TRT_TreeNode.addChild()
1684             rec = DynamicArray.append(rec, node);
1685 
1686             // index will be -1 if the node insertion was rejected
1687             // perhaps upgrade the code so that addChild() throws and
1688             // exception
1689             int index = node.getIndex(tr, suppressScan);
1690 
1691             if (oldTR == null) {
1692                 if (debug > 10) {
1693                     Debug.println("   => Inserted TR: " + tr.getTestName());
1694                     Debug.println("   => Test Ref: " + tr);
1695                     Debug.println("   => Status is: " + Status.typeToString(tr.getStatus().getType()));
1696                     Debug.println("   => TRT: " + this);
1697                     Debug.println("   => Node Ref: " + node);
1698                     Debug.println("   => Node path: " + getRootRelativePath(node));
1699                     Debug.println("   => Index in node: " + node.getIndex(tr, suppressScan));
1700                 }   // debug
1701 
1702                 if (index != -1)
1703                     notifyNewLeaf(rec, tr, node.getIndex(tr, suppressScan));
1704             }
1705             else if (oldTR == tr) {
1706                 if (debug > 10) {
1707                     Debug.println("   => Ignored new TR: " + tr.getTestName());
1708                     Debug.println("   => Test Ref: " + tr);
1709                     Debug.println("   => Status is: " + Status.typeToString(tr.getStatus().getType()));
1710                     Debug.println("   => RESETTING IT! " + updateInProgress);
1711                 }
1712 
1713                 if (updateInProgress)
1714                     resetTest(tr.getTestName());
1715             }
1716             else {
1717                 if (debug > 10) {
1718                     Debug.println("   => Updated TR: " + tr.getTestName());
1719                     Debug.println("   => Test Ref: " + tr);
1720                     Debug.println("   => Status is: " + Status.typeToString(tr.getStatus().getType()));
1721                     Debug.println("   => TRT: " + this);
1722                     Debug.println("   => Node Ref: " + node);
1723                     Debug.println("   => Node path: " + getRootRelativePath(node));
1724                     Debug.println("   => Index in node: " + index);
1725                 }   // debug
1726 
1727                 if (index == -1) {
1728                     // insert was ignored for some reason
1729                 }
1730                 else if (oldTR != null && oldTR != tr) {
1731                     // handover known info if new tr is minimal
1732                     if (tr.isShrunk()) {
1733                         try {
1734                             TestDescription desc = oldTR.getDescription();
1735                             if (desc != null)
1736                                 tr.setTestDescription(desc);
1737                         }
1738                         catch (TestResult.Fault f) {
1739                             // give up
1740                         }
1741                     }
1742 
1743                     //notifyChangeLeaf(rec, tr, index, oldTR);
1744                     notifyRemoveLeaf(rec, oldTR, index);
1745                     notifyNewLeaf(rec, tr, index);
1746                 }
1747                 else
1748                     notifyChangeLeaf(rec, tr, index, oldTR);
1749             }
1750 
1751             return oldTR;
1752         }
1753         else {
1754             // has at least 1 dir name left
1755             // find or create a TRT_TreeNode and follow it
1756 
1757             String nextDir = getDirName(path);
1758             TRT_TreeNode next = node.getTreeNode(nextDir, suppressScan);
1759 
1760             if (next == null) {     // create branch
1761                 TRT_TreeNode tn = new TRT_TreeNode(this, node);
1762                 tn.setName(getDirName(nextDir));
1763                 node.addChild(tn, suppressScan);
1764 
1765                 rec = DynamicArray.append(rec, tn);
1766                 notifyNewBranch(rec, tn, node.getIndex(tn, suppressScan));
1767 
1768                 return insert(tn, newPath, tr, rec, suppressScan);
1769             }
1770             else {                  // no work, just recurse
1771                 rec = DynamicArray.append(rec, node);
1772                 return insert(next, newPath, tr, rec, suppressScan);
1773             }
1774 
1775         }
1776     }
1777 
1778     /**
1779      *
1780      * @return true if any refreshing was needed, false otherwise.
1781      */
1782     private boolean recursiveRefresh(TRT_TreeNode node) {
1783         boolean result = node.refreshIfNeeded();
1784 
1785         TreeNode[] children = node.getTreeNodes();
1786         if (children != null)
1787             for (int i = 0; i < children.length; i++)
1788                 result |= recursiveRefresh((TRT_TreeNode)children[i]);
1789 
1790         return result;
1791     }
1792 
1793     // package private for TRT_TreeNode for now
1794     // need a better solution
1795     void notifyNewBranch(TreeNode[] where, TreeNode what, int index) {
1796         if (treeObservers == null) return;
1797 
1798         for (int i = 0; i < treeObservers.length; i++) {
1799             treeObservers[i].nodeInserted(where, what, index);
1800         }
1801     }
1802 
1803     // package private for TRT_TreeNode for now
1804     // need a better solution
1805     void notifyNewLeaf(TreeNode[] where, TestResult what, int index) {
1806         if (treeObservers == null) return;
1807 
1808         for (int i = 0; i < treeObservers.length; i++) {
1809             treeObservers[i].nodeInserted(where, what, index);
1810         }
1811     }
1812 
1813     private void notifyChangeLeaf(TreeNode[] where, TestResult what, int index,
1814                           TestResult old) {
1815         if (treeObservers == null) return;
1816 
1817         for (int i = 0; i < treeObservers.length; i++) {
1818             treeObservers[i].nodeChanged(where, what, index, old);
1819         }
1820     }
1821 
1822     // package private for TRT_TreeNode for now
1823     // need a better solution
1824     void notifyRemoveLeaf(TreeNode[] where, TestResult what, int index) {
1825         if (treeObservers == null) return;
1826 
1827         for (int i = 0; i < treeObservers.length; i++) {
1828             treeObservers[i].nodeRemoved(where, what, index);
1829         }
1830     }
1831 
1832     void notifyRemoveLeaf(TreeNode[] where, TreeNode what, int index) {
1833         if (treeObservers == null) return;
1834 
1835         for (int i = 0; i < treeObservers.length; i++) {
1836             treeObservers[i].nodeRemoved(where, what, index);
1837         }
1838     }
1839 
1840     void notifyStartRefresh(TreeNode origin) {
1841         if (treeObservers == null) return;
1842 
1843         for (int i = 0; i < treeObservers.length; i++) {
1844             if (treeObservers[i] instanceof TreeEventObserver) {
1845                 ((TreeEventObserver)treeObservers[i]).startRefresh(origin);
1846             }
1847         }
1848     }
1849     void notifyFinishRefresh(TreeNode origin) {
1850         if (treeObservers == null) return;
1851 
1852         for (int i = 0; i < treeObservers.length; i++) {
1853             if (treeObservers[i] instanceof TreeEventObserver) {
1854                 ((TreeEventObserver)treeObservers[i]).finishRefresh(origin);
1855             }
1856         }
1857     }
1858 
1859     /**
1860      * Make all the initial files usable.  This method turns the file paths
1861      * into paths relative to the testsuite root.  It also removes any files
1862      * which match the testsuite root path.  The <tt>getPath()</tt> value is
1863      * used to get the path to resolve.
1864      *
1865      * @param tests Files to resolve.  May be zero length or null.
1866      * @return Root relative paths to the given file in internal URL format.
1867      *         Null <b>or</b> zero length if the given parameter was an empty
1868      *         set, or the given files could not be resolved in this TRT.
1869      * @throws Fault Will occur if any of the initial files are completely invalid.
1870      *         validatePath() can be used to validate things in advance.
1871      * @see #validatePath
1872      * @see java.io.File#getPath()
1873      */
1874     private String[] preProcessInitFiles(File[] tests) throws Fault {
1875         if (tests == null || tests.length == 0) {
1876             if (debug > 1)
1877                 Debug.println("Initial files set empty.");
1878             return null;
1879         }
1880 
1881         if (debug > 1) {
1882             Debug.println("Initial files: ");
1883             for (int i = 0; i < tests.length; i++)
1884                 Debug.println("  + " + tests[i].getPath());
1885         }
1886 
1887         String[] files = new String[tests.length];
1888         int filesLen = files.length;
1889         int distToDel =
1890             (getWorkDir() == null ? 0 : finder.getRootDir().getAbsolutePath().length() + 1);
1891 
1892         for (int i = 0; i < tests.length; i++) {
1893             if (debug > 1)
1894                 Debug.println(" *** init url resolve begin ***");
1895             String relativeURL = null;
1896 
1897             if (finder.getRootDir().equals(tests[i])) {
1898                 // jtreg produces initial URLs which are equal to the testsuite root,
1899                 // this isn't necessary, so we ignore those
1900                 //     maybe this should trigger a special case which causes all initial
1901                 //     URLs to be ignored and runs the whole testsuite
1902                 filesLen--;
1903                 if (debug > 1)
1904                     Debug.println("An initial URL equals testsuite root, ignoring it.");
1905 
1906                 continue;
1907             }
1908             else if (tests[i].isAbsolute()) {
1909                 String rrp = getRootRelativePath(getRoot());
1910 
1911                 if (debug > 1) {
1912                     Debug.println("  -> Initial URL is absolute, stripping from " +
1913                                        tests[i].getPath());
1914                     Debug.println("  -> Stripping: " + finder.getRootDir());
1915                     Debug.println("  -> removing rrp: " + rrp);
1916                 }
1917 
1918                 String thisInitPath = tests[i].getPath();
1919 
1920                 if (!thisInitPath.startsWith(finder.getRootDir().getPath())) {
1921                     throw new Fault(i18n, "trt.badInitFile", thisInitPath);
1922                 }
1923 
1924                 distToDel += ((rrp == null || rrp.length() == 0) ? 0 : rrp.length() + 1);
1925 
1926                 // strip length of testsuite root
1927                 String platformPath = thisInitPath.substring(distToDel);
1928 
1929                 relativeURL = platformPath.replace(File.separatorChar, '/');
1930             }
1931             else
1932                 relativeURL = tests[i].getPath().replace(File.separatorChar, '/');
1933 
1934             files[i] = relativeURL;
1935         }
1936 
1937         if (filesLen != tests.length) {
1938             String[] newFiles = new String[filesLen];
1939             System.arraycopy(files, 0, newFiles, 0, filesLen);
1940             files = newFiles;
1941         }
1942 
1943         if (debug > 1)
1944             Debug.println("*** finished preprocessing of init urls ***");
1945 
1946         return files;
1947     }
1948 
1949     /**
1950      * @param where Where to start searching.
1951      * @param fullPath Work relative path to JTR to look for.  Must not be null.
1952      * @param path Current part of the path, used to support recursion.
1953      * @return null will be returned if the node cannot be located.
1954      */
1955     private static TestResult findTest(TreeNode where, String fullPath, String path) {
1956         ((TRT_TreeNode)where).scanIfNeeded();
1957 
1958         // this is an internal routine, so params are expected to be non-null
1959 
1960         if (debug == 2 || debug == 99)
1961             Debug.println("TRT looking for " + path + " IN " + where.getName());
1962 
1963         String dir = TestResultTable.getDirName(path);
1964         TestResult tr = null;
1965 
1966         if (dir == path) {
1967             if (debug == 2 || debug == 99)
1968                 Debug.println("    -> Looking for TR in this node.");
1969 
1970             int location = ((TRT_TreeNode)where).getResultIndex(fullPath, false);
1971             if (location != -1) {
1972                 tr = (TestResult)(where.getChild(location));
1973                 if (debug == 2 || debug == 99) {
1974                     Debug.println("    -> TRT.findTest() located " + tr);
1975                     Debug.println("");
1976                 }   // debug
1977             }
1978             else {
1979                 // not found, branches exist, leaf does not
1980                 if (debug == 2 || debug == 99) {
1981                     Debug.println("    -> TRT.findTest(): unable to find node " + fullPath);
1982                     Debug.println("");
1983                 }   // debug
1984             }
1985         }
1986         else {
1987             if (debug == 2 || debug == 99)
1988                 Debug.println("    -> Looking for branch name: " + dir);
1989 
1990             TRT_TreeNode tn = ((TRT_TreeNode)where).getTreeNode(dir, false);
1991 
1992             if (tn != null) {
1993                 // go down a level
1994                 tr = findTest(tn, fullPath, TestResultTable.behead(path));
1995             }
1996             else {
1997                 // not found, a branch name does not exits
1998                 if (debug == 2 || debug == 99)
1999                     Debug.println("TRT.findTest(): unable to find node " + fullPath);
2000             }
2001         }   // outer else
2002 
2003         return tr;
2004     }
2005 
2006     /**
2007      * Get a node by path.
2008      * @since 3.2
2009      */
2010     private static Object lookupNode(TreeNode where, String url) {
2011         TreeNode tn = findNode(where, url);
2012         // matched a branch!
2013         if (tn != null) {
2014             return tn;
2015         }
2016 
2017         // if url is exec/index.html#ExecSucc
2018         // that should match a test with exactly that name
2019         String jtrPath = TestResult.getWorkRelativePath(url);
2020         TestResult tr = findTest(where, jtrPath, jtrPath);
2021         return tr;      // may be null
2022     }
2023 
2024     /**
2025      * Find a branch or leaf which matches the URL.
2026      * This method searches in the following order for the given URL:
2027      * <ul>
2028      *    <li>Branch match
2029      *    <li>Test URL match (up to and including the test id)
2030      *    <li>Set of tests match (e.g. multiple test ids, same file)
2031      * </ul>
2032      *
2033      * This method is designed to deprecate lookup(TreeNode, String[]).
2034      *
2035      * @param where Node to start recursive search at.
2036      * @param url The directory name, test URL (TestDescription.getRootRelativeURL), or
2037      *            file prefix of a set of test ids (index.html might match {index.html#t1,
2038      *            index.html#t2, index.html#t3})
2039      * @return Null if no matches.  A TreeNode, or a non-empty set of TestResults.
2040      */
2041     private static Object[] lookupInitURL(TreeNode where, String url) {
2042         if (where == null || url == null)
2043             throw new IllegalArgumentException("Starting node or URL may not be null!");
2044 
2045         if (debug == 2 || debug == 99) {
2046             Debug.println("Starting iurl lookup on " + url + " in " + where.getName());
2047         }
2048 
2049         Object simple = lookupNode(where, url);
2050         if (simple != null) {
2051             if (debug == 2 || debug == 99 && simple instanceof TreeNode) {
2052                 Debug.println("  -> simple match found " + getRootRelativePath((TreeNode)simple));
2053             }
2054 
2055             if (simple instanceof TestResult)
2056                 return new TestResult[] {(TestResult)simple};
2057             else
2058                 return new TreeNode[] {(TreeNode)simple};
2059         }
2060 
2061         // first find the node directly above where we want to search
2062         // if the url is foo/fivetests.html
2063         // that should match foo/fivetests.html#1
2064         //                   foo/fivetests.html#2
2065         //                   foo/fivetests.html#3
2066         //                   foo/fivetests.html#4
2067         //                   foo/fivetests.html#5
2068         if (debug == 2 || debug == 99) {
2069             Debug.println("TRT looking for tests beginning with " + url + " IN " + where.getName());
2070             Debug.println("   -> retrieving possible TRs from " + betail(url));
2071         }
2072         TreeNode tn = findNode(where, betail(url));
2073         if (tn == null) {   // the parent dir of the requested test does not exist
2074             if (debug == 2 || debug == 99)
2075                 Debug.println("   -> No parent node found!");
2076             return null;
2077         }
2078 
2079         TestResult[] trs = tn.getTestResults();
2080         // found anything?
2081         if (trs == null || trs.length == 0)
2082             return null;
2083 
2084         // try to partial match a test
2085         Vector<TestResult> v = new Vector<>();
2086         try {
2087             for (int i = 0; i < trs.length; i++) {
2088                 if (trs[i].getDescription().getRootRelativeURL().startsWith(url))
2089                     v.addElement(trs[i]);           // match
2090             }   // for
2091         }
2092         catch (TestResult.Fault f) {
2093             // this is a very bad thing I think
2094             throw new JavaTestError(i18n, "trt.trNoTd", f);
2095         }
2096 
2097         if (v.size() > 0) {
2098             trs = new TestResult[v.size()];
2099             v.copyInto(trs);
2100         }
2101         else {
2102             // no matches
2103             trs = null;
2104         }
2105 
2106         return trs;
2107     }
2108 
2109     /**
2110      * Once the finder variable has been set, this should be called to initialize
2111      * the rest of the object state.
2112      */
2113     private void initFinder() {
2114         suiteRoot = finder.getRoot();
2115     }
2116 
2117     void awakeCache() {
2118         if (trCache == null || !trCache.workerAlive()) {
2119             try {
2120                 trCache = new TestResultCache(workDir, updater);
2121             }
2122             catch (IOException e) {
2123                 // should consider throwing Fault, but that will destabilize too many APIs for now
2124                 updater.error(e);
2125             }
2126         }
2127     }
2128 
2129 
2130     private boolean needsCacheCompress() {
2131         /*OLD
2132         int etc = workDir.getTestSuiteTestCount();
2133 
2134         if (etc > 0) {
2135             int percent = uncompressedTestCount / etc;
2136 
2137             if (percent > COMPRESSION_THRESHOLD) {
2138                 return true;
2139             }
2140         }
2141 
2142         if (trCache.needsCompress())
2143             return true;
2144 
2145         return false;
2146         */
2147         return trCache.needsCompress();
2148     }
2149 
2150     protected void finalize() throws Throwable {
2151         super.finalize();
2152         // cleanup all the http stuff
2153         RootRegistry.getInstance().removeHandler(httpHandle);
2154         RootRegistry.unassociateObject(this, httpHandle);
2155         httpHandle = null;
2156     }
2157 
2158     /**
2159      * Update the flag indicating that an update is in progress and notify
2160      * any and all threads that might be interested.
2161      */
2162     private synchronized void setUpdateInProgress(boolean b) {
2163         updateInProgress = b;
2164         notifyAll();
2165     }
2166 
2167     /**
2168      * This method returns TestResult from map of test results, collected by
2169      * TestResultCache worker. If worker didn't finished his work yet, method
2170      * returns null and adds TestDescription of requested result to a special
2171      * set.
2172      * TestResults from this set will be updated after cache worker finishes
2173      * its work.
2174      */
2175     public TestResult getCachedResult(TestDescription td) {
2176         if (cachedResults != null) {
2177             String url = TestResult.getWorkRelativePath(td.getRootRelativeURL());
2178                 TestResult res = cachedResults.get(url);
2179                 if (res != null) {
2180                     res.setTestDescription(td);
2181                 }
2182                 return res;
2183         }
2184         else {
2185             rtc.addToUpdateFromCache(td);
2186             return null;
2187         }
2188     }
2189 
2190     /**
2191      * Inner class, which specifies methods to work with set of TestDescriptions,
2192      * which need to be updated after cache will finish his work.
2193      */
2194     private class RequestsToCache {
2195         private HashSet<TestDescription> needUpdateFromCache;
2196 
2197         public synchronized void addToUpdateFromCache(TestDescription td) {
2198             if (needUpdateFromCache == null) {
2199                 needUpdateFromCache = new HashSet<>();
2200             }
2201             needUpdateFromCache.add(td);
2202         }
2203 
2204         public HashSet<TestDescription> getRequests () {
2205             return needUpdateFromCache;
2206         }
2207 
2208         public synchronized void clear() {
2209             needUpdateFromCache = null;
2210         }
2211     }
2212 
2213     private Map<String, TestResult> cachedResults;
2214     private RequestsToCache rtc;
2215 
2216     /**
2217      * Update the flag indicating that the cache has been initialized and notify
2218      * any and all threads that might be interested.
2219      */
2220     /*OLD
2221     private synchronized void setCacheInitialized(boolean b) {
2222         cacheInitialized = b;
2223         notifyAll();
2224     }
2225     */
2226 
2227     /*OLD
2228     private static final String formatVersion = "JavaTest/Results/2.0";
2229     */
2230     private Map<String, Status>[] statusTables;
2231                                 // tables indexed by status.type mapping status.reason
2232                                 // to a unique status object
2233     private WorkDirectory workDir;
2234     private TestFinder finder;
2235     private String[] finderErrors =new String[0];
2236     private Observer[] observers = new Observer[0];
2237     private TRT_HttpHandler httpHandle;     // the http handler for this instance
2238     private TreeObserver[] treeObservers = new TreeObserver[0];
2239     private TestResultCache trCache;
2240 
2241     private boolean suppressFinderScan = false; // false is traditional
2242 
2243     private Updater updater = new Updater();
2244 
2245     /**
2246      * You must hold the lock on this object first, then change this variable, then
2247      * release in the opposite order.
2248      */
2249     private volatile boolean updateInProgress;
2250     private volatile boolean cacheInitialized = false;
2251 
2252     private List<TestResult> testsInUpdate = new Vector<>();
2253 
2254     /*
2255      * Effectively a count of the number of instances of TRTs that have been created.
2256      * It does not represent the number of existing instances though; the
2257      * purpose of the counter is to provide a unique identifier.  To use, increment
2258      * the number then use it.
2259      */
2260     private static int instanceId;
2261 
2262     /*OLD
2263      * How much of the testsuite must be run before we force a cache
2264      * compression.
2265      * /
2266     private static float COMPRESSION_THRESHOLD = 0.40f;
2267     */
2268 
2269     private TRT_TreeNode root;
2270     private File suiteRoot;
2271 
2272     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestResultTable.class);
2273 
2274     // BEGIN INNER CLASSES
2275     // the public interface to a node.  impl. is in TRT_TreeNode
2276     /**
2277      * Interface to a node which contitutes the skeleton of the test result tree.
2278      * These are in most cases equivalent to directories or folders.
2279      */
2280     public interface TreeNode {
2281         /**
2282          * Add a observer for this particular node.
2283          *
2284          * @param obs The observer to attach to this node.  Should never be
2285          *        null.
2286          */
2287         public void addObserver(TestResultTable.TreeNodeObserver obs);
2288 
2289         /**
2290          * Add a observer for this particular node.
2291          * @param obs The observer to remove.  No effect if it was never
2292          *        attached.
2293          */
2294         public void removeObserver(TestResultTable.TreeNodeObserver obs);
2295 
2296         /**
2297          * Find out how many nodes are contained in this one and below.
2298          * Use with care, this may be a high overhead call.
2299          *
2300          * @return The number of tests under this node.
2301          */
2302         public int getSize();
2303 
2304         /**
2305          * Get the parent of this node.
2306          *
2307          * @return Null if this is the root.  Another TreeNode otherwise.
2308          */
2309         public TreeNode getParent();
2310 
2311         /**
2312          * Is this the root of a tree.
2313          * A parent of null indicates this state.
2314          *
2315          * @return True if this is the root node, false otherwise.
2316          */
2317         public boolean isRoot();
2318 
2319         /**
2320          * Find out what TestResultTable this node is part of.
2321          *
2322          * @return The TRT instance which this node is contained.
2323          */
2324         public TestResultTable getEnclosingTable();
2325 
2326         /**
2327          * Has the finder been used to scan this node from secondary storage?.
2328          * This is an important performance consideration, since reading nodes
2329          * may cause a noticable pause.  This method may allow you to defer
2330          * performance penalties.  This method has no effect if a finder is not
2331          * being used by the TestResultTable.
2332          *
2333          * @return True if this location in tree has already been processed,
2334          *         false otherwise.
2335          */
2336         public boolean isUpToDate();
2337 
2338         /**
2339          * Find out how many children this node contains.
2340          * If you invoke this on a node which is being lazily read from a
2341          * TestFinder, this may cause a synchronous retrieval of data from the
2342          * TestFinder.
2343          *
2344          * @return The number of immediate children this node has.
2345          */
2346         public int getChildCount();
2347 
2348         /**
2349          * Get the child at the specified location.
2350          * May be either a TestResult or TreeNode.
2351          * If you invoke this on a node which is being lazily read from a
2352          * TestFinder, this may cause a synchronous retrieval of data from the
2353          * TestFinder.
2354          *
2355          * @param index The location to retrieve.
2356          * @return Null if there are no children here or the specified index if out of
2357          *         range.
2358          */
2359         public Object getChild(int index);
2360 
2361         /**
2362          * Get any immediate children of this node which are test result objects.
2363          *
2364          * @return List of TestResult objects in this node.  null if none
2365          */
2366         public TestResult[] getTestResults();
2367 
2368         /**
2369          * Get any immediate children of this node which are tree nodes.
2370          *
2371          * @return List of children nodes objects in this node.  null if none
2372          */
2373         public TreeNode[] getTreeNodes();
2374 
2375         /**
2376          * The name of this node, not including all the ancestors names.
2377          * This does not return a URL to this node from the root.
2378          *
2379          * @return Immediate name of this node, not the full name.
2380          * @see #getRootRelativePath
2381          */
2382         public String getName();
2383 
2384         /**
2385          * Is the given element of this node a leaf.
2386          * In general this means that the element is a TestResult.  It may also
2387          * mean that the TreeNode is empty, which indicates a testsuite which is not
2388          * well designed and needs trimming.
2389          *
2390          * @param index The element index of this node.  An out of range index
2391          *        will return false.
2392          * @return True if the element at the given index is a leaf.
2393          */
2394         public boolean isLeaf(int index);
2395 
2396         /**
2397          * Get the statistics for the state of tests under this node.
2398          *
2399          * @return An array of length Status.NUM_STATES, positionally representing
2400          *         the number of tests under this node with that state.
2401          */
2402         public int[] getChildStatus();
2403 
2404         /**
2405          * Search for a specific item in this node.
2406          *
2407          * @param target The target object should either be of type TreeNode or TestResult
2408          * @return The index at which the target object is located in this node.
2409          *         -1 if not found in in this node.  -2 if the parameter is null
2410          */
2411         public int getIndex(Object target);
2412 
2413         /**
2414          * Finds a TestResult in this node with the given name.
2415          * This is a match against the test URL, not the filename.  This is not
2416          * recursive of course; it just does a straight match against all tests
2417          * in this node.
2418          *
2419          * @param url The full name of the test to find.
2420          * @return The matching test result object, or null if not found.  Also
2421          *         null if this node has no children.
2422          * @see com.sun.javatest.TestDescription#getRootRelativeURL()
2423          */
2424         public TestResult matchTest(String url);
2425     }   // TreeNode
2426 
2427     private static class NullEnum extends TRT_Iterator {
2428         private NullEnum() {
2429             super();
2430         }
2431 
2432         // --- Iterator interface ---
2433         public boolean hasNext() {
2434             return hasMoreElements();
2435         }
2436 
2437         public TestResult next() {
2438             return nextElement();
2439         }
2440 
2441         /**
2442          * Do not call this method.
2443          *
2444          * @throws UnsupportedOperationException Not available for this iterator.
2445          */
2446         public void remove() {
2447             throw new UnsupportedOperationException("Cannot remove from TestResultTable thhrough iterator.  Do not call this method.");
2448         }
2449 
2450         // --- Enumerator interface ---
2451         public boolean hasMoreElements() {
2452             return false;
2453         }
2454 
2455         public TestResult nextElement() {
2456             throw new NoSuchElementException(i18n.getString("trt.noElements"));
2457         }
2458 
2459         public static NullEnum getInstance() {
2460             if (instance == null) instance = new NullEnum();
2461 
2462             return instance;
2463         }
2464 
2465         private static NullEnum instance;
2466     }   // NullEnum
2467 
2468     /**
2469      * Defines an iterator/enumerator interface for retrieving tests out of the
2470      * tree.  This is a read-only interface, so <code>remove()</code> is not
2471      * supported.
2472      */
2473     public interface TreeIterator extends Enumeration<TestResult>, Iterator<TestResult> {
2474         // --- Enumerator interface  ---
2475         public abstract boolean hasMoreElements();
2476         public abstract TestResult nextElement();
2477 
2478         // --- Iterator interface ---
2479         public abstract boolean hasNext();
2480         public abstract TestResult next();
2481 
2482         /**
2483          * Do not call this method.
2484          *
2485          * @throws UnsupportedOperationException Not available for this iterator.
2486          */
2487         public abstract void remove();
2488 
2489         // --- Statistics info ---
2490         /**
2491          * Find out how many tests were rejected by filters while doing iteration.
2492          * This number will be available despite the setting on setRecordRejects().
2493          *
2494          * @return The number of tests found by rejected by the filters.  The
2495          *         value will be between zero and max. int.
2496          */
2497         public abstract int getRejectCount();
2498 
2499         /**
2500          * Should the rejected tests be tracked.
2501          * The default is to not record this info, activating this feature will
2502          * make <tt>getFilterStats()</tt> return useful info.  The setting can
2503          * be changed at any time, but never resets the statistics.  The
2504          * recorded statistics represent iterator output during this object's
2505          * lifetime, while this feature was enabled.  The setting here does not
2506          * affect information from <tt>getRejectCount()</tt>
2507          * @param state True to activate this feature, false to disable.
2508          * @see com.sun.javatest.TestResultTable.TreeIterator#getFilterStats()
2509          */
2510         public abstract void setRecordRejects(boolean state);
2511 
2512         /**
2513          * Find out which states the test which have been enumerated already were in.
2514          * The result is valid at any point in time, and represent the stats for the
2515          * entire selected set of tests when hasMoreElements() is false.
2516          *
2517          * @return Indexes refer to those values found in Status
2518          * @see com.sun.javatest.Status
2519          */
2520         public abstract int[] getResultStats();
2521 
2522         /**
2523          * Find out which filters rejected which tests.
2524          * The data is valid at any point in time; hasNext() does not have to
2525          * be false.  Note that filters are evaluated in the order shown in getFilters()
2526          * and that the statistics only registered the <em>first</em> filter that rejected
2527          * the test; there may be additional filters which would also reject any given
2528          * test.
2529          * <p>
2530          * The hashtable has keys of TestResults, and values which are TestFilters.
2531          * Because of CompositeFilters, the set of filters found in the ``values''
2532          * is not necessarily equivalent to those given by getFilters().
2533          *
2534          * @return Array as described or null if no tests have been rejected yet.
2535          * @since 3.0.3
2536          */
2537         public abstract HashMap<TestFilter, ArrayList<TestDescription>> getFilterStats();
2538 
2539         // --- misc info ---
2540         /**
2541          * Find out what the effective filters are.
2542          *
2543          * @return Null if there are no active filters.
2544          */
2545         public abstract TestFilter[] getFilters();
2546 
2547         /**
2548          * Find out what the effective initial URLs for this enumerator are.
2549          * The returned array can be any combination of URLs to individual tests
2550          * or URLs to directories.  Remember these are URLs, so the paths are not
2551          * platform specific.
2552          *
2553          * @return Null if no nodes or tests were found.  Any array of the initial
2554          *         URLs otherwise.
2555          */
2556         public abstract String[] getInitialURLs();
2557 
2558         /**
2559          * Peek into the future to see which object will be returned next.
2560          * @return The next object scheduled to come out of <code>next()</code>,
2561          *         or null if <code>hasNext()</code> is false.
2562          */
2563         public abstract Object peek();
2564 
2565         /**
2566          * Will the iterator be returning the given node later.
2567          * There is no checking to ensure that the parameter is within the
2568          * iterator's "address space".  The comparison is not reference based,
2569          * but location based, so, will the test at the location indicated by
2570          * the given test be evaluated for iterator later?  This query is done
2571          * without respect to the filters.
2572          * @param node The test result indicating the location in question.
2573          * @return True if the test in question has not been passed by the
2574          *         iterator and may still be returned.  False if the given
2575          *         test will not subsequently be returned by this iterator.
2576          */
2577         public abstract boolean isPending(TestResult node);
2578     }   // TreeIterator
2579 
2580     class Updater implements TestResultCache.Observer
2581     {
2582         //-----methods from TestResultCache.Observer-----
2583         public void update(Map<String, TestResult> tests) {
2584             updateFromCache(tests);
2585         }
2586 
2587         public void waitingForLock(long timeSoFar) {
2588             // in time, could propogate this message to TRT.Observer so that
2589             // GUI code could present the info better, but, for now, stay basic
2590             int seconds = (int) (timeSoFar/1000);
2591             int minutes = seconds/60;
2592             seconds = seconds - 60*minutes;
2593             writeI18N("trt.waitingForLock",
2594                       new Object[] { workDir.getRoot(), new Integer(minutes), new Integer(seconds) });
2595         }
2596 
2597         public void timeoutWaitingForLock() {
2598             // in time, could propogate this message to TRT.Observer so that
2599             // GUI code could present the info better, but, for now, stay basic
2600             writeI18N("trt.timeoutForLock", workDir.getRoot());
2601         }
2602 
2603         public void acquiredLock() {
2604             // in time, could propogate this message to TRT.Observer so that
2605             // GUI code could present the info better, but, for now, stay basic
2606         }
2607 
2608         public void releasedLock() {
2609             // in time, could propogate this message to TRT.Observer so that
2610             // GUI code could present the info better, but, for now, stay basic
2611         }
2612 
2613         public void buildingCache(boolean reset) {
2614             // in time, could propogate this message to TRT.Observer so that
2615             // GUI code could present the info better, but, for now, stay basic
2616             rebuildCount = 0;
2617         }
2618 
2619         public void buildingCache(TestResult tr) {
2620             // in time, could propogate this message to TRT.Observer so that
2621             // GUI code could present the info better, but, for now, stay basic
2622             rebuildCount++;
2623             if (rebuildCount == 100)
2624                 writeI18N("trt.rebuild");
2625             else if ((rebuildCount % 100) == 0)
2626                 System.err.println(".");
2627         }
2628 
2629         public void builtCache() {
2630             // in time, could propogate this message to TRT.Observer so that
2631             // GUI code could present the info better, but, for now, stay basic
2632             if (rebuildCount > 0)
2633                 System.err.println();
2634         }
2635 
2636         public void error(Throwable t) {
2637             // in time, could propogate this message to TRT.Observer so that
2638             // GUI code could present the info better, but, for now, stay basic
2639             writeI18N("trt.cacheError", t);
2640             t.printStackTrace();
2641         }
2642 
2643         private void writeI18N(String key) {
2644             System.err.println(i18n.getString(key));
2645         }
2646 
2647         private void writeI18N(String key, Object arg) {
2648             System.err.println(i18n.getString(key, arg));
2649         }
2650 
2651         private void writeI18N(String key, Object[] args) {
2652             System.err.println(i18n.getString(key, args));
2653         }
2654 
2655         private int rebuildCount;
2656 
2657         /*OLD
2658         public void newEntry(TestResult result) {
2659             if (debug > 9)
2660                 Debug.println("TRT - New TR from cache: " + result.getWorkRelativePath());
2661 
2662             if (testsInUpdate.contains(result)) {
2663                 // this TRT instance just inserted this test
2664                 if (debug > 9)
2665                     Debug.println("   -> ignoring");
2666 
2667                 return;
2668             }
2669 
2670             if (result != null)
2671                 insert(result);
2672         }
2673         */
2674 
2675         /*
2676         public void cacheResetting() {
2677             if (debug)
2678                 Debug.println("TRT - cache indicates restart beginning.");
2679         }
2680         */
2681 
2682         /*OLD
2683         public void resetCache()  {
2684             synchronized (TestResultTable.this) {
2685                 while (updateInProgress) {
2686                     try {
2687                         TestResultTable.this.wait(3000); // will be notified when updated; time allows debug monitor
2688 
2689                         // abort
2690                         if (cacheShutdown)
2691                             return;
2692                     }
2693                     catch (InterruptedException e) {
2694                         if (debug > 0)
2695                             e.printStackTrace(Debug.getWriter());
2696                     }   // catch
2697                 }   // while
2698 
2699                 setUpdateInProgress(true);
2700             }
2701 
2702             // hope this doesn't happen too often, because it is really
2703             // expensive
2704             if (debug > 0)
2705                 Debug.println("TRT(" +TestResultTable.this+") - Received cache reset message.");
2706 
2707             // NOTE: this will result in flushing the pre-existing test
2708             //       results which may contain full JTR info.  getEntries()
2709             //       always produces minimal size TR objects
2710 
2711             Thread updateThread = new Thread() {
2712                 { setName("TRT Background Cache Reset"); }
2713 
2714                 public void run() {
2715                     // too expensive to do in the foreground
2716                     try {
2717                         final Set newSet = trCache.getEntries();
2718 
2719                         // abort if not needed
2720                         if (cacheShutdown) {
2721                             setUpdateInProgress(false);
2722                             return;
2723                         }
2724 
2725                         update(newSet);
2726                     }
2727                     catch (TestResultCache.CacheShutdownFault f) {
2728                         // ignore, handled below...
2729                     }
2730                     catch (TestResultCache.Fault f) {
2731                         if (debug > 0)
2732                             f.printStackTrace();
2733                         throw new JavaTestError(i18n, "trt.noEntries", f);
2734                     }   // catch
2735 
2736                     setUpdateInProgress(false);
2737 
2738                 }
2739 
2740                 protected void finalize() throws Throwable {
2741                     setUpdateInProgress(false);
2742                 }
2743             };  // anonymous thread
2744 
2745             // abort if not needed
2746             if (cacheShutdown) {
2747                 return;
2748             }
2749 
2750             updateThread.setPriority(Thread.MIN_PRIORITY + 2);
2751             updateThread.start();
2752         }
2753         */
2754 
2755         /*OLD
2756         public void cacheShutdown() {
2757             cacheShutdown = true;
2758         }
2759         */
2760     }
2761 
2762     /**
2763      * Keeps track of the path to a specific node in the TestResultTable tree.
2764      * This class is made available so that a path which consists of zero or more
2765      * TreeNodes plus zero or one TestResult can be represented without using a
2766      * Object[].
2767      */
2768     public class PathRecord {
2769         PathRecord() {
2770         }
2771 
2772         /**
2773          * Create a path with only one element, the target TestResult.
2774          */
2775         PathRecord(TestResult tr) {
2776             this.tr = tr;
2777         }
2778 
2779         /**
2780          * Create a path which represents the path to a leaf node, the TestResult.
2781          */
2782         PathRecord(TreeNode[] path, TestResult tr) {
2783             this.tr = tr;
2784             nodes = path;
2785         }
2786 
2787         /**
2788          * @param path The TreeNode objects that correspond to the path.
2789          * @param inds The indexes of each TreeNode in the tree.
2790          *             This is really duplicated information, but will remove the
2791          *             need to search for a node at each level.
2792          */
2793         PathRecord(TreeNode[] path, int[] inds) {
2794             nodes = path;
2795             this.inds = inds;
2796         }
2797 
2798         /**
2799          * The end of the path.
2800          */
2801         void setTestResult(TestResult tr) {
2802             this.tr = tr;
2803         }
2804 
2805         /**
2806          * @exception JavaTestError Will be thrown if you attempt to add a node to a
2807          *            path that already has a leaf (TestResult) assigned.
2808          */
2809         void addNode(TreeNode tn) {
2810             if (tr != null) throw new JavaTestError(i18n, "trt.invalidPath");
2811 
2812             nodes = DynamicArray.append(nodes, tn);
2813         }
2814 
2815 /*
2816         public synchronized void append(Object node, int index) {
2817             int[] newArr = new int[inds.length+1];
2818             System.arraycopy(inds, 0, newArr, 0, inds.length);
2819             inds = newArr;
2820 
2821             nodes = DynamicArray.append(nodes, node);
2822 
2823             System.out.println("Path Recorded");
2824             for (int i = 0; i < nodes.length; i++)
2825                 System.out.println(nodes[i] + "   " + inds[i]);
2826         }
2827 */
2828 
2829         /**
2830          * Provides the indexes into each node provided by <code>getNodes()</code>.
2831          *
2832          * @return The indexes of the corresponding TreeNode at each level.  Null if
2833          *         no index information is available;
2834          */
2835         public int[] getIndicies() {
2836             if (inds == null) {
2837                 inds = generateInds(tr);
2838             }
2839 
2840             return inds;
2841         }
2842 
2843         /**
2844          * Get the nodes that represent the path.
2845          *
2846          * @return The path, closest to the root at the beginning of the array.
2847          */
2848         public TreeNode[] getNodes() {
2849             if (nodes == null) {
2850                 nodes = generateNodes(tr);
2851             }
2852 
2853             return nodes;
2854         }
2855 
2856         /**
2857          * Generate the path to a given test.
2858          *
2859          * @param tr The test to generate the path to.
2860          * @return The path that leads to the given test.
2861          */
2862         public /* static */ TreeNode[] generateNodes(TestResult tr) {
2863             if (tr == null)  return null;
2864 
2865             TreeNode[] nodes = null;
2866             TreeNode node = tr.getParent();
2867 
2868             while (node != null) {
2869                 nodes = DynamicArray.insert(nodes, node, 0);
2870                 node = node.getParent();
2871             }
2872 
2873             return nodes;
2874         }
2875 
2876         private /* static */ int[] generateInds(TestResult tr) {
2877             // XXX implement me!
2878             return null;
2879         }
2880 
2881         private TreeNode[] nodes;
2882         private int[] inds;
2883         private TestResult tr;
2884     }   // PathRecord
2885 
2886     /**
2887      * Remove one directory from the beginning of the path.
2888      *
2889      * @param path The path to manipulate.
2890      * @return Beheaded path, or the <b>same</b> object if there is
2891      *         no leading directory to strip.
2892      */
2893     static String behead(String path) {
2894         int index = path.indexOf("/");
2895 
2896         if (index == -1)
2897             return path;
2898         else {
2899             // assume file separator is 1 char
2900             return path.substring(index+1);
2901             //return path.substring(index+File.separator.length());
2902         }
2903     }
2904 
2905     /**
2906      * Gives the first directory name in the path.
2907      *
2908      * @param path The path to manipulate.
2909      */
2910     static String getDirName(String path) {
2911         int index = path.indexOf('/');
2912 
2913         if (index == -1)
2914             return path;
2915         else
2916             return path.substring(0, index);
2917     }
2918 
2919     /**
2920      * Opposite of behead - removes the last filename.
2921      *
2922      * @param path The path to manipulate.
2923      */
2924     static String betail(String path) {
2925         int index = path.lastIndexOf('/');
2926 
2927         if (index == -1)
2928             return path;
2929         else
2930             return path.substring(0, index);
2931     }
2932 
2933     /**
2934      * Does the given array contain the given object.
2935      * @return True if o is in arr, false if arr is null or zero length,or
2936      *         does not contain o.
2937      */
2938     static boolean arrayContains(Object[] arr, Object o) {
2939         if (arr == null || arr.length == 0)
2940             return false;
2941         else {
2942             for (int i = 0; i < arr.length; i++)
2943                 if (arr[i] == o)
2944                     return true;
2945         }
2946 
2947         return false;
2948     }
2949 
2950     private ReentrantLock processLock = new ReentrantLock();
2951 
2952     public ReentrantLock getLock() {
2953         return processLock;
2954     }
2955 
2956     static final Status notYetRun = Status.notRun("test is awaiting execution");
2957     private static int debug = Debug.getInt(TestResultTable.class);
2958 }