/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.river.test.spec.lookupservice;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.core.entry.Entry;
import net.jini.core.lease.*;
import net.jini.core.lookup.ServiceEvent;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.io.MarshalledInstance;
import net.jini.lookup.entry.ServiceType;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
import org.apache.river.api.io.Valid;
import org.apache.river.qa.harness.QAConfig;
import org.apache.river.qa.harness.QATestEnvironment;
import org.apache.river.qa.harness.TestException;
import org.apache.river.test.spec.lookupservice.ServiceLeaseOverrideProvider;
import org.apache.river.test.spec.lookupservice.attribute.Attr;

/** Provides useful constants and utility methods and classes for all 
 *  classes of the Lookup component of the Jini System. Each field and 
 *  member class, and most methods, contained in this class is public 
 *  and static; this class does not need to be instantiated.
 *
 *  @see QATestEnvironment
 *  @see org.apache.river.test.spec.lookupservice.QATestRegistrar
 */
public class QATestUtils {

    private static Logger logger = 
	Logger.getLogger("org.apache.river.qa.harness.test");

    /* Class constructor */
    public QATestUtils(){}

    /** The number of milliseconds in 1 second */
    public final static long N_MS_PER_SEC   = 1000;
    /** The number of seconds in 1 minute */
    public final static long N_SECS_PER_MIN = 60;
    /** The number of milliseconds in 1 minute */
    public final static long N_MS_PER_MIN   = N_MS_PER_SEC*N_SECS_PER_MIN;
    /** Strings describing event transition states used in debugging */
    public final static String[] trans_str = {"UNKNOWN",
                                              "MATCH_NOMATCH",
                                              "NOMATCH_MATCH",
                                              "UNKNOWN",
                                              "MATCH_MATCH"};

    /** Class which models an event "tuple" (service,attribute,transition). 
     *  
     *  This class is used by some of the tests that wish to verify that the 
     *  events received from the lookup service are the events expected; 
     *  based on the templates used to register event notification requests.
     */
    @AtomicSerial
    public static class SrvcAttrTuple implements Serializable
    {
        static final long serialVersionUID = -8254953323094761933L;

        /** @serial */
        private Object srvcObj;
        /** @serial */
        private Object attrObj;
        /** @serial */
        private int transition;
        /** @serial */
        private ServiceItem[] srvcItems ;
        /** @serial */
        private Entry[][] attrs;
	
	public SrvcAttrTuple(GetArg arg) throws IOException, CloneNotSupportedException, ClassNotFoundException{
	    this(Valid.copy(arg.get("srvcItems", null, ServiceItem[].class)),
		Valid.deepCopy(arg.get("attrs", null, Entry[][].class)),
		arg.get("srvcObj", null),
		arg.get("attrObj", null),
		arg.get("transition", 0)
	    );
	}


        /** Creates a SrvcAttrTuple with the given transition value.
         *  @param srvcItems the array of registered service items
         *  @param attrs array of Entry type elements containing attributes
         *  @param srvcObj the service component of the new tuple
         *  @param attrObj the attribute component of the new tuple
         *  @param transition the transition component of the new tuple
         */
        public SrvcAttrTuple(ServiceItem[] srvcItems,
                             Entry[][]     attrs,
                             Object        srvcObj,
                             Object        attrObj,
                             int           transition)
        {
            this.srvcItems  = srvcItems;
            this.attrs      = attrs;

            this.srvcObj    = srvcObj;
            this.attrObj    = attrObj;
            this.transition = transition;
        }

        /** Creates a SrvcAttrTuple with the "unknown" (0) transition value.
         *  @param srvcItems the array of registered service items
         *  @param attrs array of Entry type elements containing attributes
         *  @param srvcObj the service component of the new tuple
         *  @param attrObj the attribute component of the new tuple
         */
        public SrvcAttrTuple(ServiceItem[] srvcItems,
                             Entry[][]     attrs,
                             Object        srvcObj,
                             Object        attrObj)
        {
            this.srvcItems  = srvcItems;
            this.attrs      = attrs;

            this.srvcObj    = srvcObj;
            this.attrObj    = attrObj;
            this.transition = 0;
        }

        /** Creates a SrvcAttrTuple with the given transition value
         *  and null reference arrays of service items and attributes.
         *  @param srvcObj the service component of the new tuple
         *  @param attrObj the attribute component of the new tuple
         *  @param transition the transition component of the new tuple
         */
        public SrvcAttrTuple(Object srvcObj,
                             Object attrObj,
                             int    transition)
        {
            this.srvcItems  = null;
            this.attrs      = null;

            this.srvcObj    = srvcObj;
            this.attrObj    = attrObj;
            this.transition = transition;
        }

        /** Creates a SrvcAttrTuple with the "unknown" (0) transition value
         *  and null reference arrays of service items and attributes.
         *  @param srvcObj the service component of the new tuple
         *  @param attrObj the attribute component of the new tuple
         */
        public SrvcAttrTuple(Object srvcObj,
                             Object attrObj)
        {
            this.srvcItems  = null;
            this.attrs      = null;

            this.srvcObj    = srvcObj;
            this.attrObj    = attrObj;
            this.transition = 0;
        }
	
        /** Sets this tuple equal to the given tuple
         *  @param tuple the SrvcAttrTuple to set this tuple equal to
         */
        public synchronized void setEqualTo(SrvcAttrTuple tuple) {
            this.srvcItems  = tuple.srvcItems;
            this.attrs      = tuple.attrs;
            this.srvcObj    = tuple.srvcObj;
            this.attrObj    = tuple.attrObj;
            this.transition = tuple.transition;
        }

        /** Returns the array of service items associated with this tuple */
        public synchronized ServiceItem[] getSrvcItems() {
            return srvcItems.clone();
        }

        /** Sets the reference to the array of service items associated with
         *  this tuple equal to the given reference
         *  @param srvcItems the new array of service items
         */
        public synchronized  void setSrvcItems(ServiceItem[] srvcItems) {
            this.srvcItems = srvcItems == null ? null : srvcItems.clone();
        }

        /** Returns the array of attributes associated with this tuple */
        public synchronized Entry[][] getAttrs() {
            return attrs;
        }

        /** Sets the reference to the array of attributes associated with
         *  this tuple equal to the given reference
         *  @param attrs the new array-of-arrays of attributes
         */
        public synchronized void setAttrs(Entry[][] attrs) {
            this.attrs = attrs;
        }

        /** Returns the service item associated with this tuple */
        public synchronized Object getSrvcObj() {
            return srvcObj;
        }

        /** Sets the reference to the service item associated with
         *  this tuple equal to the given reference
         *  @param srvcObj the new service item
         */
        public synchronized void setSrvcObj(Object srvcObj) {
            this.srvcObj = srvcObj;
        }

        /** Returns the attribute associated with this tuple */
        public synchronized Object getAttrObj() {
            return attrObj;
        }

        /** Sets the reference to the attribute associated with
         *  this tuple equal to the given reference
         *  @param attrObj the new attribute
         */
        public synchronized void setAttrObj(Object attrObj) {
            this.attrObj = attrObj;
        }

        /** Returns the transition associated with this tuple */
        public synchronized int getTransition() {
            return transition;
        }

        /** Sets the transition associated with this tuple equal to the 
         *  given transition
         *  @param transition the new transition value
         */
        public synchronized void setTransition(int transition) {
            this.transition = transition;
        }

        /** Determines if the given object is equal to this tuple; where
         *  two tuples are equal if their service items are equal, their
         *  attributes are equal and their transitions are equal.
         *  @param obj the tuple to compare to this tuple
         *  @return boolean
         */
        public boolean equals(Object obj) {
            int trans = ((SrvcAttrTuple)obj).getTransition();
            return ( (objEquals(obj)) && (getTransition() == trans) );
        }

