/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.Vector;

import com.sun.javatest.util.Debug;
import com.sun.javatest.util.I18NResourceBundle;

/**
 * This class processes the iteration with the following priorities:
 * <ol>
 * <li>return any tests which were explicitly supplied at construction time
 * <li>serially exhaust all tests underneath each node specified at construction time
 * <li>return tests found by walking the tree in an ``in-order'' manner
 * </ul>
 * The iterator operates by immediately returning explicit tests.  If not explicit
 * tests are available, the ``next'' methods always keeps one ahead of the current
 * request so that it can answer the ``has more?'' question.  On each ``next'' request,
 * it searches for the next test it can distribute.
 */
class TRT_Iterator implements TestResultTable.TreeIterator {
/*
 * path[pathIdx] points to the next available TestResult
 * finished indicates that we're done
 */
    /**
     * Initialize the outQueue and result stats.  This is here only to share
     * construction code and does not create a usable object.
     */
    protected TRT_Iterator() {
        outQueue = new LinkedList();
        resultStats = new int[Status.NUM_STATES];
        nodeIndex = -1;
        currFrame = null;
    }

    TRT_Iterator(TestResultTable.TreeNode node) {
        this();
        nodes = new TestResultTable.TreeNode[1];
        nodes[0] = node;
        init(nodes);

        if (debug)
            Debug.println("Created TreeIterator without filters, one initial node.");
    }

    /**
     * Enumerate elements below and including the specified node.
     *
     * @param filters Which filters to apply to the tests found.  May be null.
     */
    TRT_Iterator(TestResultTable.TreeNode node, TestFilter[] filters) {
        this();
        this.filters = filters;
        nodes = new TestResultTable.TreeNode[1];
        nodes[0] = node;
        init(nodes);

        if (debug)
            Debug.println("Created TreeIterator with filters and one initial node.");
    }

    /**
     * Enumerate elements below and including the specified nodes.
     * This is mainly here to support initial URLs.
     *
     * @param nodes Which nodes to enumerate.  May be null or zero length.  This will be
     *        shallow copied.
     * @param filters Which filters to apply to the tests found.  May be null.
     */
    TRT_Iterator(TestResultTable.TreeNode[] nodes, TestFilter[] filters) {
        this();
        this.filters = filters;

        if (nodes != null) {
            this.nodes = new TestResultTable.TreeNode[nodes.length];
            System.arraycopy(nodes, 0, this.nodes, 0, nodes.length);
        }

        init(this.nodes);

        if (debug)
            Debug.println("Created TreeIterator with filters and initial nodes.");
    }

    /**
     * Enumerate elements below and including the specified nodes.
     * This is mainly here to support initial URLs.
     *
     * @param nodes Which nodes to enumerate.  May be null or zero length.  This will be
     *        shallow copied.
     * @param trs   A given set of TestResults which should be enumerated.  These
     *              are sent through the filters.  This set of tests is shallow copied.
     * @param filters Which filters to apply to the tests found.  May be null.
     */
    TRT_Iterator(TestResultTable.TreeNode[] nodes, TestResult[] trs, TestFilter[] filters) {
        this();
        this.filters = filters;

        if (trs != null && trs.length != 0) {
            Vector names = new Vector();

            // prime the outqueue with the given tests
            for (int i = 0; i < trs.length; i++) {
                try {
                    if (wouldAccept(trs[i]) == -1) {
                        outQueue.addLast(trs[i]);
                        names.add(trs[i].getDescription().getRootRelativeURL());
                    }
                    else { }
                }   // try
                catch (TestResult.Fault f) {
                }
            }   // for

            initialTests = new String[names.size()];
            names.copyInto(initialTests);
        }

        if (nodes != null) {
            this.nodes = new TestResultTable.TreeNode[nodes.length];
            System.arraycopy(nodes, 0, this.nodes, 0, nodes.length);
        }

        init(this.nodes);

        if (debug)
            Debug.println("Created TreeIterator with filters, nodes and initial TR set.");
    }

    // --- Enumerator interface  ---
    public boolean hasMoreElements() {
        if (!finished) {
            return true;
        }
        else
            return false;
    }

