/*************************************************************************
 Copyright © 2000-2003 Novell, Inc. All Rights Reserved.

 THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 TREATIES. USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE
 AGREEMENT ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS
 THIS WORK. PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS
 TO DEVELOPER A ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S
 SAMPLE CODE IN ITS PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION
 RIGHTS TO MARKET, DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT
 OF DEVELOPER'S PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR
 DEVELOPER'S CUSTOMERS WITH RESPECT TO THIS CODE.
 ************************************************************************/

package com.novell.nds.dirxml.driver.vrtest;


import java.text.*;
import java.util.*;
import java.io.IOException;

import org.w3c.dom.*;
import com.novell.nds.dirxml.driver.*;
import com.novell.nds.dirxml.driver.xds.*;
import com.novell.nds.dirxml.vrtest.VRTestException;
import com.novell.nds.dirxml.vrtest.VRTestAttribute;


/**
 *  Common implementation for the VRTest Driver.
 *  <p>
 *  This class contains common methods and fields used in the DriverShim,
 *  PublicationShim, and SubscriptionShim implementations of the VRTest
 *  Driver.
 */
public abstract class CommonImpl
        implements Constants
{

    //these constants are used to construct a <source> element in
    //  documents sent to the DirXML engine:

    /** The name of this driver. */
    static final public String PRODUCT_NAME = "VRTest DirXML Driver";
    /** The company that wrote it. */
    static final public String COMPANY_NAME = "Novell, Inc.";
    /** The marketing version of this driver. */
    static final public String VERSION = "1.1";

    /**
     *  A temporary trace identifier for this driver.
     *  <p>
     *  Temporarily identifies this driver in trace messages
     *  until the driver's RDN is known.
     */
    static protected final String TRACE_ID = "VRTest Driver";

    /**
     *  Default runCount.
     *  <p>
     * @see #runCount
     */
    static protected final String DEFAULT_COUNT = "0";

    /**
     *  How much to increment <code>runCount</code> by.
     *  <p>
     *  @see #runCount
     */
    static protected final int COUNT_INCREMENT = 1;

    //custom tags that contain driver state info:
    static protected final String TAG_TIME_STAMP = "time-stamp";
    static protected final String TAG_RUN_COUNT = "run-count";


    /**
     *  Container for driver parameters passed to
     *  <code>VRTestDriverShim.init(XmlDocument)</code>.
     *  <p>
     *  Contains shared authentication parameters.
     *  <p>
     *  @see VRTestDriverShim#init(com.novell.nds.dirxml.driver.XmlDocument)
     *  @see VRTestDriverShim#setDriverParams()
     */
    protected Map driverParams;

    /** This driver's Relative Distinguished Name (RDN). */
    protected String driverRDN;

    /**
     *  Used by derived classes to output trace messages to DSTrace
     *  or a Java trace file.
     *  <p>
     *  To cause trace messages to appear on the DSTrace screen set
     *  <code>the DirXML-DriverTraceLevel</code> attribute on the driver
     *  set object to a value greater than zero. To log trace messages
     *  to a file, set the <code>DirXML-JavaTraceFile</code> attribute
     *  on the driver set object to a file path.
     */
    protected Trace trace;

    /** How many times this driver has been started. */
    private long runCount;

    /** A DirXML-friendly wrapper to the VRTest server's client API. */
    protected VRTestAPIWrapper api;

    /**
     *  Connection state.
     *  <p>
     *  @see VRTestSubscriptionShim#execute(com.novell.nds.dirxml.driver.XmlDocument, com.novell.nds.dirxml.driver.XmlQueryProcessor)
     *  @see VRTestPublicationShim#start(com.novell.nds.dirxml.driver.XmlCommandProcessor)
     *  @see #connect()
     *  @see #disconnect()
     */
    protected boolean connected;

    /**
     *  Format for the date contained by the <code>&lt;time-stamp&gt;</code>
     *  state element.
     *  <p>
     *  @see #TAG_TIME_STAMP
     */
    private DateFormat dateFormat;


    /**
     *  Constructor.
     */
    protected CommonImpl()
    {
        this.driverRDN = null;
        this.trace = null;
        this.driverParams = null;
        this.runCount = -1; //uninitialized
        this.dateFormat = DateFormat.getDateTimeInstance();
        this.connected = false;
    }//CommonImpl()


    /**
     *  Sets this driver's initialization parameters.
     *  <p>
     *  @param params this driver's initialization parameters;
     *      may be <code>null</code>
     */
    protected void setDriverParams(Map params)
    {
        driverParams = params;
    }//setDriverParams(Map):void


    /**
     *  Gets this driver's initialization parameters.
     *  <p>
     *  @return may be <code>null</code>
     */
    Map getDriverParams()
    {
        return this.driverParams;
    }//getDriverParams():Map


    /**
     *  Sets this driver's name.
     *  <p>
     *  @param name the Relative Distinguished Name (RDN) of this
     *  driver instance; may be <code>null</code>
     */
    protected void setDriverRDN(String name)
    {
        driverRDN = name;
    }//setDriverRDN(String):void


    /**
     *  Gets this driver's name.
     *  <p>
     *  @return will not return <code>null</code>
     */
    protected String getDriverRDN()
    {
        return (driverRDN == null) ? TRACE_ID : driverRDN;
    }//getDriverRDN():String


    /**
     *  Creates a trace object.
     *  <p>
     *  @param suffix the suffix of the trace message to follow this driver's
     *  RDN; may be <code>null</code>
     */
    protected void setTrace(String suffix)
    {
        String header;

        header = (suffix == null) ? getDriverRDN() : (getDriverRDN() + "\\" + suffix);
        trace = new Trace(header);
    }//setTrace(String):void


    /**
     *  Sets how many times this driver has been run.
     *  <p>
     *  @param nonNegative non-negative number
     */
    protected void setRunCount(long nonNegative)
    {
        this.runCount = (nonNegative + COUNT_INCREMENT);
    }//setRunCount(long):void


    /**
     *  Gets how many times this driver has been run.
     */
    protected long getRunCount()
    {
        return this.runCount;
    }//getRunCount():long


    /**
     *  Sets a refernce to the VRTest server's client API.
     *  <p>
     *  @param someAPI a refernce to the VRTest server's client API;
     *      must not be <code>null</code>
     */
    protected void setAPI(VRTestAPIWrapper someAPI)
    {
        Lib.CheckParam(someAPI, "someAPI");

        api = someAPI;
    }//setAPI(VRTestAPIWrapper):void


    /**
     *  Gets a reference to the VRTest server's client API.
     *  <p>
     *  @return will not return <code>null</code>
     */
    protected VRTestAPIWrapper getAPI()
    {
        return api;
    }//getAPI():VRTestAPIWrapper


    /**
     *  Appends a populated <code>&lt;source&gt;</code> element to
     *  <code>doc</code>.
     *  <p>
     *  @param doc may be <code>null</code>
     */
    protected void appendSourceInfo(WriteableDocument doc)
    {
        if (doc != null)
        {
            XDSSourceElement source;
            XDSProductElement product;

            source = doc.appendSourceElement();
            product = source.appendProductElement();
            product.setBuild(Build.STAMP);
            product.setInstance(driverRDN);
            product.setVersion(VERSION);
            product.appendText(PRODUCT_NAME);
            source.appendContactElement(COMPANY_NAME);
        }//if
    }//appendSourceInfo(WriteableDocument):void


    /**
     *  Utility method for instantiating an <code>XDSResultDocument</code>.
     *  <p>
     *  The returned result document contains a populated
     *  <code>&lt;source&gt;</code> element.
     *  <p>
     *  @return will not return <code>null</code>
     */
    protected XDSResultDocument newResultDoc()
    {
        XDSResultDocument resultDoc;

        resultDoc = new XDSResultDocument();
        appendSourceInfo(resultDoc);

        return resultDoc;
    }//newResultDoc():XDSResultDocument


    /**
     *  Utility method for instantiating an <code>XDSCommandDocument</code>.
     *  <p>
     *  The returned result document contains a populated source element.
     *  <p>
     *  @return will not return <code>null</code>
     */
    protected XDSCommandDocument newCommandDoc()
    {
        XDSCommandDocument commandDoc;

        commandDoc = new XDSCommandDocument();
        appendSourceInfo(commandDoc);

        return commandDoc;
    }//newCommandDoc():XDSCommandDocument


    /**
     *  A non-interface method that opens a connection to the
     *  VRTest server.
     *  <p>
     *  @throws java.io.IOException if unable to connect
     *  @throws com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     */
    protected void connect()
            throws IOException, VRTestException
    {
        trace.trace("connect", 1);

        if (!connected)
        {
            //both the publisher and subscriber share the same connection
            //  object
            synchronized (this.api.connectMutex)
            {
                if (this.api.badConnection())
                {
                    if (this.api.connected())
                    {
                        this.api.resetConnection();
                    }
                    else
                    {
                        this.api.openConnection();
                    }
                }//if
            }//synchronized
        }//if
    }//connect():void


    /**
     *  A non-interface method that closes the connection to the
     *  VRTest server.
     */
    protected void disconnect()
    {
        trace.trace("disconnect", 1);

        connected = false;

        //both the publisher and subscriber share the same connection
        //  object; check for null since this method could be called
        //  from almost any context
        if(this.api != null)
        {
            synchronized (this.api.connectMutex)
            {
                this.api.closeConnection();
            }
        }//if
    }//disconnect():void


    /**
     *  A non-interface method.
     */
    protected String getConnectHeader()
    {
        if (connected)
        {
            return "Lost connection.";
        }
        else
        {
            return "Unable to connect.";
        }
    }//getConnectHeader():String


    /**
     *  Common <code>XmlQueryProcessor</code> implementation for
     *  the VRTestPublicationShim and VRTestSubscriptionShim.
     *  <p>
     *  @param query may be <code>null</code>
     *  @param result unsued; present to ensure API consistency;
     *      may be <code>null</code>
     */
    protected void queryHandler(XDSQueryElement query,
                                QueryResultDocument result
                                )
            throws VRTestException, IOException
    {

        trace.trace("queryHandler", 1);

        if (query == null)
        {
            //nothing to do
            return;
        }

        VRTestObject baseObject;
        VRTestAttributeFilter readAttributes;

        //get the base object for the query; the base object has different
        //	semantics depending upon the scope of the query;  since we haven't
        //	dealt with scope yet, the base object could the object we're
        //	querying about (entry scope), the immediate parent object of the
        //	object(s) we're querying about (subordinates scope), or the root
        //	of a tree that is the object or contains the object(s) we're
        //	querying about (subtree scope)
        baseObject = getBaseObject(query, result);
        if ((baseObject == null))
        {
            //no matching object; return successfully
            return;
        }

        //get the attributes we need to read values for
        readAttributes = getReadAttributeFilter(query);

        if (query.hasEntryScope())
        {
            if (api.isRootObject(baseObject))
            {
                //there can be no matching object independent of whether
                //	the schema is flat or hierarchcial

                //if the schema is hierarchcial, the base object is the root
                //	object; since the root object doesn't really exist,
                //  we can't query the server about it

                //if the schema is flat, the base object signifies all objects;
                //	since the query has entry scope (a single-object scope value),
                //	the query is nonsensical

                //in any case, we processed the query successfully
                return;
            }//if
            else
            {
                //we're querying about the base object itself
                appendInstanceElement(result,
                                      baseObject,
                                      readAttributes);
            }//else
        }//if
        else//subtree or subordinate scope
        {
            VRTestAttributeFilter searchAttributes;
            List searchClassNames;

            //get the class name(s) of the object(s) we're searching for
            searchClassNames = getSearchClasses(query);
            //get the attribute(s) required for a match
            searchAttributes = getSearchAttributeFilter(query);

            if (api.getSchema().isHierarchical())
            {
                //depending upon the scope of the query, we may need to filter
                //	objects based upon their location relative
                //	to the base object

                if (query.hasSubordinatesScope())
                {
                    //we're searching for the immediate child element(s) of
                    //	the base objects with one of the search class names
                    //	and all of the search attributes
                    subordinateObjectsToXDS(result,
                                            baseObject,
                                            searchClassNames,
                                            searchAttributes,
                                            readAttributes);
                }//if
                else //subtree scope
                {
                    //we're searching for the base object and all
                    //	of it's descendants with one of the search
                    //	class names and all of the search attributes
                    subtreeObjectsToXDS(result,
                                        baseObject,
                                        searchClassNames,
                                        searchAttributes,
                                        readAttributes);
                }//else
            }//if
            else //no filtering required
            {
                //we're searching for any object on the test server
                //	having one of the search class names and all of
                //	the search attributes
                objectsToXDS(result,
                             searchClassNames,
                             searchAttributes,
                             readAttributes);
            }//else
        }//else
    }//queryHandler(XDSQueryElement, QueryResultDocument):void


    /**
     *  Determines what the base object of <code>query</code> is.
     *  The base object is the query root.
     *  </p>
     *	@param query the <code>&lt;query&gt;</code> element;
     *      must not be <code>null</code>
     *  @param result unsued
     *  @return will not return <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     *  @throws java.io.IOException  if a communications error occurs
     */
    private VRTestObject getBaseObject(XDSQueryElement query,
                                       QueryResultDocument result
                                       )
            throws VRTestException, IOException
    {
        //  <!-- the element being handled -->
        //
        //	<query dest-dn="dn">
        //		<association>association</association>
        //	</query>
        //
        //	<!-- association element is optional -->
        //	<!-- dest-dn attriute is optional -->

        //the base object of the query is identified by either the
        //	<association> element or dest-dn attribute value; if both
        //	are present, the association takes precedence; if neither
        //	are present, the base object defaults to the root object
        //	in a hierarchical namespace or all objects in a non-heirarchical
        //	namespace

        //check for an association first
        String association;

        association = query.extractAssociationText();
        if (association != null)
        {
            VRTestObject baseObject;

            //the class-name attribute is reserved and should not be
            //	used by the driver during query processing
            baseObject = api.getObjectByAssociation(association,
                                                    null); //class name

            return baseObject;
        }//if

        //there was no association; check for a dn
        String dn;

        dn = query.getDestDN();
        if (dn != null)
        {
            return api.getObjectByDN(dn);
        }

        //both the association and dn are absent; return some value
        //	indicating that the default base object be used for the
        //	query; in this case, returning the root object has dual
        //	significance--it not only represents the root object in
        //	a heirarchical namespace, but also all objects in a flat
        //	namespace (which can all be thought of as root objects)

        return api.getRootObject();
    }//getBaseObject(XDSQueryElement, QueryResultDocument):VRTestObject


    /**
     *  Returns the attributes that should to be read.
     *  <p>
     *	@param query the <code>&lt;query&gt;</code> element;
     *      must not be <code>null</code>
     *  @return
     *  <dl>
     *      <dt><code>VRTestAttributeFilter.NO_ATTRIBUTES</code>:</dt>
     *          <dd>if <code>query</code> contains a single
     *          <code>&lt;read-attr&gt;</code> element with no
     *          <code>attr-name</code> attribute value</dd>
     *      <dt><code>VRTestAttributeFilter.ALL_ATTRIBUTES</code>:</dt>
     *          <dd>if <code>query</code> contains no
     *          <code>&lt;read-attr&gt;</code> elements</dd>
     *      <dt><code>VRTestAttributeFilter</code>:</dt>
     *          <dd>if <code>query</code> contains one ore more
     *          <code>&lt;read-attr&gt;</code> element with
     *          <code>attr-name</code> attribute values</dd>
     *  </dl>
     *  will not return <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     */
    private VRTestAttributeFilter getReadAttributeFilter(XDSQueryElement query)
            throws VRTestException
    {
        //  <!-- the element being handled -->
        //
        //	<query>
        //		<read-attr attr-name="name"/> <!-- * -->
        //	</query>
        //
        //	<!-- * == zero or more -->
        //	<!-- attr-name == optional -->

        VRTestAttributeFilter readFilter;

        if (query.shouldReadAttributes())
        {
            if (query.containsReadAttrElements())
            {
                ListIterator readAttrs;
                XDSReadAttrElement readAttr;
                String attrName;

                //get the attributes to read
                readFilter = new VRTestAttributeFilter();
                readAttrs = query.extractReadAttrElements().listIterator();

                while (readAttrs.hasNext())
                {
                    readAttr = (XDSReadAttrElement) readAttrs.next();
                    attrName = readAttr.getAttrName();
                    readFilter.addAttribute(attrName);
                }
            }//if
            else
            {
                readFilter = VRTestAttributeFilter.ALL_ATTRIBUTES;
            }
        }//if
        else
        {
            readFilter = VRTestAttributeFilter.NO_ATTRIBUTES;
        }

        return readFilter;
    }//getReadAttributeFilter(XDSQueryElement):VRTestAttributeFilter


    /**
     *  Returns the classes that should be searched.
     *  <p>
     *	@param query the <code>&lt;query&gt;</code> element;
     *      must not be <code>null</code>
     *  @return a list of <code>VRTestClassSchema</code> or
     *      <code>XDSSearchClassElement</code> objects;
     *      will not return <code>null</code>; class names
     *      can be retrieved using the <code>toString()</code>
     *      method
     */
    private List getSearchClasses(XDSQueryElement query)
    {
        if (query.containsSearchClassElements())
        {
            //search class names are enumerated in the query
            return query.extractSearchClassElements();
        }
        else
        {
            //no search classes are specified; return all
            //  classes
            return api.getSchema().getClassSchemas();
        }
    }//getSearchClasses(XDSQueryElement):List


    /**
     *  Returns the attributes/attribute values that should be
     *  searched for.
     *  <p>
     *	@param  query the <code>&lt;query&gt;</code> element;
     *      must not be <code>null</code>
     *  <dl>
     *      <dt><code>VRTestAttributeFilter.NO_ATTRIBUTES</code>:</dt>
     *          <dd>if <code>query</code> contains no
     *          <code>&lt;search-attr&gt;</code> elements</dd>
     *      <dt><code>VRTestAttributeFilter</code>:</dt>
     *          <dd>if <code>query</code> contains one ore more
     *          <code>&lt;search-attr&gt;</code> elements</dd>
     *  </dl>
     *  will not return <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     */
    private VRTestAttributeFilter getSearchAttributeFilter(XDSQueryElement query)
            throws VRTestException
    {
        //  <!-- the element being handled -->
        //
        //	<query>
        //		<search-attr attr-name="name">
        //			<value>value</value> <!-- + -->
        //		</search> <!-- * -->
        //	</query>
        //
        //	<!-- + == one or more -->
        //	<!-- * == zero or more -->
        //	<!-- attr-name == required -->

        VRTestAttributeFilter searchFilter;

        if (query.containsSearchAttrElements())
        {
            //search attributes are enumerated in the query

            List searchAttrs;
            ListIterator l;
            XDSSearchAttrElement searchAttr;
            String attrName;

            searchAttrs = query.extractSearchAttrElements();
            l = searchAttrs.listIterator();
            searchFilter = new VRTestAttributeFilter();

            while (l.hasNext())
            {
                searchAttr = (XDSSearchAttrElement) l.next();
                attrName = searchAttr.getAttrName();

                //serialize the attribute values
                ToVRTestAttributes(searchFilter,
                                   searchAttr,
                                   attrName);
            }
        }//if
        else
        {
            //search attributes are not specified
            searchFilter = VRTestAttributeFilter.NO_ATTRIBUTES;
        }

        return searchFilter;
    }//getSearchAttributeFilter(XDSQueryElement):VRTestAttributeFilter


    /**
     *  Queries for matching objects and appends an
     *  <code>&lt;instance&gt;</code> element for each
     *  matching ojbect to <code>result</code>.
     *  <p>
     *  @param result the document to append <code>&lt;instance&gt;</code>
     *      elements to; must not be <code>null</code>
     *  @param searchClasses the classes to search; mut not be <code>null</code>
     *  @param searchAttributes the attributes/attribute value matching
     *      criteria; must not be <code>null</code>
     *  @param readAttributes the attributes to read from matching objects;
     *      must not be <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     *  @throws java.io.IOException  if a communications error occurs
     */
    private void objectsToXDS(QueryResultDocument result,
                              List searchClasses,
                              VRTestAttributeFilter searchAttributes,
                              VRTestAttributeFilter readAttributes
                              )
            throws VRTestException, IOException
    {
        //  <!-- the element(s) being constructed -->
        //
        //	<instance class-name="class" src-dn	="dn">
        //		<attr attr-name="name">
        //			<value>value</value> <!-- + -->
        //		</attr> <!-- * -->
        //	</instance> <!-- * -->
        //
        //  <!-- * == zero or more -->
        //  <!-- + == one or more -->
        //  <!-- class-name attribute is required -->
        //  <!-- src-dn attribute is optional -->

        List objects;
        VRTestObject object;
        Object searchClass;
        ListIterator sc, o;

        sc = searchClasses.listIterator();
        while (sc.hasNext())
        {
            searchClass = sc.next();
            objects = api.getMatchingObjects(searchClass.toString(),
                                             searchAttributes);
            o = objects.listIterator();
            while (o.hasNext())
            {
                object = (VRTestObject) o.next();
                appendInstanceElement(result,
                                      object,
                                      readAttributes);
            }
        }//while
    }//objectsToXDS(QueryResultDocument, List, VRTestAttributeFilter,
    //             VRTestAttributeFilter):void


    /**
     *  Queries for matching objects and appends an
     *  <code>&lt;instance&gt;</code> element for each
     *  matching ojbect to <code>result</code>.
     *  Matches must be subordinates of <code>baseObject</code>.
     *  Subordinates are immediate children.
     *  <p>
     *  @param result the document to append <code>&lt;instance&gt;</code>
     *      elements to; must not be <code>null</code>
     *  @param baseObject the base object of the query; must not be <code>null</code>
     *  @param searchClasses the classes to search; must not be <code>null</code>
     *  @param searchAttributes the attributes/attribute value matching
     *      criteria; must not be <code>null</code>
     *  @param readAttributes the attributes to read from matching objects;
     *       must not be <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     *  @throws java.io.IOException if a communications error occurs
     */
    private void subordinateObjectsToXDS(QueryResultDocument result,
                                         VRTestObject baseObject,
                                         List searchClasses,
                                         VRTestAttributeFilter searchAttributes,
                                         VRTestAttributeFilter readAttributes
                                         )
            throws VRTestException, IOException
    {
        //  <!-- the element(s) being constructed -->
        //
        //	<instance class-name="class" src-dn="dn">
        //		<attr attr-name="name">
        //			<value>value</value> <!-- + -->
        //		</attr> <!-- * -->
        //	</instance> <!-- * -->
        //
        //  <!-- * == zero or more -->
        //  <!-- + == one or more -->
        //  <!-- class-name attribute is required -->
        //  <!-- src-dn attribute is optional -->

        List objects;
        VRTestObject object;
        Object searchClass;
        ListIterator sc, o;

        sc = searchClasses.listIterator();
        while (sc.hasNext())
        {
            searchClass = sc.next();
            objects = api.getMatchingObjects(searchClass.toString(),
                                             searchAttributes);
            o = objects.listIterator();
            while (o.hasNext())
            {
                object = (VRTestObject) o.next();
                if (baseObject.isSubordinate(object))
                {
                    appendInstanceElement(result,
                                          object,
                                          readAttributes);
                }
            }//while
        }//while
    }//subordinateObjectsToXDS(QueryResultDocument, VRTestObject, List,
    //                        VRTestAttributeFilter, VRTestAttributeFilter):void


    /**
     *  Queries for matching objects and appends an
     *  <code>&lt;instance&gt;</code> element for each
     *  matching ojbect to <code>result</code>.
     *  Matches must be in <code>baseObject</code>'s subtree.
     *  The subtree of an object consists of the object itself,
     *  it's immediate children, and it's descendants.
     *  <p>
     *  @param result the document to append <code>&lt;instance&gt;</code>
     *      elements to; must not be <code>null</code>
     *  @param baseObject the base object of the query; must not be <code>null</code>
     *  @param searchClasses the classes to search; must not be <code>null</code>
     *  @param searchAttributes the attributes/attribute value matching
     *      criteria; must not be <code>null</code>
     *  @param readAttributes the attributes to read from matching objects;
     *       must not be <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     *  @throws java.io.IOException if a communications error occurs
     */
    private void subtreeObjectsToXDS(QueryResultDocument result,
                                     VRTestObject baseObject,
                                     List searchClasses,
                                     VRTestAttributeFilter searchAttributes,
                                     VRTestAttributeFilter readAttributes
                                     )
            throws VRTestException, IOException
    {
        //  <!-- the element(s) being constructed -->
        //
        //	<instance class-name="class" src-dn="dn">
        //		<attr attr-name="name">
        //			<value>val</value> <!-- + -->
        //		</attr> <!-- * -->
        //	</instance> <!-- * -->
        //
        //  <!-- * == zero or more -->
        //  <!-- + == one or more -->
        //  <!-- class-name attribute is required -->
        //  <!-- src-dn attribute is optional -->

        List objects;
        VRTestObject object;
        Object searchClass;
        ListIterator sc, o;

        sc = searchClasses.listIterator();
        while (sc.hasNext())
        {
            searchClass = sc.next();
            objects = api.getMatchingObjects(searchClass.toString(),
                                             searchAttributes);
            o = objects.listIterator();
            while (o.hasNext())
            {
                object = (VRTestObject) o.next();
                if (baseObject.inSubtree(object))
                {
                    appendInstanceElement(result,
                                          object,
                                          readAttributes);
                }
            }//while
        }//while
    }//subtreeObjectsToXDS(QueryResultDocument, VRTestObject, List,
    //                    VRTestAttributeFilter, VRTestAttributeFilter):void


    /**
     *  Appends one <code>&lt;instance&gt;</code> element to <code>result</code>.
     *  <p>
     *  @param result the document to append to; must not be <code>null</code>
     *  @param object the object instance; must not be <code>null</code>
     *  @param readAttributes the attributes to read from <code>object</code>;
     *      must not be <code>null</code>.
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     *  @throws java.io.IOException if a communications error occurs
     */
    private void appendInstanceElement(QueryResultDocument result,
                                       VRTestObject object,
                                       VRTestAttributeFilter readAttributes
                                       )
            throws VRTestException, IOException
    {
        //  <!-- the element being constructed -->
        //
        //	<instance class-name="class" src-dn="dn">
        //		<association>assoc</association>
        //		<attr attr-name="name">
        //			<value>value</value><!--+-->
        //		</attr><!--*-->
        //	</instance>
        //
        //  <!-- * == zero or more -->
        //  <!-- + == one or more -->
        //  <!-- association element is required -->


        XDSInstanceElement instance;

        instance = result.appendInstanceElement();
        instance.setClassName(object.getClassName());
        instance.setSrcDN(object.getDN());
        instance.appendAssociationElement(object.extractAssociationText());

        if (readAttributes != null)
        {
            appendAttrElements(instance, object.getAttributes(readAttributes));
        }
    }//appendInstanceElements(QueryResultDocument, VRTestObject, VRTestAttributeFilter):void


    /**
     *  Appends zero or more <code>&lt;attr&gt;</code> elements to an
     *      <code>instance</code>.
     *  <p>
     *  @param instance the element to append to; must not be <code>null</code>
     *  @param attributes the attributes being appended; must not be <code>null</code>
     */
    private void appendAttrElements(XDSInstanceElement instance,
                                    List attributes
                                    )
    {
        //  <!-- the element being constructed -->
        //
        //	<attr attr-name="name">
        //		<value>value</value> <!-- + -->
        //	</attr> <!-- * -->
        //
        //  <!-- * == zero or more -->
        //  <!-- + == one or more -->

        VRTestAttribute attribute;
        XDSAttrElement attr;
        ListIterator a;

        a = attributes.listIterator();
        while (a.hasNext())
        {
            attribute = (VRTestAttribute) a.next();
            attr = instance.appendAttrElement();
            attr.setAttrName(attribute.getName());
            attr.appendValueElement(attribute.getValue());
        }
    }//appendAttrElements(XDSInstanceElement, List):void


    /**
     *	Translates one or more <code>&lt;value&gt;</code> elements into
     *  an equivalent set of <code>VRTestAttribute</code> objects.
     *  <p>
     *  @param attributes the container to hold the <code>VRTestAttribute</code>
     *      objects; must not be <code>null</code>
     *  @param element the element containing <code>&lt;value&gt;</code> elements
     *      to translate; must not be <code>null</code>
     *  @param attributeName the attribute's name in the VRTest namespace;
     *      must not be <code>null</code>
     *	@throws	com.novell.nds.dirxml.vrtest.VRTestException if an API error occurs
     */
    static protected void ToVRTestAttributes(VRTestAddAttrContainer attributes,
                                             ValueElementsParent element,
                                             String attributeName
                                             )
            throws VRTestException
    {
        //this method serializes a potentially multi-valued XDS attribute
        //	into multiple single-valued VRTest attributes
        //
        //	<add-attr attr-name="Last Name">
        //		<value>Jackson</value>
        //		<value>Smith</value>
        //	<add-attr/>
        //
        //	would serialize to
        //
        // 	{"Last Name", "Jackson", ADD}, {"Last Name", "Smith", ADD}

        ListIterator v;
        XDSValueElement value;

        v = element.extractValueElements().listIterator();
        while (v.hasNext())
        {
            value = (XDSValueElement) v.next();
            attributes.addAttribute(attributeName,
                                    value.extractText());
        }
    }//ToVRTestAttributes(VRTestAddAttrContainer, ValueElementsParent, String):void


    /**
     *   Appends driver state information to <code>state</code>.
     *   <p>
     *   @param state must not be <code>null</code>
     */
    void appendStateInfo(StateParent state)
    {
        //  <!-- the element being constructed -->
        //
        //	<init-params>
        //		<time-stamp>June 12th, 1999, 8:00 PM</time-stamp>
        //		<run-runCount>1</run-runCount>
        //	</init-params>

        Document doc;
        Element parent;
        Element timeStamp, runCount;
        Text text;

        doc = state.domDocument();
        parent = state.domElement();

        //create and append the <time-stamp> element to the
        //	<init-params> element
        timeStamp = doc.createElement(TAG_TIME_STAMP);
        //gets the current date/time in milli-seconds
        text = doc.createTextNode(dateFormat.format(new Date()));
        timeStamp.appendChild(text);
        parent.appendChild(timeStamp);

        //create and append the <run-runCount> element to the
        //	<init-params> element
        runCount = doc.createElement(TAG_RUN_COUNT);
        text = doc.createTextNode(String.valueOf(this.runCount));
        runCount.appendChild(text);
        parent.appendChild(runCount);
    }//appendStateInfo(StateParent):void

}//class CommonImpl