        /** Determines if the service item and and the attribute of the 
         *  given object are equal to the corresponding fields of this 
         *  tuple; that is, this method ignores the transition values
         *  of the tuples.
         *  @param obj the tuple to compare to this tuple
         *  @return boolean
         */
        public boolean objEquals(Object obj) {
            if (this == obj) {
                return true;
            } else if ( (obj.getClass()).equals(SrvcAttrTuple.class) ) {
                if ((getSrvcItems() == null)||(getAttrs() == null)) {
                    return false;
		}
                int n=0;
                int k=0;
                Object srvc = ((SrvcAttrTuple)obj).getSrvcObj();
                Object attr = ((SrvcAttrTuple)obj).getAttrObj();
                try {
                    n = getSrvcIndx(getSrvcObj(),getSrvcItems());
                    k = getAttrIndx(getAttrObj(),getAttrs());
                    if (    ((getSrvcItems()[n].service).equals(srvc))
                         && ((getAttrs()[k][0]).equals(attr)) ) {
                        return true;
		    } else {
                        return false;
		    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    return false;
		}
            } else {
                return false;
            }
        }
    }

    /** Steps through the given array of service items searching for the
     *  element that equals the given service item Object; returning the
     *  element index if a match is found and -1 otherwise
     *  @param obj service item object to search for
     *  @param srvcItems array of service items to search
     *  @return int (array index or -1 if no match found)
     */
    public static int getSrvcIndx(Object        obj,
                                  ServiceItem[] srvcItems) {
        try {
            int n;
	    /* get the srvc instance the input Object corresponds to */
            for(n=0;n<srvcItems.length;n++) {
                if ( (srvcItems[n].service).equals(obj) ) {
                    break;
                }
            }
            if (n < srvcItems.length) {
                return n;
	    } else {
                return -1;
	    }
        } catch (ArrayIndexOutOfBoundsException e) {
           return -1;
        } catch (NullPointerException e) {
            return -1;
     	}
    }

    /** Steps through the given array of attribute objects searching for
     *  the element that equals the given attribute Object; returning the
     *  element index if a match is found and -1 otherwise
     *  @param obj attribute object to search for
     *  @return int (array index or -1 if no match found)
     */
    public static int getAttrIndx(Object     obj,
                                  Entry[][]  attrs) {
        try {
            int n;
	    /* get the attribute instance the input Object corresponds to */
            for(n=0;n<attrs.length;n++) {
                if ( (attrs[n][0]).equals(obj) ) {
                    break;
		}
   	    }
            if (n < attrs.length) {
                return n;
	    } else {
                return -1;
	    }
        } catch (ArrayIndexOutOfBoundsException e) {
           return -1;
        } catch (NullPointerException e) {
            return -1;
     	}
    }

    /** Returns the current time in milliseconds 
     *  @return long
     */
    public static long getCurTime() {
	return System.currentTimeMillis();
    }

    /** Creates an instance of the given (loaded) class object. This method 
     *  assumes that the loaded class has a constructor with a single argument
     *  of type int That single int field of the new object will be
     *  initialized to the given value.
     *  @param classObj class type to instantiate
     *  @param instanceIndx value to which to initialize the class field
     *  @exception TestException usually indicates a failure
     *  @return java.lang.Object 
     */
    public static Object createInstance(Class classObj,
                                        int   instanceIndx)
                                                         throws Exception
    {
        Class[] parameterTypes = { int.class };
	    Constructor con = classObj.getConstructor(parameterTypes);
            return con.newInstance(new Object[]{new Integer(instanceIndx)});
    } 

    /** Creates an instance of the given (loaded) class object. This method 
     *  assumes that the loaded class has a constructor with a set of 
     *  arguments corresponding to the contents of given array of Objects. 
     *  The fields of the new object will be initialized to the values 
     *  contained in the given array.
     *  @param classObj class type to instantiate
     *  @param constructorArgs array of Objects for field initialization
     *  @return java.lang.Object 
     */
    public static Object createInstance(Class    classObj,
                                        Object[] constructorArgs)
                                                         throws Exception
    {
        Object o = null;
        if (constructorArgs == null) {
	    o = classObj.getDeclaredConstructor().newInstance();
        } else {
            Class[] constructorArgTypes = new Class[constructorArgs.length];
	    for (int i=0;i<constructorArgs.length;i++) {
		constructorArgTypes[i] = constructorArgs[i].getClass();
	    }
	    Constructor con = classObj.getConstructor(constructorArgTypes);
	    o = con.newInstance(constructorArgs);
	}
        return o;
    } 

    /** Computes a wait duration based on the given "start" time, a desired 
     *  increment (assuming instanteous time -- the given deltaT), and an 
     *  adjustment to correct for compute time (the current time). A negative 
     *  duration will be treated as an invalid -- or "unresolved" -- test 
     *  case. If the duration is non-negative, then this method will sleep 
     *  for that many milliseconds, and then return.
     *  @param baseT0 the start time
     *  @param deltaT time increment
     *  @exception TestException usually indicates a failure
     */
    public static void computeDurAndWait(long baseT0, long deltaT, Object lock) throws Exception
    {
        long finishTime = baseT0 + deltaT;
        long dur = finishTime - System.currentTimeMillis();
	if(dur > 0) {
            do {
                synchronized (lock) { lock.wait(dur); }
                dur = finishTime - System.currentTimeMillis();
            } while (dur > 0); // In case of spurious wakeup or notify.
	} else {
            throw new TestException("Environment problem; this configuration"
                                    + " does not allow for the timing"
                                    + " assumptions made by the test");
                                    
	}
    }

    /** Sleeps for the given amount of milliseconds
     *  @param deltaT time in milliseconds to sleep
     */
    public static void waitDeltaT(long deltaT, Object lock) throws Exception {
	long finish = System.currentTimeMillis() + deltaT;
	synchronized (lock){
	    do {
		    lock.wait(deltaT);
	    } while (finish > System.currentTimeMillis());
	}
//	Thread.sleep(deltaT);
    }

    /** Returns true if the input argument is even; false otherwise
     *  @param i integer to test for even or odd
     */
    public static boolean isEven(int i) {
        return ((i&1) == 1 ? false:true);
    }

    /** Determines if all of the expected (and no un-expected) events 
     *  have arrived. The test in this method depends on the semantics of 
     *  event-notification. That is, it will use the fact that if the events
     *  were generated for each service class in sequence (which they were),
     *  although the events may arrive out of order, they can be arranged
     *  back into the same sequence using RemoteEventComparator. 
     *  This means we can expect, when examining the event corresponding 
     *  to index i, that the serviceID returned in the ServiceEvent should 
     *  correspond to the i_th service registered. If it does not,
     *  remote events are missing and failure is declared.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  NotifyOnAttrAdd
     *                  NotifyOnAttrMod
     *                  NotifyOnAttrDel
     *                  NotifyOnSrvcLeaseExpiration
     *
     *  @param events vector containing the events to test
     *  @param nExpectedEvnts number of events expected
     *  @param expectedTransition the expected event transition
     *  @param serviceRegs array of ServiceRegistrations of each service
     *  @exception TestException usually indicates a failure
     */
    public static void verifyEventVector(Collection<ServiceEvent> events,
                                         int nExpectedEvnts,
                                         int expectedTransition,
                                         ServiceRegistration[] serviceRegs)
                                                         throws Exception
    {
        ServiceEvent evnt = null;
        if (events.size() != nExpectedEvnts) {
            throw new TestException("# of Events Received ("+
                                             events.size()+
                                             ") != # of Events Expected ("+
                                             nExpectedEvnts+")");
	} else {
            ServiceID evntSrvcID;
            ServiceID expdSrvcID;
            ServiceID handbackSrvcID;
            Iterator<ServiceEvent> it = events.iterator();
	    for (int i = 0; it.hasNext(); i++) {
                evnt = it.next();
                if (evnt == null) {
                    throw new TestException
                             ("null Event returned from Vector at element "+i);
	   	} else {
                    if (evnt.getTransition() != expectedTransition) {
			dumpEventIDs(events, serviceRegs);
                        throw new TestException("Unexpected Transition returned ("+
						evnt.getTransition()+")");
		    } else {
                        evntSrvcID = evnt.getServiceID();
                        expdSrvcID = serviceRegs[i].getServiceID();
                        if ( !(evntSrvcID.equals(expdSrvcID)) ) {
                            throw new TestException("Service ID Received ("+
						    evntSrvcID+
						    ") != Service ID Expected ("+
						    expdSrvcID+")");
			} else {
			    handbackSrvcID = 
				(ServiceID)(evnt.getRegistrationInstance().get(false));

			    if ( !(handbackSrvcID.equals(expdSrvcID)) ) {
				throw new TestException
				    ("Handback Service ID ("+
				     handbackSrvcID+
				     ") != Service ID Expected ("+
				     expdSrvcID+")");
			    }
			}
		    }
		}
	    }
	}
	verifyEventItems(events);
    }