    public Object nextElement() {
        TestResult val = null;

        if (hasMoreElements()) {
            synchronized (outQueueLock) {
                val = (TestResult)(outQueue.removeFirst());
            }   // sync

            findNext();

            // XXX refresh val from TRT?
            // the test in the TRT may have changed since it was selected by
            // findNext()

            resultStats[val.getStatus().getType()]++;

            return val;
        }
        else {
            throw new NoSuchElementException(i18n.getString("trt.noElements"));
        }
    }

    // --- Iterator interface ---
    public boolean hasNext() {
        return hasMoreElements();
    }

    public Object next() {
        return nextElement();
    }

    /**
     * Do not call this method.
     *
     * @throws UnsupportedOperationException Not available for this iterator.
     */
    public void remove() {
        throw new UnsupportedOperationException("Cannot remove from TestResultTable thhrough interator.  Do not call this method.");
    }

    // --- Statistics info ---
    /**
     * Find out how many tests have been processed.
     * This count includes tests which have been filtered out and is an
     * instantaneous snapshot.
     */
    public int getProcessedCount() {
        return absoluteCount;
    }

    public int getRejectCount() {
        return rejectCount;
    }

    /**
     * Determines whether or not filtered out tests are recorded for future use
     * or not.  This does not affect operation of <tt>getRejectCount()</tt>.
     * By default, this setting is false.
     * @param state True if rejected tests should be logged, false otherwise.
     */
    public void setRecordRejects(boolean state) {
        recordRejects = state;

        if (recordRejects == true) {
            // only create new objects if they don't exist
            // users can turn on and off this feature if they want
            if (filteredTRs == null)
                filteredTRs = new Hashtable(10);        // not likely to have > 10 filters

            if (fo == null)
                fo = new FilterObserver();
        }
    }

    public int[] getResultStats() {
        int[] copy = new int[resultStats.length];
        System.arraycopy(resultStats, 0, copy, 0, resultStats.length);

        return copy;
    }

    /**
     * Find out which filters rejected which tests.
     * The data is valid at any point in time; hasNext() does not have to
     * be false.  Note that filters are evaluated in the order shown in getFilters()
     * and that the statistics only registered the <em>first</em> filter that rejected
     * the test; there may be additional filters which would also reject any given
     * test.
     * <p>
     * The hashtable has keys of TestResults, and values which are TestFilters.
     * Because of CompositeFilters, the set of filters found in the ``values''
     * is not necessarily equivalent to those given by getFilters().
     *
     * @return Array as described or null if no tests have been rejected yet.
     */
    public Hashtable getFilterStats() {
        if (filteredTRs == null)
            return null;
        else {
            // create shallow, reformatted version of stored data
            // keys are TRs, values are TestFilters.
            Hashtable out = new Hashtable();

            synchronized (rejLock) {
                Enumeration keys = filteredTRs.keys();
                // each key is a TestFilter
                while (keys.hasMoreElements()) {
                    // could cast this to TestFilter, but why?
                    Object thisKey = keys.nextElement();

                    // the HT value is a Vector or TRs
                    Iterator it = ((Vector)(filteredTRs.get(thisKey))).iterator();

                    while (it.hasNext()) {
                        out.put(it.next(), thisKey);
                    }   // inner while
                }   // outer while
            }   // sync

            return out;
        }
    }

    /**
     * Find out what the effective filters are.
     *
     * @return Null if there are no active filters.
     */
    public TestFilter[] getFilters() {
        if (filters == null || filters.length == 0)
            return null;
        else {
            // shallow copy it
            TestFilter[] copy = new TestFilter[filters.length];
            System.arraycopy(filters, 0, copy, 0, filters.length);
            return copy;
        }

    }

    /**
     * Find out what the effective initial URLs for this iterator are.
     * The returned array can be any combination of URLs to individual tests
     * or URLs to directories.  Remember these are URLs, so the paths are not
     * platform specific.
     * <p>
     * Note: this method isn't free, cache the data if you need to call this
     * method repeatedly.  The initial URLs will not change over time for any
     * particular instance of this class.
     *
     * @return Null if no specific nodes or tests were requested.  Any array of the initial
     *         URLs otherwise.
     */
    public String[] getInitialURLs() {
        String[] list = null;
        Vector urls = new Vector();

        // can we correctly show the effective urls from here?  what about TCs?
        if (nodes != null) {
            for (int i = 0; i < nodes.length; i++)
                urls.addElement(TestResultTable.getRootRelativePath(nodes[i]));
        }

        if (initialTests != null) {
            for (int i = 0; i < initialTests.length; i++)
                urls.add(initialTests[i]);
        }

        if (urls.size() > 0) {
            list = new String[urls.size()];
            urls.copyInto(list);
        }

        return list;
    }