    public static void dumpEventIDs(Collection<ServiceEvent> events,
                                    ServiceRegistration[] serviceRegs)
    {
        ServiceEvent evnt = null;
	ServiceID evntSrvcID;
	ServiceID expdSrvcID;
	ServiceID handbackSrvcID;
        Iterator<ServiceEvent> it = events.iterator();
	for ( int i = 0; it.hasNext(); i++) {
	    evnt = it.next();
	    evntSrvcID = evnt.getServiceID();
	    expdSrvcID = serviceRegs[i].getServiceID();
	    System.out.println("Expected ID = " + expdSrvcID + ", received ID = " + evntSrvcID);
	}
    }

    /** Verifies that the ServiceItem in each event is as expected.
     *  If there is no later event with the same service ID, then
     *  compare the item in the event with the current item in the
     *  lookup service.  If the item in the event is null, then the
     *  item must no longer exist in the lookup service, otherwise
     *  the item must exist in the lookup service and be equal to
     *  the one in the event.  We skip an event if there is a later
     *  event with the same service ID, as that means the state of
     *  the item recorded in the event was subsequently changed again.
     *
     *  @param events List containing the events to test
     *  @exception TestException usually indicates a failure
     */
    public static void verifyEventItems(Collection<ServiceEvent> eventsCollection)
	throws Exception
    {
	ServiceTemplate tmpl;
        List<ServiceEvent> events = new ArrayList<ServiceEvent>(eventsCollection);
    outer:
	for (int i = 0; i < events.size(); i++) {
	    ServiceEvent evnt = (ServiceEvent)events.get(i);
	    for (int j = i + 1; j < events.size(); j++) {
		ServiceEvent oevnt = (ServiceEvent)events.get(j);
		if (evnt.getServiceID().equals(oevnt.getServiceID()))
		    continue outer;
	    }
	    ServiceItem item = evnt.getServiceItem();
	    tmpl = new ServiceTemplate(evnt.getServiceID(), null, null);
	    ServiceRegistrar proxy = (ServiceRegistrar)evnt.getSource();
	    proxy = (ServiceRegistrar) 
		    QAConfig.getConfig().prepare("test.reggiePreparer",
						      proxy);
	    ServiceMatches matches;
	    matches = proxy.lookup(tmpl, 1);
	    if (item == null) {
		if (matches.items.length != 0)
		    throw new TestException(
			      "verifyEventItems: event item is null, lookup returns non-null");
	    } else {
		if (matches.items.length == 0)
		    throw new TestException(
			      "verifyEventItems: event item is non-null, lookup returns null");
		ServiceItem litem = matches.items[0];
		if (!item.service.equals(litem.service))
		    throw new TestException(
			      "verifyEventItems: event item service does not equal lookup value");
		if (!attrsEqual(item.attributeSets, litem.attributeSets))
		     throw new TestException(
			      "verifyEventItems: event item attrs do not equal lookup value");
	    }
	}
    }

    /** Tests if two arrays of attribute sets are the same length and
     *  have equal elements, ignoring element ordering differences.
     *
     *  @param attrs1 an array of attribute sets
     *  @param attrs2 an array of attribute sets
     */
    public static boolean attrsEqual(Entry[] attrs1, Entry[] attrs2) {
	if (attrs1.length != attrs2.length)
	    return false;
    outer:
	for (int i = 0; i < attrs1.length; i++) {
	    for (int j = 0; j < attrs2.length; j++) {
		if (attrs1[i].equals(attrs2[j]))
		    continue outer;
	    }
	    return false;
	}
	return true;
    }

    /** Performs a simple lookup and a match lookup using only the serviceID
     *  @param srvcItems array of registered service items 
     *  @param templates array of used input to lookup()
     *  @param proxy proxy of Registrar through which lookup is performed
     *  @exception TestException usually indicates a failure
     */
    public static void doLookup(ServiceItem[] srvcItems,
                                ServiceTemplate[] templates,
				ServiceRegistrar proxy)  throws Exception
    {
        Object serviceObj = null;
        ServiceMatches matches = null;
        for (int i=0; i<templates.length; i++) {
	    serviceObj = proxy.lookup(templates[i]);
            if (!srvcItems[i].service.equals(serviceObj)) {
                throw new TestException("srvcItems["+i+
					"] != serviceObj returned by lookup()");
	    }

	    matches = proxy.lookup(templates[i],Integer.MAX_VALUE);
            if (matches.totalMatches != 1) {
                throw new TestException
		    ("totalMatches != EXPECTED_N_MATCHES");
	    } else {
                if (!srvcItems[i].service.equals(matches.items[0].service)) {
                    throw new TestException("srvcItems["+i+
					    "] != items[0].service returned by lookup()");
		}
	    }
	}
    }

    /** Steps through the given array of leases, comparing each element's
     *  lease expiration against the given minimum lease expiration; a
     *  failure exception will be thrown if any of the lease expirations
     *  is less than the minimum expiration 
     *  @param leases array of lease objects corresponding to each service 
     *  @param minExpiration minimum lease expiration time
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lease.Lease
     */
    public static void verifyLeases(Lease[] leases,
                                    long minExpiration)  throws Exception
    {
	for(int i=0; i<leases.length; i++) {
	    if(leases[i].getExpiration() < minExpiration) {
                throw new TestException("verifyLeases: expiration of lease ["
					+i+"] ("+leases[i].getExpiration()+
					" ms) < expected min expiration ("
					+minExpiration+" ms)");
	    } else {
		minExpiration = leases[i].getExpiration();
	    }
	}
    }

    /** Steps through the given array of leases, renewing each element's
     *  lease with the given lease duration value
     *  @param leases array of lease objects corresponding to each service 
     *  @param leaseDuration lease duration to request in renewal
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lease.Lease
     */
    public static void doRenewLease(Lease[] leases,
                                    long leaseDuration) throws Exception
    {
        for(int i=0; i< leases.length; i++ ) {
	    leases[i].renew(leaseDuration);
	}
    }

    /** Steps through the given array of service items, searching for the
     *  element that equals the given Object; returning the index of the
     *  matching element or -1 if no match is found
     *  @param srvcObj service item to search for 
     *  @param srvcItems array of service items to search through 
     *  @see net.jini.core.lookup.ServiceItem
     */
    public static int srvcIndxFrmSimpleLookup( Object        srvcObj,
                                               ServiceItem[] srvcItems ) {
        int indx = -1;
        for (int j=0;((indx==-1)&&(j<srvcItems.length)); j++) {
            if ((srvcItems[j].service.equals(srvcObj))) {
               indx  = j;
	    }
	}
        return indx;
    }

    /** Inserts the given value into the given histogram.
     *  
     *  Note that the method that calls this method must create the 
     *  histogram. If the histogram is null, then this method will throw
     *  a NullPointerException
     *  @param value value to place in the histogram
     *  @param histogram HashMap representing the histogram 
     */
    public static void srvcHistogram( int value,
                                      HashMap histogram ) {
        Integer histKey = new Integer(value);
        Integer histVal = (Integer)histogram.get(histKey);
        int newHistVal = ( (histVal==null) ? 1 : 1+histVal.intValue() );
        Integer newValue = new Integer(newHistVal);
        histogram.put(histKey,newValue);
    }

    /** Returns the value of the largest key index in the given histogram 
     *  @param histogram HashMap representing the histogram 
     *  @return int
     */
    public static int getMaxKeyHistogram( HashMap histogram ) {
        int maxKey = 0;
        int counter = 0;
        if ( histogram != null ) {
            int hSize = histogram.size();
	    for ( int i=0; counter < hSize; i++ ) {
	        if (histogram.get(new Integer(i)) != null) {
                    counter++;
                    if (counter >= hSize) {
                        maxKey = i;
		    }
		}
	    }
	}
        return maxKey;
    }

    /** Prints the values of the given histogram to stdout (for debugging) 
     *  @param histogram HashMap representing the histogram 
     */
    public static void displayHistogram( HashMap histogram ) {
	if ( histogram != null ) {
	    for ( int i=0; i <= getMaxKeyHistogram(histogram); i++ ) {
                System.out.println("histogram("+i+") = "
                                          +histogram.get(new Integer(i)));
	    }
	}
    }

    /** Disregarding order, verify that the given set of class types is
     *  equal to the given set of type descriptors. For the case where
     *  the classType is assignable to net.jini.lookup.entry.ServiceType,
     *  it is only required that the class associated with the typeDescriptor
     *  also is assignable to net.jini.lookup.entry. This is to handle
     *  the case where the actual class is 
     *  org.apache.river.lookup.entry.BasicServiceType,
     *  which may be preferred. This would case an 'equals' comparison to fail.
     *  In addition, a special case involves a check for the reggie proxy class.
     *  Since the proxy used is configuration dependent, if a typeDescriptor
     *  has the value "org.apache.river.reggie.proxy.RegistrarProxy", then equality
     *  will be satisfied if a classType can be found whose getName method
     *  returns either "org.apache.river.reggie.proxy.RegistrarProxy" or
     *  "org.apache.river.reggie.proxy.ConstrainableRegistrarProxy"
     *
     *  This method is currently employed by the following test classes:
     *
     *                  GetServiceTypesEmpty
     *                  GetServiceTypesClass
     *                  GetServiceTypesAttr
     *
     *  @param classTypes array of class types
     *  @param typeDescriptors array of class name descriptors 
     *  @exception TestException usually indicates a failure
     */
    public static boolean classTypesEqualTypeDescriptors
                                            ( Class[] classTypes,
                                              String[] typeDescriptors )
    {
        if (typeDescriptors == null) {
            return (classTypes == null);
	}
	if (classTypes == null) {
	    logger.log(Level.INFO, "classTypes is null");
	}
        if (classTypes.length != typeDescriptors.length) {
	    logger.log(Level.INFO, "classTypes != typeDescriptors");
	    for (int i = 0; i < classTypes.length; i++) {
		logger.log(Level.INFO, 
			   "classTypes[ " + i + "] = " + classTypes[i]);
	    }
	    for (int i = 0; i < typeDescriptors.length; i++) {
		logger.log(Level.INFO, 
			   "typeDescriptors[ " + i + "] = " 
			   + typeDescriptors[i]);
	    }
            return false;
	}
	String reggieProxy = "org.apache.river.reggie.proxy.RegistrarProxy";
	String cReggieProxy = "org.apache.river.reggie.proxy.ConstrainableRegistrarProxy";
        iLoop:
            for (int i=0; i<classTypes.length; i++) {
                for (int j=0;(j<typeDescriptors.length); j++) {
		    try {
			Class c = Class.forName(typeDescriptors[j]);
                        if (classTypes[i].equals(c)) {
                            continue iLoop;
	                }
			Class serviceTypeClass = ServiceType.class;
                        if (serviceTypeClass.isAssignableFrom(classTypes[i])) {
			    if (serviceTypeClass.isAssignableFrom(c)) {
			        continue iLoop;
			    }
			}
			if (typeDescriptors[j].equals(reggieProxy)) {
			    if (classTypes[i].getName().equals(reggieProxy)
			     || classTypes[i].getName().equals(cReggieProxy)) {
				continue iLoop;
			    }
			}
		    } catch (ClassNotFoundException e) {
			if (classTypes[i].getName().equals(typeDescriptors[j])) {
			    continue iLoop;
			}
			if (typeDescriptors[j].equals(reggieProxy)) {
			    if (classTypes[i].getName().equals(reggieProxy)
			     || classTypes[i].getName().equals(cReggieProxy)) {
				continue iLoop;
			    }
			}
			logger.log(Level.INFO, 
				   "Could not find service type class named " 
				   + typeDescriptors[j]);
		    }
	        }
	        logger.log(Level.INFO,
			   "Could not find match for classTypes = " 
			   + classTypes[i]);
                return false;
            }
        return true; /* success */
    }

    /** Returns the number of classes in the chain of super classes of the
     *  given Object; up to, but not including, the Object class itself
     *  @param obj the Object to analyze for super classes
     *  @exception TestException usually indicates a failure
     *  @return int
     */
    public static int getNSuperClasses(Object obj) {
        int n = 0;
        try {
            Class superClass = obj.getClass().getSuperclass();
            while(true) {
                if( !(superClass.getSuperclass() == null) ) {
                    n++;
                    superClass = superClass.getSuperclass();
   	        } else {
                    return n;
	        }
	    }
        } catch (NullPointerException e) {
            return 0;
        }
    }

    /** Returns the nth super class in the chain of super classes of the
     *  given Object; returns null if the given value of n is invalid.
     *  @param obj the Object to analyze for super classes
     *  @param n super class "index" to return
     *  @exception TestException usually indicates a failure
     *  @return int
     */
    public static Object getNthSuperClass(Object obj, int n) {
        int totalN;
        Class superClass = null;
        try {
            totalN = getNSuperClasses(obj);
            if ((totalN > 0)&&(n>0)&&(n<=totalN)) {
                superClass = obj.getClass().getSuperclass();
                for(int i=1;i<n;i++) {
                    superClass = superClass.getSuperclass();
                }
                return (Object)superClass;
	    } else {
                return null;
	    }
        } catch (NullPointerException e) {
            return null;
        }
    }

    /** Returns true if the given attribute objects match, else returns false
     *  Note that the order of the input Objects is NOT important
     *  @param attrObj0 first attribute object to compare
     *  @param attrObj1 second attribute object to compare
     *  @exception TestException usually indicates a failure
     *  @return boolean
     */
    public static boolean attrsMatch(Object attrObj0, Object attrObj1) {
        Object sObj = null;
        Object cObj  = null;
        if ( attrObj0.equals(attrObj1) ) {
            return true;
	} else if ( (attrObj0.getClass()).equals((attrObj1).getClass()) ) {
            return false;
	} else {
	    if ((attrObj0.getClass()).isAssignableFrom(attrObj1.getClass())) {
                /* cast attrObj1 to super class attrObj0 */
                sObj = attrObj0;
                cObj = attrObj1;
	    } else if
               ((attrObj1.getClass()).isAssignableFrom(attrObj0.getClass())) {
                /* cast attrObj0 to super class attrObj1 */
                sObj = attrObj1;
                cObj = attrObj0;
	    } else {
                return false;
	    }
            return ((Attr)sObj).matches(cObj);
	}
    }

    /** Returns true if the given attribute objects match, else returns false
     *  Note that the order of the input Objects IS important
     *  @param attrObj0 first attribute object to compare
     *  @param attrObj1 second attribute object to compare
     *  @param orderImportant true or false
     *  @exception TestException usually indicates a failure
     *  @return boolean
     */
    public static boolean attrsMatch(Object attrObj0,
                                     Object attrObj1,
                                     boolean orderImportant)
    {
        if (!orderImportant) {
            return attrsMatch(attrObj0,attrObj1);
	} else {
            if ( attrObj0.equals(attrObj1) ) {
                return true;
	    } else if ( (attrObj0.getClass()).equals((attrObj1).getClass()) ) {
                return true;
   	    } else if((attrObj0.getClass()).isAssignableFrom
                                                        (attrObj1.getClass())){
                return ((Attr)attrObj0).matches(attrObj1);
	    } else {
                return false;
	    }
	}
    }