    public Object peek() {
        if (hasNext())
            synchronized (outQueueLock) {
                return outQueue.getFirst();
            }   // sync
        else
            return null;
    }

    public boolean isPending(TestResult node) {
        if (!hasMoreElements())
            return false;

        String testName = node.getTestName();
        String partial = testName;

        synchronized (outQueueLock) {
            // compare to outgoing test result objects
            if (outQueue.size() > 0)
                for (int i = 0; i < outQueue.size(); i++) {
                    TestResult tr = (TestResult)(outQueue.get(i));
                    if (tr.getTestName().equals(testName)) {
                        return true;
                    }
                }   // for
        }       // sync

        boolean result = false;
        boolean done = false;
        if (nodes != null) {
            for (int i = nodeIndex; i < nodes.length; i++) {
                String nodePath = TestResultTable.getRootRelativePath(nodes[i]);

                if (testName.startsWith(nodePath)) {
                    if (i > nodeIndex) {    // haven't processed that init. node yet
                        result = true;
                        done = true;
                        break;
                    }
                    else if (i < nodeIndex) { // passed this location already
                        result = false;
                        done = true;
                        break;
                    }
                    else {                  // working at this location
                        // if this is a root iterator, we always end up in this
                        // case

                        // remove the "known" part of the location
                        // if we are at node lang/foo, the test name
                        // lang/foo/bar/baz.html becomes bar/baz.html and
                        // we continue...
                        if (nodePath.length() != 0)     // root nodePath is ""
                            partial = testName.substring(nodePath.length()+1);

                        // traverse the stack
                        // XXX this could be made more efficienet
                        for (int j = 0; j < stack.size(); j++) {
                            String dir = TestResultTable.getDirName(partial);
                            partial = TestResultTable.behead(partial);

                            // XXX special cases here...

                            // it would be nicer if we could just cast to TRT.TreeNode
                            PseudoFrame frame = (PseudoFrame)(stack.elementAt(j));
                            TRT_TreeNode tn = (TRT_TreeNode)(frame.getNode());
                            int pos = tn.getNodeIndex(dir, false);
                            int currIndex = frame.getCurrentIndex();

                            // special case where test is at top of testsuite
                            if (testName.indexOf("/") == -1)
                                pos = currIndex;

                            // possible outcomes:
                            // 1 test in question can not be found
                            // 2 test path indicates that we've passed it's position
                            // 3 we are working along the test's path
                            // 4 the test's path is ahead of our position

                            // not present or already past
                            if (pos == -1 || (pos < currIndex)) {   // case 1-2
                                result = false;
                                done = true;
                                break;
                            }
                            else if (pos == frame.getCurrentIndex()) { // case 3
                                if (partial.indexOf('/') != -1) {
                                    // more depth needed, go around again
                                    continue;
                                }
                                else if (j+1 == stack.size()) {     // test should be in currFrame
                                    done = false;   // jump out to special case
                                    break;
                                }
                                else {                              // test should be in this frame
                                    switch (checkTestPosition(frame, testName)) {
                                    case -1:
                                    case 0 :
                                        result = false;
                                        done = true;
                                        break;
                                    case 2 :
                                        result = true;
                                        done = true;
                                        break;
                                    case 1 :
                                    default:
                                        throw new IllegalStateException();
                                    }   // switch

                                    break;
                                }
                            }
                            else {                                  // case 4
                                result = true;
                                done = true;
                                break;
                            }
                        }   // inner for

                        // assertions:
                        // the only ways out of the loop above
                        // - determined answer that this method is answering
                        //   in which case done == true
                        // - loop exhaused the stack and done == false

                        // need to check exact iterator location
                            // chop at depth of incoming node
                            // chop stack size
                            // search for first path comp. in currFrame
                            // match node name, compare to location
                    }
                }
            }   // outer for

            // special case to check currFrame which is never
            // represented on the stack
            if (!done) {
                TRT_TreeNode tn = (TRT_TreeNode)(currFrame.getNode());
                String dir = TestResultTable.getDirName(partial);
                partial = TestResultTable.behead(partial);

                if (partial.indexOf("/") == -1) {
                    switch (checkTestPosition(currFrame, testName)) {
                    case -1:
                    case 0 :
                        result = false;
                        break;
                    case 2 :
                        result = true;
                        break;
                    case 1 :
                    default:
                        throw new IllegalStateException();
                    }   // switch
                }
                else if (!dir.equals(currFrame.getNode().getName())) {
                    result = false;         // can't find location
                    // test is *below* this node
                    dir = TestResultTable.getDirName(partial);
                    tn = (TRT_TreeNode)(currFrame.getNode());
                    int pos = tn.getNodeIndex(dir, false);
                    int currIndex = currFrame.getCurrentIndex();

                    // possible outcomes:
                    // 1 test in question can not be found
                    // 2 test path indicates that we've passed it's position
                    // 3 we are working along the test's path
                    // 4 the test's path is ahead of our position

                    if (pos == -1) {                        // case 1
                        result = false;
                    }
                    else if (pos < currIndex) {             // case 2
                        result = false;
                    }
                    else if (pos == currIndex) {            // case 3
                        // not possible?
                        throw new IllegalStateException("");
                    }
                    else {                                          // case 4
                        result = true;
                    }
                }
                else {
                    int pos = tn.getTestIndex(testName);
                    if (pos == -1) {        // not found
                        result = false;
                    }

                    if (pos > currFrame.getCurrentIndex()) {
                        result = true;
                    }
                    else {
                        result = false;
                    }
                }
            }   // if !done

        }   // outer if

        return result;
    }

    // ------------------ TreeIterator Private --------------------
    /**
     * Initialize state.  Should be called during construction, but after filters are set.
     * outQueue should be populated before you call this method.  This method should only be
     * called at construction time since it is not synchronized.
     *
     * @param nodes May be null or zero length.
     */
    private void init(TestResultTable.TreeNode[] nodes) {
        if (nodes != null && nodes.length == 0)
            this.nodes = null;
        else
            this.nodes = nodes;

        boolean hasNodes = nextNode();

        if (hasNodes)
            findNext();
        else if (outQueue.size() == 0)
            finished = true;
        // else outQueue.length != 0, so we continue
    }

    /**
     * Move the object state to provide the next available element.
     *
     * @return Has the next test been found.  If not, recursion down another
     *         path is requested.
     */
    private synchronized void findNext() {
        boolean done = false;

        if (finished == true)
            return;

        while (!done) {
            // init(),nextFrame() will set currFrame if there are any nodes to
            // process.  If this is null, our work is done.
            if (currFrame == null) {
                synchronized (outQueueLock) {
                    if (outQueue.size() == 0)
                        finished = true;
                }   // sync
                return;
            }

            // get data from current node
            int nextIndex = currFrame.nextIndex();
            if (nextIndex == -1) {          // done with this node
                nextFrame();
                continue;
            }
            else {                          // keep going
                Object anonNode = currFrame.getNode().getChild(nextIndex);
                if (anonNode == null) {     // error condition really
                    nextFrame();
                    continue;               // attempt to recover by skipping
                }

                if (anonNode instanceof TestResultTable.TreeNode) {
                    // need to recurse into this node
                    // setup stack frames and recurse
                    TestResultTable.TreeNode node = (TestResultTable.TreeNode)anonNode;
                    push(currFrame);
                    currFrame = new PseudoFrame(node);
                    continue;               // recurse
                }
                else {
                    // we can assume that the node is a test since it is not
                    // null and not a node
                    TestResult test = (TestResult)anonNode;

                    // check the filters
                    try {
                        int would = wouldAccept(test);

                        if (would >= 0) {               // rejected
                            continue;           // recurse
                        }
                    }
                    catch (TestResult.Fault f) {

                        TestResultTable trt = null;
                        // need handle onto TRT to reset
                        // could use TR.getParent(), but it's
                        // probably better not to trust it
                        if (nodes != null && nodes[0] != null)
                            trt = nodes[0].getEnclosingTable();
                        else if (test.getParent() != null) {
                            trt = test.getParent().getEnclosingTable();
                        }
                        else
                            continue;

                        if (trt == null)
                            continue;
                        else {
                            test = trt.resetTest(test);
                            // nowr retry once
                            try {
                                if (wouldAccept(test) >= 0)
                                    continue;   // rejected
                            }
                            catch (TestResult.Fault f2) {
                                if (debug)
                                    f2.printStackTrace(Debug.getWriter());
                                // give up
                                continue;
                            }
                        }
                    }

                    // not filtered out
                    // put this test into the output queue
                    synchronized (outQueueLock) {
                        outQueue.addLast(test);
                    }   // sync

                    done = true;
                }
            }
        }   // while
    }