    /** Initializes the given SrvcAttrTuple state array element at the two
     *  given index values to the corresponding values contained in the 
     *  arguments named srvcItems and attrs
     *
     *  This method is currently employed by the following test classes:
     *
     *                  NotifyOnComboAttrAddNull
     *                  NotifyOnComboAttrAddNonNull
     *                  NotifyOnComboAttrModNull
     *                  NotifyOnComboAttrModNonNull
     *                  NotifyOnComboAttrDelNull
     *                  NotifyOnComboAttrDelNonNull
     *                  NotifyOnComboSrvcLeaseExp
     *
     *  @param srvcIndx index of the service item
     *  @param attrIndx index of the attribute
     *  @param superChainLen length of the service super class "chain"
     *  @param srvcItems array of registered services
     *  @param srvcsForEquals array of instantiated services for comparison
     *  @param attrs array-of-arrays of attribute objects for matching
     *  @param state the state array of tuples
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lookup.ServiceItem
     */
    public static void initStateTupleArray
                                        (int srvcIndx,
                                         int attrIndx,
                                         int superChainLen,
                                         ServiceItem[] srvcItems,
                                         ServiceItem[] srvcsForEquals,
                                         Entry[][] attrs,
                                         SrvcAttrTuple[][][] state)
	throws Exception
    {
        state[srvcIndx][attrIndx][0]
                               = new SrvcAttrTuple(srvcsForEquals,
                                                   attrs,
                                                   srvcItems[srvcIndx].service,
                                                   attrs[attrIndx][0]);

        Class superClass = null;
        superClass = (srvcItems[srvcIndx].service).getClass().getSuperclass();
        for(int i=1;i<superChainLen;i++) {
            if( !(superClass.getSuperclass() == null) ) {
                state[srvcIndx][attrIndx][i] =
                         new SrvcAttrTuple(srvcsForEquals,attrs,
                                           createInstance(superClass,srvcIndx),
                                           attrs[attrIndx][0]);
                superClass = superClass.getSuperclass();
	    } else {
                state[srvcIndx][attrIndx][i] =
                             new SrvcAttrTuple(srvcsForEquals,attrs,
                                               createInstance(superClass,null),
                                               attrs[attrIndx][0]);
                return;
	    }
	}
    }

    /** Initializes all of the elements in the given SrvcAttrTuple array to 
     *  null for the attribute component of the Tuple; and to the 
     *  corresponding service item for the service component
     *
     *  This method is currently employed by the following test classes:
     *
     *                  NotifyOnComboAttrAddNull
     *                  NotifyOnComboAttrAddNonNull
     *                  NotifyOnComboSrvcReg
     *
     *  @param srvcItems array of registered services
     *  @param srvcsForEquals array of instantiated services for comparison
     *  @param attrs array-of-arrays of attribute objects for matching
     *  @param state the state array of tuples
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lookup.ServiceItem
     *  @see net.jini.core.entry.Entry
     */
    public static void initStateTupleArray
                                        (ServiceItem[] srvcItems,
                                         ServiceItem[] srvcsForEquals,
                                         Entry[][] attrs,
                                         SrvcAttrTuple[][][] state)
	throws Exception
    {
        for(int i=0;i<state.length;i++) {
            jLoop:
            for(int j=0;j<state[i].length;j++) {
                state[i][j][0] = new SrvcAttrTuple(srvcsForEquals,attrs,
                                                   srvcItems[i].service,null);
                Class superClass = null;
                superClass = (srvcItems[i].service).getClass().getSuperclass();
                for(int k=1;k<state[i][j].length;k++) {
                    if( !(superClass.getSuperclass() == null) ) {
                        state[i][j][k] = new SrvcAttrTuple
                                                 (srvcsForEquals,attrs,
                                                  createInstance(superClass,i),
                                                  null);
                            superClass = superClass.getSuperclass();
		    } else {
                        state[i][j][k] = new SrvcAttrTuple
                                              (srvcsForEquals,attrs,
                                               createInstance(superClass,null),
                                               null);
                        continue jLoop;
		    }
		}
	    }
	}
    }

    /** Retrieves the chain of super classes of the given object; up to, but
     *  not including, the Object class itself. Returns a vector containing 
     *  initialized instances of each class in the chain (including
     *  the instance of the given object itself).
     *
     *  Note: this method is specific to the set of test service classes
     *        that are registered during any typical lookup test. Thus,
     *        this method should be invoked only on objects that belong to
     *        a chain of classes contained in the set of test services; 
     *        that is, the input object, as well as each of the super 
     *        classes in the chain have a constructor that requires a 
     *        single argument of type int.
     *
     *  @param obj object that is analyzed for its super classes
     *  @param srvcItems array of registered services
     *  @exception TestException usually indicates a failure
     *  @return java.util.Vector 
     *  @see net.jini.core.lookup.ServiceItem
     */
    public static Vector getSrvcSupersVector(Object        obj,
                                             ServiceItem[] srvcItems)
	throws Exception
    {
        int n;
        Class superClass = null;
        Vector objSupers = new Vector();
        objSupers.addElement(obj);
        superClass = obj.getClass().getSuperclass();
        while(true) {
            if( !(superClass.getSuperclass() == null) ) {
                for(n=0;n<srvcItems.length;n++) {
                    if (superClass.isInstance(srvcItems[n].service) ) {
                        break;
		    }
		}
                objSupers.addElement(createInstance(superClass,n));
                superClass = superClass.getSuperclass();
	    } else {
                return objSupers;
	    }
	}
    }

    /** Retrieves the chain of super classes of the input object; up to, but
     *  not including, the Object class itself. Returns a vector containing 
     *  non-initialized instances of each class in the chain (including
     *  the instance of the input object itself).
     *
     *  Note: this method should be invoked only on objects that have 
     *        super classes containing no-arg constructors.
     *
     *  @param obj object that is analyzed for its super classes
     *  @exception TestException usually indicates a failure
     *  @return java.util.Vector 
     */
    public static Vector getNoArgSupersVector(Object obj) throws Exception {
        Class superClass = null;
        Vector objSupers = new Vector();
        objSupers.addElement(obj);
        superClass = obj.getClass().getSuperclass();
        while(true) {
            if( !(superClass.getSuperclass() == null) ) {
		objSupers.addElement( superClass.getDeclaredConstructor().newInstance() );
                superClass = superClass.getSuperclass();
	    } else {
                return objSupers;
	    }
	}
    }

    /** From the assumption that the addition, modification or deletion 
     *  of an attribute of one of the registered service classes will 
     *  result in one or more events being generated by the lookup service, 
     *  this method "predicts" all of the service/attribute "pairs" 
     *  corresponding to the set of events that will be generated from 
     *  the "base" pair represented by the input srvcObj/attrObj pair.
     *  The number and "cause" of these events is dependent on the 
     *  contents of the template or templates used to register the event 
     *  notification requests with lookup. This method assumes that an 
     *  event was registered using a template containing the class of 
     *  both the input srvcObj argument and the attrObj argument.  
     *
     *  That is, given the template/event-registration-request assumption
     *  just described, if attrObj is added to, modified on, or deleted from 
     *  srvcObj, then an event will be generated for each service/attribute
     *  pair in the lookup service which also matches one of the templates.
     *
     *  This method will populate the given tuples vector with the set of
     *  service/attribute/transition "tuples" corresponding to the events
     *  expected to be generated when the srvcObj's state is modified 
     *  The tuples vector will contain all of the tuples that have accumulated
     *  over the life of the current test run. Thus, because that vector
     *  contains all of the expected pairs generated by previous calls to
     *  this method, the tuples vector must be created and maintained --
     *  outside of this method -- by the invoking class.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  NotifyOnComboAttrAddNull
     *                  NotifyOnComboAttrAddNonNull
     *                  NotifyOnComboAttrModNull
     *                  NotifyOnComboAttrModNonNull
     *                  NotifyOnComboAttrDelNull
     *                  NotifyOnComboAttrDelNonNull
     *                  NotifyOnComboSrvcReg
     *                  NotifyOnComboSrvcLeaseExp
     *
     *  @param srvcObj object whose state has changed
     *  @param srvcItems array of registered services
     *  @param srvcsForEquals array of instantiated services for comparison
     *  @param attrs array-of-arrays of attribute objects for matching
     *  @param nSrvcsPerClass number of service instances per class type
     *  @param template template to use for matching
     *  @param preEventState the state array of tuples prior to the event
     *  @param postEventState the state array of tuples after the event
     *  @param tuples vector in which the expected tuples are accumulated
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lookup.ServiceItem
     *  @see net.jini.core.entry.Entry
     */
    public static void setExpectedEvents(Object srvcObj,
                                         ServiceItem[] srvcItems,
                                         ServiceItem[] srvcsForEquals,
                                         Entry[][] attrs,
                                         int nSrvcsPerClass,
                                         ServiceTemplate[][] template,
                                         SrvcAttrTuple[][][] preEventState,
                                         SrvcAttrTuple[][][] postEventState,
                                         List tuples)
                                                        throws Exception
    {
        int i=0;
        int j=0;
        SrvcAttrTuple tmplTuple;;
	int s0 = getSrvcIndx(srvcObj,srvcItems);
	int nAttrs = preEventState[0].length;
	int trans;
	int newSrvcIndx;
	ServiceTemplate srvcTmpl;
	for(j=0;j<nAttrs;j++) {
	    srvcTmpl = template[s0/nSrvcsPerClass][j];
	    for(i=0;i<(srvcTmpl.serviceTypes).length;i++) {
		tmplTuple = new SrvcAttrTuple
		    ( srvcsForEquals,attrs,
		      createInstance(srvcTmpl.serviceTypes[i],s0),
		      (srvcTmpl).attributeSetTemplates[0] );

		if (  ((preEventState[s0][j][i]).getAttrObj() == null)
		      &&((postEventState[s0][j][i]).getAttrObj()!= null))
		    {
		        /* attribute addition */
                        if ( (postEventState[s0][j][i]).objEquals(tmplTuple) )
			    {
				trans = ServiceRegistrar.TRANSITION_NOMATCH_MATCH;

				(preEventState[s0][j][i]).setEqualTo
				    (postEventState[s0][j][i]);
				newSrvcIndx = ((s0/nSrvcsPerClass)*nSrvcsPerClass)
				    -(nSrvcsPerClass*i);
				tuples.add(new SrvcAttrTuple
				    (srvcsForEquals,attrs,
				     srvcItems[newSrvcIndx].service,
				     attrs[j][0],
				     trans));
			    } else {
				break;
			    }
		    } else if( ((preEventState[s0][j][i]).getAttrObj() != null)
			       &&((postEventState[s0][j][i]).getAttrObj()== null))
			{
			    /* attribute deletion */
			    if ( (preEventState[s0][j][i]).objEquals(tmplTuple) ) {
				trans = ServiceRegistrar.TRANSITION_MATCH_NOMATCH;

				(preEventState[s0][j][i]).setEqualTo
				    (postEventState[s0][j][i]);
				newSrvcIndx = ((s0/nSrvcsPerClass)*nSrvcsPerClass)
				    -(nSrvcsPerClass*i);

				tuples.add(new SrvcAttrTuple
				    (srvcsForEquals,attrs,
				     srvcItems[newSrvcIndx].service,
				     attrs[j][0],
				     trans));
			    } else {
				break;
			    }
			} else if( ((preEventState[s0][j][i]).getAttrObj() != null)
				   &&((postEventState[s0][j][i]).getAttrObj()!= null))
			    {
				/* attribute modification */
				if ((postEventState[s0][j][i]).objEquals(tmplTuple)) {

				    if ((postEventState[s0][j][i]).objEquals
					(preEventState[s0][j][i]))
					{
					    trans
						= ServiceRegistrar.TRANSITION_MATCH_MATCH;
					} else {
					    trans
						= ServiceRegistrar.TRANSITION_NOMATCH_MATCH;

					    (preEventState[s0][j][i]).setEqualTo
						(postEventState[s0][j][i]);
					}
				    newSrvcIndx =((s0/nSrvcsPerClass)*nSrvcsPerClass)
					-(nSrvcsPerClass*i);

				    tuples.add(new SrvcAttrTuple
					(srvcsForEquals,attrs,
					 srvcItems[newSrvcIndx].service,
					 attrs[j][0],
					 trans));
				} else {
				    break;
				}
			    }
	    }
	}

    }

    /** From the assumption that an event was registered using a template
     *  containing the class of both the input srvcObj argument and the
     *  attrObj argument, this method "predicts" all of the service/attribute
     *  "pairs" corresponding to the set of events that will be generated 
     *  from the "base" pair represented by the input srvcObj/attrObj pair. 
     *
     *  This method will populate the given tuples vector with the set of
     *  service/attribute/transition "tuples" corresponding to the events
     *  expected to be generated when the srvcObj's state is modified 
     *  The tuples vector will contain all of the tuples that have accumulated
     *  over the life of the current test run. Thus, because that vector
     *  contains all of the expected pairs generated by previous calls to
     *  this method, the tuples vector must be created and maintained --
     *  outside of this method -- by the invoking class.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  NotifyOnEntryAttrSrvcReg
     *                  NotifyOnEntryAttrAddNull
     *                  NotifyOnEntryAttrAddNonNull
     *
     *  @param srvcIndx index into the template corresponding to the service
     *  @param srvcItems array of registered services
     *  @param srvcsForEquals array of instantiated services for comparison
     *  @param attrs array-of-arrays of attribute objects for matching
     *  @param template template to use for matching
     *  @param state the state array of tuples after the event
     *  @param tuples vector in which the expected tuples are accumulated
     *  @exception TestException usually indicates a failure
     *  @see net.jini.core.lookup.ServiceItem
     *  @see net.jini.core.entry.Entry
     */
    public static void setExpectedEvents(int srvcIndx,
                                         ServiceItem[] srvcItems,
                                         ServiceItem[] srvcsForEquals,
                                         Entry[][] attrs,
                                         ServiceTemplate[] template,
                                         SrvcAttrTuple[][][] state,
                                         List tuples)
                                                        throws Exception
    {
        int trans = ServiceRegistrar.TRANSITION_NOMATCH_MATCH;
        Object tmplAttr = (template[srvcIndx]).attributeSetTemplates[0];

        for(int i=0;i<state.length;i++) {
            for(int j=0;j<state[i].length;j++) {
                if (state[i][j][0] != null) {
                    Object stateAttr = (state[i][j][0]).getAttrObj();
                    if (stateAttr != null) {
                        if (attrsMatch(stateAttr,tmplAttr,true)) {
                            tuples.add(new SrvcAttrTuple
                                               (srvcsForEquals,attrs,
                                                (state[i][j][0]).getSrvcObj(),
                                                stateAttr,
                                                trans));
		        }
		    }
		}
	    }
	}
    }

    /** Compares the number and content of the given set of expected events
     *  to the given set of received events; where the event information 
     *  is stored in the given vectors as objects of class type SrvcAttrTuple
     *  @param receivedTuples array of received event tuples
     *  @param expectedTuples array of expected event tuples
     *  @param maxWaitTime maximum number of milliseconds to wait for events
     *  @param showTime true/false: write elapsed time to standard output
     *  @exception TestException usually indicates a failure
     */
    public static void verifyEventTuples(List receivedTuples, List expectedTuples, long maxWaitTime, boolean showTime, Object lock)
                                                         throws Exception
    {
        int i,j;
        long waitDeltaT = N_MS_PER_MIN;
        long nMsWaited  = waitDeltaT;
        /* give the Listener a chance to collect all events */
        while(true) {
	    try {
                synchronized (lock){
                    lock.wait(waitDeltaT);
                }
	    } catch (InterruptedException e) { }
            if (showTime) {
                System.out.println("Total Time: "
                                   +(nMsWaited/N_MS_PER_MIN)+
                                   " mins");
	    }
            if (receivedTuples.size() != expectedTuples.size()) {
	        if (nMsWaited < maxWaitTime) {
                    nMsWaited = nMsWaited + waitDeltaT;
		} else {
                    throw new TestException
                     ("# of Events Received ("+receivedTuples.size()+
                      ") != # of Events Expected ("+expectedTuples.size()+")");
		}
	    } else {
                break;
	    }
	}
        if (showTime) {
            System.out.println("\n# of Events Expected = "
                               +expectedTuples.size());
            System.out.println("# of Events Received = "
                               +receivedTuples.size()+"\n");
            System.out.println
                           ("# of seconds each event took to arrive (approx): "
                   +(N_MS_PER_SEC*receivedTuples.size())/nMsWaited+
                            " secs\n");
            System.out.println
                       ("Comparing Received Events to Expected Events ...\n");
	}
        verifyEventTupleContent(receivedTuples,expectedTuples);
    }