    /**
     * Is this location the top of an initial node.
     * In the simplist case where we are iterating from the root, is this the
     * root node?
     */
    private boolean isTop(TestResultTable.TreeNode where) {
        if (where.isRoot() || nodes[nodeIndex] == where)
            return true;
        else
            return false;
    }

    /**
     * Move to the next initial node by changing the value of currFrame.
     * This method selects the next node in the queue to process.  currFrame will
     * be null if this method returns false;
     *
     * @return True if at least one node to enum. has been found.  False
     *         otherwise.
     */
    private boolean nextNode() {
        // stacks only valid in the context of a single node
        // we create a new one when we move to a new initial node
        stack = new Stack();

        // invalidate the current frame
        // NOTE: a null currFrame will terminate the iteration in findNext()
        currFrame = null;

        if (nodes != null && ++nodeIndex < nodes.length) {
            // more on to the next node
            TestResultTable.TreeNode next = nodes[nodeIndex];
            currFrame = new PseudoFrame(next);

            return true;
        }
        else {
            // no more nodes
            return false;
        }
    }

    /**
     * Move down one level in the tree.
     * The path basically operates like a program stack and remembers what we
     * were doing at each level so we can continue when we get back there.
    private void push(TestResultTable.TreeNode newLocation) {
        // increase available recording depth
        if (pathIdx + 1 == path.length) {
            // need more array space
            int[] temp = new int[path.length+1];
            System.arraycopy(path, 0, temp, 0, path.length);
            path = temp;
        }

        pathIdx++;
        path[pathIdx] = -1;
        location = (TestResultTable.TreeNode)newLocation;
    }
     */

    private void push(PseudoFrame frame) {
        stack.push(frame);
    }

    /**
     * @return The next frame on the stack.  Null if stack is empty.
     */
    private PseudoFrame pop() {
        if (!stack.empty())
            return (PseudoFrame)(stack.pop());
        else
            return null;
    }

    /**
     * Get the next frame off the stack or goto the next node in the input queue.
     */
    private void nextFrame() {
        currFrame = pop();

        // if we reach the bottom of the stack, we need a new node
        // it will set currFrame for us
        if (currFrame == null)
            nextNode();
    }

    /**
     * This method used to be boolean, but was changed to int to collect stats.
     *
     * @param tr The test to run through the filter, must not be null
     * @return The index into filters[] which rejected the test, -1 if it would be accepted.
     * @throws TestResult.Fault May happen when requesting info from the TestResult,
     *         probably a reload fault.
     */
    private int wouldAccept(TestResult tr) throws TestResult.Fault {
        if (filters == null || filters.length == 0)
            return -1;

        if (debug)
            Debug.println("Iterator checking filter for: " + tr.getWorkRelativePath());

        absoluteCount++;
        currentResult = tr;

        for (int i = 0; i < filters.length; i++) {
            boolean accepted = true;
            try {
                if (fo == null)
                    accepted = filters[i].accepts(tr.getDescription());
                else
                    accepted = filters[i].accepts(tr.getDescription(), fo);
            }
            catch (TestFilter.Fault f) {
                accepted = true;
                if (debug)
                    Debug.println("   -> exception while checking filter: " + f.getMessage());
            }   // catch

            if (!accepted) {
                if (debug) {
                    Debug.println("   -> Rejected by: " + filters[i]);
                    Debug.println("   -> Test Status: " + tr.getStatus().getType());
                }

                rejectCount++;
                // rejected
                return i;
            }
        }   // for

        // accepted
        return -1;
    }