    /** Compares the number and content of the given set of expected events
     *  to the given set of received events; where the event information 
     *  is stored in the given vectors as objects of class type SrvcAttrTuple
     *  @param receivedTuples array of received event tuples
     *  @param expectedTuples array of expected event tuples
     *  @param maxWaitTime maximum number of milliseconds to wait for events
     *  @exception TestException usually indicates a failure
     */
    public static void verifyEventTuples(Vector  receivedTuples,
                                         Vector  expectedTuples,
                                         long    maxWaitTime)
                                                         throws Exception
    {
        int i,j;
        long waitDeltaT = N_MS_PER_MIN;
        long nMsWaited  = waitDeltaT;
        /* give the Listener a chance to collect all events */
        while(true) {
	    try {
                Thread.sleep(waitDeltaT);
	    } catch (InterruptedException e) { }
            if (receivedTuples.size() != expectedTuples.size()) {
	        if (nMsWaited < maxWaitTime) {
                    nMsWaited = nMsWaited + waitDeltaT;
		} else {
                    throw new TestException
                     ("# of Events Received ("+receivedTuples.size()+
                      ") != # of Events Expected ("+expectedTuples.size()+")");
		}
	    } else {
                break;
	    }
	}
        verifyEventTupleContent(receivedTuples,expectedTuples);
    }

    /** Based on the number of service class instances and the number of
     *  attributes per service participating in the current test run, 
     *  this method computes and returns the maximum number of milliseconds
     *  to wait for all events to arrive from the lookup service before 
     *  attempting to verify the events.
     *  @param nSrvcs total number of service instances registered
     *  @param nAttrs total number of attribute instances
     *  @return long 
     */
    public static long getMaxNMsToWaitForEvents(int nSrvcs,
                                                int nAttrs) {
        long maxTime = 10*nSrvcs*nAttrs*N_MS_PER_SEC;
        if (maxTime < N_MS_PER_MIN) {
            maxTime = N_MS_PER_MIN;
	}
        return maxTime;
    }

    /** Based on the number of service class instances and the number of
     *  attributes per service participating in the current test run, 
     *  this method computes and returns the maximum number of milliseconds
     *  to wait for all events to arrive from the lookup service before 
     *  attempting to verify the events.
     *  @param nSrvcs total number of service instances registered
     *  @param nAttrs total number of attribute instances
     *  @param showTime true/false: write elapsed time to standard output
     *  @return long 
     */
    public static long getMaxNMsToWaitForEvents(int nSrvcs,
                                                int nAttrs,
                                                boolean showTime) {
        long maxTime = getMaxNMsToWaitForEvents(nSrvcs,nAttrs);
        if (showTime) {
            System.out.println("\nmaxNMsToWaitForEvents = "
                               +(maxTime/N_MS_PER_MIN)+" mins");
	}
        return maxTime;
    }

    /** This method sets values for the maximum duration of all service
     *  and event leases.
     *  @param sysConfig the test config object
     *  @param leaseDuration maximum service lease duration and event lease
     *         duration in milliseconds
     */
    public static void setLeaseDuration(QAConfig sysConfig,
                                        long leaseDuration) 
    {
        setLeaseDuration(sysConfig,leaseDuration,leaseDuration);
    }

    /** This method sets values for the maximum duration of all service
     *  and event leases.
     *  @param sysConfig the test config object
     *  @param serviceLeaseDuration maximum service lease duration 
     *         in milliseconds
     *  @param eventLeaseDuration maximum event lease duration 
     *         in milliseconds
     */
    public static void setLeaseDuration(QAConfig sysConfig,
                                        long serviceLeaseDuration,
                                        long eventLeaseDuration) 
    {
        sysConfig.addOverrideProvider(new ServiceLeaseOverrideProvider(
            sysConfig, serviceLeaseDuration, eventLeaseDuration));
    }

    /** Prints the fields of each element (tuple) of the given tuples vector. 
     *  Each element of the vector must be an instance of the class 
     *  SrvcAttrTuple. This method is intended to be used only for
     *  debugging.
     *  @param tuples vector in which the expected tuples are accumulated
     */
    public static void displayTuples(Vector tuples) {
        System.out.println
          ("\n--------------------------------------------------------------");
        int i=0;
        Object tupleSrvc;
        Object tupleAttr;
        int trans;
	try {
            for(i=0;i<tuples.size();i++) {
                tupleSrvc
                     = ((SrvcAttrTuple)tuples.get(i)).getSrvcObj();
                tupleAttr
                     = ((SrvcAttrTuple)tuples.get(i)).getAttrObj();
                trans
                  = ((SrvcAttrTuple)tuples.get(i)).getTransition();
                System.out.println("srvcObj    = "+tupleSrvc);
                System.out.println("attrObj    = "+tupleAttr);
                System.out.println("Transition = "+trans_str[trans]);
                if(i<(tuples.size()-1))System.out.println(" ");
            }
	} catch (ClassCastException e) {
            System.out.println
                   ("displayTuples: ClassCastException (element at index i="+i+
                    " can NOT be cast to type SrvcAttrTuple)");
	} catch (ArrayIndexOutOfBoundsException e) {
            System.out.println
                   ("displayTuples: ArrayIndexOutOfBoundsException (i="+i+")");
	}
        System.out.println
          ("--------------------------------------------------------------\n");
    }

    /** Check if an array contains the object. return true if it contains */
    public static boolean isArrayContain(Object a[], Object o) {
	for(int i=0; i<a.length; i++) 
	    if (a[i].equals(o) )
		return true;
	return false;
    }
 

    /** Prints the fields of each element (tuple) of the given state array. 
     *  Each element of the array must be an instance of the class 
     *  SrvcAttrTuple. This method is intended to be used only for
     *  debugging.
     *  @param state array of state tuples
     */
    private static void displayTuples(SrvcAttrTuple[][][] state) {
        System.out.println
          ("\n--------------------------------------------------------------");
        int i=0;
        int j=0;
        int k=0;
        Object tupleSrvc;
        Object tupleAttr;
        int trans;
	try {
            for(i=0;i<state.length;i++) {
                for(j=0;j<(state[i]).length;j++) {
                    for(k=0;k<(state[i][j]).length;k++) {
                        tupleSrvc 
                    = ((SrvcAttrTuple)state[i][j][k]).getSrvcObj();
                        tupleAttr
                    = ((SrvcAttrTuple)state[i][j][k]).getAttrObj();
                        trans
                    = ((SrvcAttrTuple)state[i][j][k]).getTransition();
                        System.out.println("srvcObj    = "+tupleSrvc);
                        System.out.println("attrObj    = "+tupleAttr);
                        System.out.println("Transition = "+trans_str[trans]);
                    if(k<(state[i][j]).length-1)System.out.println(" ");
		    }
                    if(j<(state[i]).length-1)System.out.println(" ");
                }
                if(i<state.length-1)System.out.println(" ");
            }
	} catch (ClassCastException e) {
            System.out.println
                        ("displayTuples: ClassCastException (element at i="+i+
                         ",j="+j+" can NOT be cast to type SrvcAttrTuple)");
	} catch (ArrayIndexOutOfBoundsException e) {
            System.out.println
                   ("displayTuples: ArrayIndexOutOfBoundsException (i="+i+
                                                                  ",j="+j+")");
	}
        System.out.println
          ("--------------------------------------------------------------\n");
    }

    /*  Compares the content of the given set of expected events to the given
     *  set of received events (this method is shared by the overloaded 
     *  versions of the method verifyEventTuples)
     */
    private static void verifyEventTupleContent(List<SrvcAttrTuple>  receivedTuples,
                                                List<SrvcAttrTuple>  expectedTuples)
                                                         throws Exception
    {
        int i,j;
//	ListIterator<SrvcAttrTuple> rit = receivedTuples.listIterator();
//	ListIterator<SrvcAttrTuple> eit = expectedTuples.listIterator();
        iLoop:
//	while (rit.hasNext() && eit.hasNext()){
//	    SrvcAttrTuple received = rit.next();
//	    SrvcAttrTuple expected = eit.next();
//	    if (received.equals(expected)) continue iLoop;
//	    throw new TestException("Received an UNEXPECTED Event\nSrvc  = " +
//		    received.getSrvcObj() + ",\nAttr  = " +
//		    received.getAttrObj() + ",\nTrans = " +
//		    received.getTransition() + ",\n EXPECTED Event was\nSrvc  = "+
//		    expected.getSrvcObj() + ",\nAttr  = " +
//		    expected.getAttrObj() + ",\nTrans = " +
//		    expected.getTransition()
//		    );
//	}
        for(i=0; i<receivedTuples.size(); i++) {
	    /*  step through the vector containing the expected tuples
             *  if the current received tuple is not in the set of 
             *  expected tuples, declare failure
             */
           for(j=0;j<expectedTuples.size();j++) {
                if((receivedTuples.get(i)).equals(expectedTuples.get(j))) {
                    expectedTuples.remove(j);
                    continue iLoop;
		}
	    }
            throw new TestException
                   ("Received an UNEXPECTED Event\nSrvc  = "
                    +(((SrvcAttrTuple)receivedTuples.get(i)).getSrvcObj())+
                    ",\nAttr  = "
                    +(((SrvcAttrTuple)receivedTuples.get(i)).getAttrObj())+
                    ",\nTrans = "
                    +(((SrvcAttrTuple)receivedTuples.get(i)).getTransition()));
        }
    }

    /** Removes any repeated elements from the ArrayList and then compares
     *  the String elements in the array to the String elements in ArrayList.
     *  Returns true if the sets are equal in size and if each element in
     *  one set is equal to one element in the other set; returns false
     *  otherwise.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  SimpleAddMemberGroups
     *                  SimpleSetMemberGroups
     *                  SimpleRemoveMemberGroups
     *
     *  @param returnedGroups array of String elements from getMemberGroups
     *  @param expectedGroups ArrayList of expected String elements
     */
    public static boolean groupsAreEqual(String[] returnedGroups,
                                         ArrayList expectedGroups) {
        ArrayList filteredList = removeRepeatedElems(expectedGroups);
        if (returnedGroups.length != filteredList.size()) {
            return false;
	}
	iLoop:
        for(int i=0;i<filteredList.size();i++) {
            for(int j=0;j<returnedGroups.length;j++) {
                if (returnedGroups[j].equals((String)(filteredList.get(i)))) {
                    continue iLoop;
		}
	    }
            return false;
        }
        return true;
    }

    /** Removes any repeated elements from the input ArrayList; returns
     *  a new ArrayList containing all unique elements from the input list.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  SimpleAddMemberGroups
     *                  SimpleSetMemberGroups
     *                  SimpleRemoveMemberGroups
     *
     *  @param list ArrayList of String elements
     */
    public static ArrayList removeRepeatedElems(ArrayList list) {
        ArrayList newList = new ArrayList();
        iLoop:
        for(int i=0;i<list.size();i++) {
            String str = (String)(list.get(i));
            for(int j=0; j<newList.size();j++) {
                if (str.equals((String)(newList.get(j)))) {
                    continue iLoop;
		}
	    }
            newList.add(str);
        }
        return newList;
    }

    /** Removes all elements of the String array from the ArrayList; returns
     *  a new ArrayList containing the elements of the original ArrayList 
     *  minus elements from the String array.
     *
     *  This method is currently employed by the following test classes:
     *
     *                  SimpleRemoveMemberGroups
     *
     *  @param list ArrayList of String elements
     *  @param strArray array of String elements
     */
    public static ArrayList removeListFromArray(ArrayList list,
                                                String[] strArray) {
        ArrayList newList = new ArrayList();
        iLoop:
        for(int i=0;i<list.size();i++) {
            String str = (String)(list.get(i));
            for(int j=0;j<strArray.length;j++) {
                if (str.equals(strArray[j])) {
                    continue iLoop;
		}
	    }
            newList.add(str);
        }
        return newList;
    }

    /** Prints the elements of the input String array.
     *  
     *  This method is intended to be used only for debugging.
     *  @param strArray array of String elements
     */
    public static void displayStringArray(String[] strArray) {
        System.out.println(" ");
        for(int i=0;i<strArray.length;i++) {
	    if ((i>=0)&&(i<=9)) {
                System.out.println("["+i+"]   = "+strArray[i]);
            } else if ((i>=10)&&(i<=99)) {
                System.out.println("["+i+"]  = "+strArray[i]);
   	    } else {
                System.out.println("["+i+"] = "+strArray[i]);
            }
	}
    }

    /** Prints the elements of the input ArrayList.
     *  
     *  Each element of the ArrayList must be of type String. This method 
     *  is intended to be used only for debugging.
     *  @param list ArrayList of String elements
     */
    public static void displayStringArrayList(ArrayList list) {
        System.out.println(" ");
        for(int i=0;i<list.size();i++) {
	    if ((i>=0)&&(i<=9)) {
                System.out.println("["+i+"]   = "+(String)(list.get(i)));
            } else if ((i>=10)&&(i<=99)) {
                System.out.println("["+i+"]  = "+(String)(list.get(i)));
   	    } else {
                System.out.println("["+i+"] = "+(String)(list.get(i)));
            }
	}
    }

    /** Determines if the input Objects are equal; where equality is defined
     *  by Class equality and field equality. A special case is subclasses
     *  of net.jini.lookup.entry.ServiceType. If both objects are assignable
     *  to ServiceType, then class equality is not required. This is necessary
     *  to handle the case where org.apache.river.lookup.entry.BasicServiceType
     *  is being compared, and one of the object was obtained from a preferred
     *  class loader.
     *
     *  @param obj0 the to compare to obj1 for equality
     *  @param obj1 the to compare to obj0 for equality
     *  @return boolean
     */
    public static boolean objsAreEqual(Object obj0, Object obj1) {
        Class obj0Class = (obj0).getClass();
        Class obj1Class = (obj1).getClass();
        if (!(obj1Class.equals(obj0Class))) {
            Class st = net.jini.lookup.entry.ServiceType.class;
	    if (!st.isAssignableFrom(obj0Class)
                || !st.isAssignableFrom(obj1Class)) {
                return false;
            }
        }
        Field[] obj0Fields = obj0Class.getFields();
        Field[] obj1Fields = obj1Class.getFields();
        try {
	    jLoop:
            for(int j=0;j<obj0Fields.length;j++) {
                String name0 = (obj0Fields[j]).getName();
                for(int k=0;k<obj1Fields.length;k++) {
                    String name1 = (obj1Fields[k]).getName();
                    if (name0.equals(name1)) {
                        Object field0 = (obj0Fields[j]).get(obj0);
                        Object field1 = (obj1Fields[k]).get(obj1);
			if (field0 == null) {
			    if (field1 != null)
				return false;
			    continue jLoop;
			} else if (field0.equals(field1)) {
                            continue jLoop;
                        } else {
                            return false;
                        }
		    }
		}
                return false;
            }
        } catch (IllegalAccessException e) {
            return false;
	}
        return true;
    }
}