    /**
     * @return -1==not found, 0==before, 1==current pos., 2==after, 3==error
     */
    private static int checkTestPosition(PseudoFrame frame, String testName) {
        // need to determine if it is before or after the
        // current iterator position
        TRT_TreeNode tn = (TRT_TreeNode)(frame.getNode());
        int targetIndex = tn.getTestIndex(testName);
        int currIndex = frame.getCurrentIndex();
        int result = 3;

        if (targetIndex == -1)
            result = -1;
        else if (targetIndex < currIndex)
            result = 0;
        else if (targetIndex == currIndex)
            result = 1;
        else if (targetIndex > currIndex)
            result = 2;
        else
            result = 3;

        return result;
    }

    /**
    private TestResultTable.TreeNode traverseToNode(TestResultTable.TreeNode start, String path) {
        String curr;
        TestResultTable.TreeNode pos;

        while (path != null) {
        }   // while
    }
    */

    /**
     * Tests (literally) that were provided by the client through one of the
     * constructors.  This is mainly here to facilitate recall of the initial URL
     * list if requested.
     */
    private String[] initialTests;

    /**
     * Queue which holds all outgoing tests.  This is initially contains the tests
     * provided by the client.
     */
    private Object outQueueLock = new Object();
    private LinkedList outQueue;  // queue of tests to be given out

    private TestResultTable.TreeNode[] nodes;       // which nodes we are enumerating
    private int nodeIndex;                          // which node are we working on

    private TestFilter[] filters;
    private int[] resultStats;          // pass/fail/error statistics
    private int absoluteCount;          // how many tests were processed


    // filter rejection info
    private int rejectCount;
    private boolean recordRejects;      // true when we are collecting reject stats
    private Hashtable filteredTRs;      // key==TestFilter  value=Vector of TestResults
    private TestResult currentResult;   // necessary communicate with filter observer, yuck
    private Object rejLock;
    private FilterObserver fo;          // null if feature is disabled

    // ------ state information ------
    private Stack stack;
    private PseudoFrame currFrame;

    private boolean finished = false;
    private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TRT_Iterator.class);
    protected boolean debug = Debug.getBoolean(TRT_Iterator.class);

    // INNER CLASS
    /**
     * We can't use real recursion to implement an iterator, so this is a simulated,
     * persistent stack frame which will store state as we traverse the tree.
     */
    private static class PseudoFrame {
        /**
         * @param node The tree node which this frame is responsible for.
         */
        PseudoFrame(TestResultTable.TreeNode node) {
            this.node = node;
            this.currIndex = -1;
        }

        /**
         * Next index in this node to process.
         *
         * @return -1 if work in this node is complete
         */
        int nextIndex() {
            if (++currIndex < node.getChildCount())
                return currIndex;
            else
                return -1;
        }

        /**
         * What node does this frame track?
         */
        TestResultTable.TreeNode getNode() {
            return node;
        }

        /**
         * What was the last index returned.
         * @return A value greater than zero, unless the node has been
         *         exhausted, in which case -1.
         */
        int getCurrentIndex() {
            return currIndex;
        }

        private int currIndex;
        private TestResultTable.TreeNode node;
    }

    private class FilterObserver implements TestFilter.Observer {
        public void rejected(TestDescription d, TestFilter rejector) {
            synchronized (rejLock) {
                Vector vec = (Vector)(filteredTRs.get(rejector));
                if (vec == null) {
                    vec = new Vector();
                    filteredTRs.put(rejector, vec);
                }

                // remove later...
                try {
                    if (currentResult.getDescription() != d)
                        throw new JavaTestError("TRT_Iterator observered TR.TD does not match filtered one.");
                }
                catch (TestResult.Fault f) {
                    throw new JavaTestError("TRT_Iterator cannot determine TR source info.", f);
                }   // sanity check

                vec.add(currentResult);

            }   // sync
        }
    }
}       // TRT_Iterator
