/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package net.jini.lookup;

import com.sun.jini.logging.Levels;
import com.sun.jini.lookup.entry.LookupAttributes;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.thread.TaskManager.Task;

import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import net.jini.lease.LeaseRenewalManager;
import net.jini.export.Exporter;
import net.jini.io.MarshalledInstance;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;

import net.jini.core.lease.Lease;
import net.jini.core.entry.Entry;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.EventRegistration;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceEvent;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

import java.io.IOException;

import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * The <code>ServiceDiscoveryManager</code> class is a helper utility class
 * that any client-like entity can use to "discover" services registered 
 * with any number of lookup services of interest. On behalf of such
 * entities, this class maintains - as much as possible - up-to-date
 * state information about both the lookup services the entity wishes
 * to query, and the services the entity wishes to acquire and use.
 * By maintaining current service state information, the entity can 
 * implement efficient mechanisms for service access and usage.
 * <p>
 * There are three basic usage patterns for this class. In order of
 * importance and typical usage, those patterns are:
 * <p>
 * <ul>
 *   <li> The entity requests that the <code>ServiceDiscoveryManager</code>
 *        create a cache (an instance of
 *        {@link net.jini.lookup.LookupCache LookupCache}) which will
 *        asynchronously "discover", and locally store, references
 *        to services that match criteria defined by the entity; services
 *        which are registered with one or more lookup services managed
 *        by the <code>ServiceDiscoveryManager</code> on behalf of the entity.
 *        The cache can be viewed as a set of service references that the 
 *        entity can access locally as needed through one of the public, 
 *        non-remote methods provided in the cache's interface. Thus, rather
 *        than making costly remote queries of multiple lookup services at
 *        the point in time when the entity needs the service, the entity
 *        can simply make local queries on the cache for the services that
 *        the cache acquired and stored at a prior time. An entity should
 *        employ this pattern when the entity must make <i>frequent</i>
 *        queries for multiple services. By populating the cache with
 *        multiple instances of the desired services, redundancy in the
 *        availability of those services can be provided. Thus, if an
 *        instance of a service is found to be unavailable when needed,
 *        the entity can execute a local query on the cache rather than
 *        one or more remote queries on the lookup services to acquire
 *        an instance that is available. To employ this pattern, the entity
 *        invokes the method 
 *        {@link net.jini.lookup.ServiceDiscoveryManager#createLookupCache
 *        createLookupCache}.
 *   <li> The entity can register with the event mechanism provided by the
 *        <code>ServiceDiscoveryManager</code>. This event mechanism allows the
 *        entity to request that it be notified when a service of interest
 *        is discovered for the first time, or has encountered a state change
 *        such as removal from all lookup services, or attribute set changes.
 *        Although interacting with a local cache of services in the way
 *        described in the first pattern can be very useful to entities that
 *        need frequent access to multiple services, some client-like 
 *        entities may wish to interact with the cache in a reactive manner.
 *        For example, an entity such as a service browser typically wishes
 *        to be notified of the arrival of new services of interest as well
 *        as any changes in the state of the current services in the cache.
 *        In these situations, polling for such changes is usually viewed as
 *        undesirable. If the cache were to also provide an event mechanism
 *        with notification semantics, the needs of entities that employ
 *        either pattern can be satisfied. To employ this pattern, the entity
 *        must create a cache and supply it with an instance of the
 *        {@link net.jini.lookup.ServiceDiscoveryListener
 *        ServiceDiscoveryListener} interface that will receive instances of 
 *        {@link net.jini.lookup.ServiceDiscoveryEvent ServiceDiscoveryEvent}
 *        when events of interest, related to the services in the cache, occur.
 *   <li> The entity, through the public API of the 
 *        <code>ServiceDiscoveryManager</code>, can directly query the lookup 
 *        services managed by the <code>ServiceDiscoveryManager</code> for 
 *        services of interest; employing semantics similar to the semantics
 *        employed in a typical lookup service query made through the
 *        {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
 *        interface. Such queries will result in a remote call being made at
 *        the same time the service is needed (unlike the first pattern, in
 *        which remote calls typically occur prior to the time the service is
 *        needed). This pattern may be useful to entities needing to find
 *        services on an infrequent basis, or when the cost of making a remote
 *        call is outweighed by the overhead of maintaining a local cache (for
 *        example, due to limited resources). Although an entity that needs
 *        to query lookup service(s) can certainly make such queries through
 *        the {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
 *        interface, the <code>ServiceDiscoveryManager</code> provides a broad
 *        API with semantics that are richer than the semantics of the 
 *        {@link net.jini.core.lookup.ServiceRegistrar#lookup lookup} methods
 *        provided by the {@link net.jini.core.lookup.ServiceRegistrar
 *        ServiceRegistrar}. This API encapsulates functionality that many
 *        client-like entities may find more useful when managing both the set
 *        of desired lookup services, and the service queries made on those
 *        lookup services. To employ this pattern, the entity simply
 *        instantiates this class with the desired parameters, and then
 *        invokes the appropriate version of the
 *        {@link net.jini.lookup.ServiceDiscoveryManager#lookup lookup}
 *        method when the entity wishes to acquire a service that matches 
 *        desired criteria.
 * </ul>
 * <p>
 * All three mechanisms just described - local queries on the cache,
 * service discovery notification, and remote lookups - employ the same 
 * template-matching scheme as that employed in the 
 * {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar} interface.
 * Additionally, each mechanism allows the entity to supply an object 
 * referred to as a <i>filter</i>; an instance of 
 * {@link net.jini.lookup.ServiceItemFilter ServiceItemFilter}. A filter
 * is a non-remote object that defines additional matching criteria that the
 * <code>ServiceDiscoveryManager</code> applies when searching for the
 * entity's services of interest. Employing a filter is particularly useful
 * to entities that wish to extend the capabilities of the standard
 * template-matching scheme.
 * <p>
 * In addition to (or instead of) employing a filter to apply additional
 * matching criteria to candidate service proxies initially found through
 * template matching, filters can also be used to extend the selection
 * process so that only proxies that are <i>safe</i> to use are returned
 * to the entity. To do this, the entity would use the
 * {@link net.jini.lookup.ServiceItemFilter ServiceItemFilter} interface to
 * supply the <code>ServiceDiscoveryManager</code> or
 * {@link net.jini.lookup.LookupCache LookupCache} with a filter that,
 * when applied to a candidate proxy, performs a set of operations that
 * is referred to as <i>proxy preparation</i>. As described in the 
 * documentation for {@link net.jini.security.ProxyPreparer}, proxy
 * preparation typically includes operations such as, verifying trust
 * in the proxy, specifying client constraints, and dynamically granting
 * necessary permissions to the proxy.
 * <p>
 * Note that this utility class is not remote. Clients and services that wish
 * to use this class will create an instance of this class in their own address
 * space to manage the state of discovered services and their associated
 * lookup services locally.
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * The following implementation-specific items are discussed below:
 * <ul><li> <a href="#sdmConfigEntries">Configuring ServiceDiscoveryManager</a>
 *     <li> <a href="#sdmLogging">Logging</a>
 * </ul>
 *
 * <a name="sdmConfigEntries">
 * <p>
 * <b><font size="+1">Configuring ServiceDiscoveryManager</font></b>
 * <p>
 * </a>
 *
 * This implementation of <code>ServiceDiscoveryManager</code> supports
 * the following configuration entries; where each configuration entry
 * name is associated with the component name
 * <code>net.jini.lookup.ServiceDiscoveryManager</code>. Note that the
 * configuration entries specified here are specific to this implementation
 * of <code>ServiceDiscoveryManager</code>. Unless otherwise stated, each
 * entry is retrieved from the configuration only once per instance of this
 * utility, where each such retrieval is performed in the constructor.
 * <p>
 * It is important to note that in addition to allowing a client of this
 * utility to request - through the public API - the creation of a cache
 * that is used externally by the client, this utility also creates
 * instances of the cache that are used internally by the utility itself.
 * As such, in addition to the configuration entries that are used only
 * in this utility (and not in any cache), and the configuration entries
 * that are retrieved during the construction of each new cache (and used
 * by only that cache), there are configuration entries specified below
 * that are retrieved once during the construction of this utility, but
 * which are shared with, and used by, the caches that are created.
 *
 *
 * <a name="cacheTaskManager">
 * <table summary="Describes the cacheTaskManager configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>cacheTaskManager</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> {@link com.sun.jini.thread.TaskManager}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code>new 
 *             {@link com.sun.jini.thread.TaskManager#TaskManager()
 *                                   TaskManager}(10, (15*1000), 1.0f)</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The object that pools and manages the various threads 
 *            executed by each of the lookup caches created by this 
 *            utility. There is one such task manager created for each
 *            cache. The default manager creates a maximum of 10 threads,
 *            waits 15 seconds before removing idle threads, and uses a
 *            load factor of 1.0 when determining whether to create a new
 *            thread. For each cache that is created in this utility, a
 *            single, separate instance of this task manager will be
 *            retrieved and employed by that cache. This object should
 *            not be shared with other components in the application that
 *            employs this utility.
 * </table>
 *
 * <a name="discardTaskManager">
 * <table summary="Describes the discardTaskManager configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>discardTaskManager</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> {@link com.sun.jini.thread.TaskManager}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code>new 
 *             {@link com.sun.jini.thread.TaskManager#TaskManager()
 *                                   TaskManager}(10, (15*1000), 1.0f)</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The object that pools and manages the threads, executed 
 *            by a cache, that wait on verification events after a 
 *            previousy discovered service has been discarded. The 
 *            default manager creates a maximum of 10 threads, waits 
 *            15 seconds before removing idle threads, and uses a load 
 *            factor of 1.0 when determining whether to create a new
 *            thread. For each cache that is created in this utility,
 *            a single, separate instance of this task manager will be
 *            retrieved and employed by that cache. This object should
 *            not be shared with other components in the application
 *            that employs this utility.
 * </table>
 *
 * <a name="discardWait">
 * <table summary="Describes the discardWait
 *                configuration entry" border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>discardWait</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> <code>long</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code>2*(5*60*1000)</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The value used to affect the behavior of the mechanism
 *            that handles the <i>service discard problem</i> described
 *            in this utility's specification. This item allows each
 *            entity that uses this utility to define how long (in
 *            milliseconds) to wait for verification from the lookup
 *            service(s) that a discarded service is actually down
 *            before committing or un-committing a requested service
 *            discard. The current implementation of this utility
 *            defaults to waiting 10 minutes (twice the maximum lease
 *            duration granted by the Reggie implementation of the
 *            lookup service). Note that this item is used only by the
 *            caches (both internal and external) that are created by
 *            this utility, and not by the utility itself.
 * </table>
 *
 * <a name="discoveryManager">
 * <table summary="Describes the discoveryManager configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>discoveryManager</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> {@link net.jini.discovery.DiscoveryManagement}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code> new 
 *    {@link net.jini.discovery.LookupDiscoveryManager#LookupDiscoveryManager(
 *      java.lang.String[],
 *      net.jini.core.discovery.LookupLocator[],
 *      net.jini.discovery.DiscoveryListener,
 *      net.jini.config.Configuration) LookupDiscoveryManager}(
 *                       "", 
 *                       new {@link net.jini.core.discovery.LookupLocator}[0],
 *                       null, config)</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The object used to manage the discovery processing
 *            performed by this utility. This entry will be retrieved
 *            from the configuration only if no discovery manager is
 *            specified in the constructor. Note that this object should
 *            not be shared with other components in the application that
 *            employs this utility.  This item is used only by the service
 *            discovery manager, and not by any cache that is created.
 * </table>
 *
 * <a name="eventLeasePreparer">
 * <table summary="Describes the eventLeasePreparer configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>eventLeasePreparer</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Type: <td> {@link net.jini.security.ProxyPreparer}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
 *                     </code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *   Description:
 *     <td> Preparer for the leases returned when a cache registers
 *          with the event mechanism of any of the discovered lookup
 *          services. This item is used only by the caches (both
 *          internal and external) that are created by this utility,
 *          and not by the utility itself. 
 *          <p>
 *          Currently, no methods of the returned proxy are invoked by
 *          this utility.
 * </table>
 *
 * <a name="eventListenerExporter">
 * <table summary="Describes the eventListenerExporter configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>eventListenerExporter</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Type: <td> {@link net.jini.export.Exporter}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Default: <td> <code> new 
 *                {@link net.jini.jeri.BasicJeriExporter#BasicJeriExporter(
 *                                        net.jini.jeri.ServerEndpoint,
 *                                        net.jini.jeri.InvocationLayerFactory,
 *                                        boolean,
 *                                        boolean) BasicJeriExporter}(
 *              {@link net.jini.jeri.tcp.TcpServerEndpoint#getInstance
 *                                      TcpServerEndpoint.getInstance}(0),<br>
 *               &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
 *               &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
 *               new {@link net.jini.jeri.BasicILFactory}(),<br>
 *               &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
 *               &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
 *               false, false)</code>
 *
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *   Description:
 *     <td> Exporter for the remote event listener that each cache
 *          supplies to the lookup services whose event mechanisms 
 *          those caches register with. Note that for each cache that
 *          is created in this utility, a single, separate instance
 *          of this exporter will be retrieved and employed by that
 *          cache. Note also that the default exporter defined here
 *          will disable distributed garbage collection (DGC) for the
 *          server endpoint associated with the exported listener,
 *          and the listener backend (the "impl") will be strongly
 *          referenced. This means that the listener will not "go away"
 *          unintentionally. Additionally, that exporter also sets the
 *          <code>keepAlive</code> flag to <code>false</code> to allow
 *          the VM in which this utility runs to "go away" when 
 *          desired; and not be kept alive simply because the listener
 *          is still exported.
 * </table>
 *
 * <a name="leaseManager">
 * <table summary="Describes the leaseManager configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>leaseManager</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> {@link net.jini.lease.LeaseRenewalManager}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code> new 
 *       {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
 *        net.jini.config.Configuration) LeaseRenewalManager}(config)</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The object used to manage any event leases returned
 *            to a cache that has registered with the event mechanism
 *            of the various discovered lookup services. This entry will
 *            be retrieved from the configuration only if no lease 
 *            renewal manager is specified in the constructor. This item
 *            is used only by the caches (both internal and external)
 *            that are created by this utility, and not by the utility
 *            itself.
 * </table>
 *
 * <a name="registrarPreparer">
 * <table summary="Describes the registrarPreparer configuration entry" 
 *                border="0" cellpadding="2">
 *   <tr valign="top">
 *     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *     <th scope="col" align="left" colspan="2"> <font size="+1">
 *     <code>registrarPreparer</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Type: <td> {@link net.jini.security.ProxyPreparer}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
 *                     </code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *   Description:
 *     <td> Preparer for the proxies to the lookup services that are
 *          discovered and used by this utility. This item is used only
 *          by the service discovery manager, and not by any cache that
 *          is created.
 *          <p>
 *          The following methods of the proxy returned by this preparer are
 *          invoked by this utility:
 *       <ul>
 *         <li>{@link net.jini.core.lookup.ServiceRegistrar#lookup lookup}
 *         <li>{@link net.jini.core.lookup.ServiceRegistrar#notify notify}
 *       </ul>
 *
 * </table>
 *
 * <a name="sdmLogging">
 * <p>
 * <b><font size="+1">Logging</font></b>
 * <p>
 * </a>
 *
 * This implementation of <code>ServiceDiscoveryManager</code> uses the
 * {@link Logger} named <code>net.jini.lookup.ServiceDiscoveryManager</code>
 * to log information at the following logging levels: <p>
 *
 * <table border="1" cellpadding="5"
 *        summary="Describes the information logged by ServiceDiscoveryManager,
 *                 and the levels at which that information is logged">
 * 
 *
 * <caption halign="center" valign="top">
 *   <b><code>net.jini.lookup.ServiceDiscoveryManager</code></b>
 * </caption>
 *
 * <tr> <th scope="col"> Level</th>
 *      <th scope="col"> Description</th>
 * </tr>
 *
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>
 *     when any exception occurs while querying a lookup service, or upon
 *     applying a filter to the results of such a query
 *   </td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>
 *     when any exception occurs while attempting to register with the event
 *     mechanism of a lookup service, or while attempting to prepare the lease
 *     on the registration with that event mechanism
 *   </td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>when any exception occurs while attempting to prepare a proxy</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>
 *     when an <code>IllegalStateException</code> occurs while discarding
 *     a lookup service proxy after logging a failure that has occurred in
 *     one of the tasks executed by this utility
 *   </td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>upon failure of the lease renewal process</td>
 * </tr>
 * <tr>
 *   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
 *   <td>
 *     when an exception occurs because a remote call to a lookup service
 *     has been interrupted as a result of the termination of a cache
 *   </td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINER FINER}</td>
 *   <td>upon failure of the lease cancellation process</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>whenever any task is started</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>whenever any task completes successfully</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>whenever a lookup cache is created</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>whenever a lookup cache is terminated</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>whenever a proxy is prepared</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>
 *     when an exception (that is, <code>IllegalStateException</code>)
 *     occurs while unexporting a cache's remote event listener while the
 *     cache is being terminated
 *   </td>
 * </tr>
 * </table>
 * <p>
 * See the {@link com.sun.jini.logging.LogManager} class for one way to use
 * the logging level {@link com.sun.jini.logging.Levels#HANDLED HANDLED} in
 * standard logging configuration files.
 * <p>
 *
 * @author Sun Microsystems, Inc.
 *
 * @see net.jini.discovery.DiscoveryManagement
 * @see net.jini.lookup.LookupCache
 * @see net.jini.lookup.ServiceDiscoveryListener
 * @see net.jini.lookup.ServiceDiscoveryEvent
 * @see net.jini.core.lookup.ServiceRegistrar
 */
public class ServiceDiscoveryManager {

    /** Class for implementing register/lookup/notify/dropProxy/discard tasks*/
    private static abstract class CacheTask implements TaskManager.Task {
        protected ProxyReg reg;
        public CacheTask(ProxyReg reg) {
            this.reg = reg;
        }//end constructor
        /* check if the task is on a specific ProxyReg, return true if it is */
        public boolean isFromProxy(ProxyReg reg) {
            if(this.reg == null) return false;
            return (this.reg).equals(reg);
        }//end isFromProxy
        /** Returns true if current instance must be run after task(s) in
         *  task manager queue.
         *  @param tasks the tasks to consider.
         *  @param size elements with index less than size are considered.
         */
        public boolean runAfter(List tasks, int size) {
            return false;
        }//end runAfter
	public abstract void run();
    }//end class ServiceDiscoveryManager.CacheTask

    /** Abstract base class for controlling the order-of-execution of tasks
     *  corresponding to a particular serviceID associated with a particular
     *  lookup service.
     */
    private static abstract class ServiceIdTask extends CacheTask {
        protected ServiceID thisTaskSid;
        protected int thisTaskSeqN;
        ServiceIdTask(ServiceID srvcId, ProxyReg reg, int seqN) {
            super(reg);
            this.thisTaskSid  = srvcId;
            this.thisTaskSeqN = seqN;
        }//end constructor

        /** Returns true if the current instance of this task must be run
         *  after at least one task in task manager queue. 
         *  
         *  The criteria for determining what value to return is as follows: 
         *  
         *    If there is at least one task in the given task list that is
         *    associated with the same serviceID as this task, and that task
         *    has a sequence number less than the sequence number of this task,
         *    then run this task *after* the task in the list (return true);
         *    otherwise run this task now (return false).
         *
         *  @param tasks the tasks to consider.
         *  @param size elements with index less than size are considered.
         */
        public boolean runAfter(List tasks, int size) {
            for(int i=0; i<size; i++) {
                TaskManager.Task t = (TaskManager.Task)tasks.get(i);
                /* Compare only instances of this task class */
                if( !(t instanceof ServiceIdTask) )  continue;
                ServiceID otherTaskSid = ((ServiceIdTask)t).getServiceID();
                if( thisTaskSid.equals(otherTaskSid) ) {
                    if(thisTaskSeqN > ((ServiceIdTask)t).getSeqN()) {
                        return true;//run this task after the other task
                    }//endif
                }//endif
            }//end loop
            return false;//run this task now
        }//end runAfter

        /** Returns the ServiceID associated with this task. */
        public ServiceID getServiceID() {
            return thisTaskSid;
        }//end getServiceID

        /** Returns the unique sequence number of this task. */
        public int getSeqN() {
            return thisTaskSeqN;
        }//end getSeqN

    }//end class ServiceIdTask

    /** Class that defines the listener that will receive local events from
     *  the internal LookupCache used in the blocking versions of lookup().
     */
    private final static class ServiceDiscoveryListenerImpl 
                                          implements ServiceDiscoveryListener
    {
	ArrayList items = new ArrayList(1);
	public synchronized void serviceAdded(ServiceDiscoveryEvent event) {
	    items.add(event.getPostEventServiceItem());
	    this.notifyAll();
	}
	public void serviceRemoved(ServiceDiscoveryEvent event){ }
	public void serviceChanged(ServiceDiscoveryEvent event){ }
	public synchronized ServiceItem[] getServiceItem() {
	    ServiceItem[] r = new ServiceItem[items.size()];
	    items.toArray(r);
	    items.clear();
	    return r;
	}
    }//end class ServiceDiscoveryManager.ServiceDiscoveryListenerImpl

    /** 
     * Data structure used to group together the lease and event sequence
     * number. For each LookupCache, there is a HashMap that maps a ProxyReg
     * to an EventReg.
     */
    private final static class EventReg  {
	/* The Event notification lease */
	public Lease lease;
	/* The current event sequence number for the Service template */
	public long seqNo;
	public EventReg(Lease lease, long seqNo) {
	    this.lease = lease;
	    this.seqNo = seqNo;
	}
    }//end class ServiceDiscoveryManager.EventReg
    
    /**
     * Used in the LookupCache. For each LookupCache, there is a HashMap that
     * maps ServiceId to a ServiceItemReg. The ServiceItemReg class helps
     * track where the ServiceItem comes from.
     */
    private final static class ServiceItemReg  {
	/* Stores ServiceRegistrars that has the ServiceItem registered. */
	private final ArrayList proxys = new ArrayList(1);
	/* Flag that indicates that the ServiceItem has been discarded. */
	private boolean bDiscarded = false;
        /* The discovered service, prior to filtering. */
	public ServiceItem item;
        /* The discovered service, after filtering. */
	public ServiceItem filteredItem;
        /* Creates an instance of this class, and associates it with the given
         * lookup service proxy.
         */
	public ServiceItemReg(ServiceRegistrar proxy, ServiceItem item) {
	    addProxy(proxy);
	    this.item = item;
	}
	/* Adds the given proxy to the 'id-to-itemReg' map. This method is
         * called from this class' constructor, LookupTask, NotifyEventTask,
         * and ProxyRegDropTask.
         */
	public void addProxy(ServiceRegistrar proxy) {
	    if(!proxys.contains(proxy))  proxys.add(proxy);
	}
	/* Removes the given proxy from the 'id-to-itemReg' map. This method
         * is called from NotifyEventTask and ProxyRegDropTask.
         */
	public void removeProxy(ServiceRegistrar proxy) {
	    int index = proxys.indexOf(proxy);
	    if(index != -1)  proxys.remove(index);
	}
	/* Determines if the 'id-to-itemReg' map contains any mappings.
         * This method is called from NotifyEventTask and ProxyRegDropTask.
         */
	public boolean hasNoProxys() {
	    return proxys.isEmpty();
	}
	/* Marks the ServiceItem as discarded. */ 
	public void setDiscarded(boolean b) {
	    bDiscarded = b;
	}
	/* Returns the flag indicating whether the ServiceItem is discarded. */
	public boolean isDiscarded() {
	   return  bDiscarded;
	}
    }//end class ServiceDiscoveryManager.ServiceItemReg

    /** A wrapper class for a ServiceRegistrar. */
    private final static class ProxyReg  {
	public ServiceRegistrar proxy;
	public ProxyReg(ServiceRegistrar proxy) {
	    if(proxy == null)  throw new IllegalArgumentException
                                                     ("proxy cannot be null");
	    this.proxy = proxy;
	}//end constructor	    

	public boolean equals(Object obj) {
	    if (obj instanceof ProxyReg){
		return proxy.equals(((ProxyReg)obj).proxy);
	    } else return false;
	}//end equals

	public int hashCode() {
	    return proxy.hashCode();
	}//end hashCode

    }//end class ServiceDiscoveryManager.ProxyReg
    
    /** The Listener class for the LeaseRenewalManager. */
    private final class LeaseListenerImpl implements LeaseListener {
	private ServiceRegistrar proxy;
	public LeaseListenerImpl(ServiceRegistrar proxy) {
	    this.proxy = proxy;
	}
	/* When lease renewal fails, we discard the proxy  */
	public void notify(LeaseRenewalEvent e) {
            fail(e.getException(),proxy, this.getClass().getName(), "notify",
                 "failure occurred while renewing an event lease", false);
	}
    }//end class ServiceDiscoveryManager.LeaseListenerImpl

    /** Internal implementation of the LookupCache interface. Instances of
     *  this class are used in the blocking versions of lookup() and are
     *  returned by createLookupCache.
     */
    private final class LookupCacheImpl implements LookupCache {
	/* RemoteEventListener class that is registered with the proxy to
         * receive notifications from lookup services when any ServiceItem
         * changes (NOMATCH_MATCH, MATCH_NOMATCH, MATCH_MATCH)
	 */
        private final class LookupListener implements RemoteEventListener,
                                                      ServerProxyTrust
        {
	    public LookupListener() throws ExportException {
                lookupListenerProxy =
                     (RemoteEventListener)lookupListenerExporter.export(this);
            }//end constructor

	    public void notify(RemoteEvent evt) {
		ServiceEvent theEvent = (ServiceEvent)evt;
		notifyServiceMap( (ServiceRegistrar)theEvent.getSource(),
				  theEvent.getServiceID(),
				  theEvent.getServiceItem(),
				  theEvent.getTransition(),
				  theEvent.getSequenceNumber() );
	    }//end notify

            /** Returns a <code>TrustVerifier</code> which can be used to
             *  verify that a given proxy to this listener can be trusted.
             */
            public TrustVerifier getProxyVerifier() {
	        return new BasicProxyTrustVerifier(lookupListenerProxy);
            }//end getProxyVerifier
	}//end class LookupCacheImpl.LookupListener

	/** This task class, when executed, first registers to receive
         *  ServiceEvents from the given ServiceRegistrar. If the registration
         *  process succeeds (no RemoteExceptions), it then executes the
         *  LookupTask to query the given ServiceRegistrar for a "snapshot"
         *  of its current state with respect to services that match the
         *  given template. 
         *
         *  Note that the order of execution of the two tasks is important.
         *  That is, the LookupTask must be executed only after registration
         *  for events has completed. This is because when an entity registers
         *  with the event mechanism of a ServiceRegistrar, the entity will
         *  only receive notification of events that occur "in the future",
         *  after the registration is made. The entity will not receive events
         *  about changes to the state of the ServiceRegistrar that may have
         *  occurred before or during the registration process.
         *
         *  Thus, if the order of these tasks were reversed and the LookupTask
         *  were to be executed prior to the RegisterListenerTask, then the
         *  possibility exists for the occurrence of a change in the
         *  ServiceRegistrar's state between the time the LookupTask retrieves
         *  a snapshot of that state, and the time the event registration
         *  process has completed, resulting in an incorrect view of the
         *  current state of the ServiceRegistrar.
         */
        private final class RegisterListenerTask extends CacheTask {
	    public RegisterListenerTask(ProxyReg reg) {
		super(reg);
	    }

            public void run() {
                logger.finest("ServiceDiscoveryManager - RegisterListenerTask "
                              +"started");
		long duration = getLeaseDuration();
		if(duration < 0)  return;
                try {
                    EventReg eventReg = registerListener(reg.proxy,
                                                         tmpl, 
                                                         lookupListenerProxy,
                                                         duration);
                    synchronized(serviceIdMap) {
                        /* Cancel the lease if the cache has been terminated */
                        if(bCacheTerminated) {
                            cancelLease(eventReg.lease); 
                        } else {
                            eventRegMap.put(reg, eventReg);
                        }//endif
                    }//end sync(serviceIdMap)
                    /* Execute the LookupTask only if there were no problems */
                    (new LookupTask(reg)).run();
                } catch (Exception e) {
                    boolean cacheTerminated;
                    synchronized(serviceIdMap) {
                        cacheTerminated = bCacheTerminated;
                    }//end sync
                     ServiceDiscoveryManager.this.fail
                         (e,reg.proxy,this.getClass().getName(),"run",
                          "Exception occurred while attempting to register "
                          +"with the lookup service event mechanism",
                          cacheTerminated);
                }
                logger.finest("ServiceDiscoveryManager - RegisterListenerTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.RegisterListenerTask

	/** This class requests a "snapshot" of the given registrar's state.*/
        private final class LookupTask extends CacheTask {
	    public LookupTask(ProxyReg reg) {
		super(reg);
	    }
            public void run() {

                logger.finest("ServiceDiscoveryManager - LookupTask started");
		ServiceRegistrar proxy = reg.proxy;
		ServiceMatches matches;
                /* For the given lookup, get all services matching the tmpl */
		try {
		    matches = proxy.lookup(tmpl, Integer.MAX_VALUE);
		} catch (Exception e) {
                    boolean cacheTerminated;
                    synchronized(serviceIdMap) {
                        cacheTerminated = bCacheTerminated;
                    }//end sync
                    ServiceDiscoveryManager.this.fail
                           (e,proxy,this.getClass().getName(),"run",
                            "Exception occurred during call to lookup",
                            cacheTerminated);
		    return;
		}
                if(matches.items == null) {
                    throw new AssertionError("spec violation in queried "
                              +"lookup service: ServicesMatches instance "
                              +"returned by call to lookup() method contains "
                              +"null 'items' field");
                }
                synchronized(serviceIdMap) {
                    /* 1. Cleanup "orphaned" itemReg's. */
                    Iterator iter = (serviceIdMap.entrySet()).iterator();
                    while(iter.hasNext()) {
                        Map.Entry e = (Map.Entry)iter.next();
                        ServiceID srvcID = (ServiceID)e.getKey();
                        ServiceItem itemInSnapshot = findItem(srvcID,
                                                              matches.items);
                        if(itemInSnapshot != null) continue;//not an orphan
                        ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
                        UnmapProxyTask t = new UnmapProxyTask(reg,
                                                              itemReg,
                                                              srvcID,
                                                              taskSeqN++);
                        cacheTaskMgr.add(t);
                    }//end loop
                    /* 2. Handle "new" and "old" items from the given lookup */
                    for(int i=0; i<(matches.items).length; i++) {
                        /* Skip items with null service field (Bug 4378751) */
                        if( (matches.items[i]).service == null )  continue;
                        NewOldServiceTask t =
                                        new NewOldServiceTask(reg,
                                                              matches.items[i],
                                                              false,
                                                              taskSeqN++);
                        cacheTaskMgr.add(t);
                    }//end loop
                }//end sync(serviceIdMap)
                logger.finest("ServiceDiscoveryManager - LookupTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.LookupTask

	/** When the given registrar is discarded, this Task class is used to
         *  remove the registrar from the various maps maintained by this
         *  cache.
         */
        private final class ProxyRegDropTask extends CacheTask {	    
	    public ProxyRegDropTask(ProxyReg reg) {
		super(reg);
	    }
            public void run() {
                logger.finest("ServiceDiscoveryManager - ProxyRegDropTask "
                              +"started");
		synchronized(serviceIdMap) {
		    //lease has already been cancelled by removeProxyReg 
		    if(eventRegMap.containsKey(reg)) {
		       eventRegMap.remove(reg);
                    }
		}//end sync(serviceIdMap)
                /* For each itemReg in the serviceIdMap, disassociate the
                 * lookup service referenced here from the itemReg; and
                 * if the itemReg then has no more lookup services associated
                 * with it, remove the itemReg from the map and send a
                 * service removed event.
                 */
                synchronized(serviceIdMap) {
                    Iterator iter = (serviceIdMap.entrySet()).iterator();
                    while(iter.hasNext()) {
                        Map.Entry e = (Map.Entry)iter.next();
                        ServiceID srvcID = (ServiceID)e.getKey();
                        ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
                        UnmapProxyTask t = new UnmapProxyTask(reg,
                                                              itemReg,
                                                              srvcID,
                                                              taskSeqN++);
                        cacheTaskMgr.add(t);
                    }//end loop
                }//end sync(serviceIdMap)
                logger.finest("ServiceDiscoveryManager - ProxyRegDropTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.ProxyRegDropTask

	/** Task class used to asynchronously discard the given service. */
        private final class DiscardServiceTask extends CacheTask {
	    private Object service;
	    public DiscardServiceTask(Object service) {
		super(null);
		this.service = service;
	    }

            public void run() {
                logger.finest("ServiceDiscoveryManager - DiscardServiceTask "
                              +"started");
		Iterator iter = getServiceIdMapEntrySetIterator();
		while(iter.hasNext()) {
		    Map.Entry e = (Map.Entry)iter.next();
		    ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
                    ServiceItem item;
                    synchronized(itemReg) {
                        item = itemReg.filteredItem;
                    }//end sync(itemReg)
		    if( (item.service).equals(service) ) {
			ServiceID sid = (ServiceID)e.getKey();
                        removeServiceNotify(item);
                        serviceDiscardTimerTaskMgr.add
                                         ( new ServiceDiscardTimerTask(sid) );
			return;
		    }//endif
		}//end loop
                logger.finest("ServiceDiscoveryManager - DiscardServiceTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.DiscardServiceTask

	/** Task class used to asynchronously notify all registered service
         *  discovery listeners of serviceAdded/serviceRemoved/serviceChanged
         *  events.
         */
        private final class NotifyEventTask extends ServiceIdTask {
	    private ServiceID sid;
	    private ServiceItem item;
	    private int transition;
	    public NotifyEventTask(ProxyReg reg,
				   ServiceID sid,
				   ServiceItem item,
				   int transition,
                                   int seqN)
            {
                super(sid, reg, seqN);
		this.sid = sid;
		this.item = item;
		this.transition = transition;
	    }//end constructor

            public void run() {
                logger.finest("ServiceDiscoveryManager - NotifyEventTask "
                              +"started");
                /* Fix for Bug ID 4378751. The conditions described by that
                 * bug involve a ServiceItem (corresponding to a previously
                 * discovered service ID) having a null service field. A
                 * null service field is due to an UnmarshalException caused
                 * by a SecurityException that results from the lack of a
                 * connection permission for the lookup service codebase
                 * to the service's remote codebase. Skip this ServiceItem,
                 * otherwise an un-expected serviceRemoved event will result
                 * because the primary if-block will be unintentionally
                 * entered due to the null service field in the ServiceItem.
                 */
                if( (item != null) && (item.service == null) ) {
                    return;
                }//endif
                /* Handle the event by the transition type, and by whether 
                 * the associated ServiceItem is an old, previously discovered
                 * item, or a newly discovered item.
                 */
		if(transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) {
                    handleMatchNoMatch(reg.proxy, sid, item);
                } else {//(transition == NOMATCH_MATCH or MATCH_MATCH)
                    (new NewOldServiceTask(reg, item,
                       (transition == ServiceRegistrar.TRANSITION_MATCH_MATCH),
                                               thisTaskSeqN)).run();
                }//endif(transition)
                logger.finest("ServiceDiscoveryManager - NotifyEventTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.NotifyEventTask

        /** Task class used to determine whether or not to "commit" a service
         *  discard request, increasing the chances that the service will
         *  eventually be re-discovered. This task is also used to attempt
         *  a filter retry on an item in which the cache's filter initially
         *  returned indefinite.
         */
        private final class ServiceDiscardTimerTask implements TaskManager.Task
        {
            private ServiceID serviceID;
            public ServiceDiscardTimerTask(ServiceID serviceID) {
                this.serviceID = serviceID;
            }//end constructor
            public void run(){
                logger.finest("ServiceDiscoveryManager - "
                              +"ServiceDiscardTimerTask started");
                /* Exit if this cache has already been terminated. */
                synchronized(serviceIdMap) {
                    if(bCacheTerminated)  return;
                }//end sync(serviceIdMap)
                /* Simply return if a MATCH_NOMATCH event arrived for this
                 * item prior to this task running and as a result, the item
                 * was removed from the map.
                 */
                synchronized(serviceIdMap) {
                    if(!serviceIdMap.containsKey(serviceID))  return;
                }//end sync(serviceIdMap)
                /* Default to twice the (common) 5 minute max lease duration */
                long curDur = discardWait;
                long endTime = curDur+System.currentTimeMillis();
                synchronized(serviceDiscardMutex) {
                    /* Wait until interrupted or time expires */
                    while(curDur > 0) {
                        try {
                            serviceDiscardMutex.wait(curDur);
                        } catch(InterruptedException e){ }
                        /* Exit if this cache was terminated while waiting. */
                        synchronized(serviceIdMap) {
                            if(bCacheTerminated) return;
                        }//end sync(serviceIdMap)
                        /* Either the wait period has completed or has been
                         * interrupted. If the service ID is no longer in
                         * in the serviceIdMap, then it's assumed that a
                         * MATCH_NOMATCH event must have arrived which could be
                         * viewed as an indication that the service's lease
                         * expired, which then could be interpreted as meaning
                         * the service is actually down, and will be
                         * re-discovered when it comes back on line. In that
                         * case, exit the thread.
                         */
                        synchronized(serviceIdMap) {
                            if(!serviceIdMap.containsKey(serviceID))  return;
                        }//end sync(serviceIdMap)
                        curDur = endTime-System.currentTimeMillis();
                    }//end loop
                }//end sync
                /* The thread was not interrupted, time expired.
                 * 
                 * If the service ID is still contained in the serviceIdMap,
                 * then a MATCH_NOMATCH event did not arrive, which is
                 * interpretted here to mean that the service is still up.
                 * The service ID will still be in the map if one (or both)
                 * of the following is true:
                 *  - the client discarded an unreachable service that never
                 *    actually went down (so it's lease never expired, and
                 *    a MATCH_NOMATCH event was never received)
                 *  - upon applying the cache's filter to the service, the
                 *    filter returned indefinite, and this task was queued
                 *    to request that filtering be retried at a later time
                 *
                 * For the first case above, the service is "un-discarded" so
                 * the service will be available to the client again. For the
                 * second case, the filter is retried. If the service passes
                 * the filter, the service is "un-discarded"; otherwise, it is
                 * 'quietly' removed from the map (because a service removed
                 * event was already sent whent the service was orginally
                 * discarded.
                 */
                ServiceItemReg itemReg = null;
                synchronized(serviceIdMap) {
                    itemReg = (ServiceItemReg)serviceIdMap.get(serviceID);
                }//end sync(serviceIdMap)
                if(itemReg != null) {
                    ServiceItem item = null;
                    ServiceItem filteredItem = null;
                    synchronized(itemReg) {
                        if(itemReg.filteredItem == null) {
                            item = new ServiceItem
                                              ( (itemReg.item).serviceID,
                                                (itemReg.item).service,
                                                (itemReg.item).attributeSets);
                            filteredItem = new ServiceItem
                                              ( (itemReg.item).serviceID,
                                                (itemReg.item).service,
                                                (itemReg.item).attributeSets);
                        }//endif
                    }//end sync(itemReg)
                    if(filteredItem != null) {//retry the filter
                        if( filterPassFail(filteredItem,filter) ) {
                            addFilteredItemToMap(item,filteredItem);
                        } else {//'quietly' remove the item
                            removeServiceIdMapSendNoEvent(serviceID);
                            return;
                        }//endif
                    }//endif
                    /* Either the filter was retried and passed, in which case,
                     * the filtered itemCopy was placed in the map; or the
                     * filter wasn't applied above (a non-null filteredItem
                     * field in the itemReg in the map means that the filter
                     * was applied at some previous time). In either case, the
                     * service can now be "un-discarded", and a notification
                     * that the service is now available can be sent.
                     */
                    ServiceItem itemToSend;
                    synchronized(itemReg) {
                        itemReg.setDiscarded(false);
                        itemToSend = itemReg.filteredItem;
                    }//end sync(itemReg)
                    addServiceNotify(itemToSend);
                }//endif
                logger.finest("ServiceDiscoveryManager - "
                              +"ServiceDiscardTimerTask completed");
            }//end run
            /** Returns true if current instance must be run after task(s) in
             *  task manager queue.
             *  @param tasks the tasks to consider.
             *  @param size elements with index less than size are considered.
             */
            public boolean runAfter(List tasks, int size) {
                return false;
            }//end runAfter
        }//end class LookupCacheImpl.ServiceDiscardTimerTask

	/** Task class used to asynchronously process the service state
         *  ("snapshot"), matching this cache's template, that was retrieved 
         *  from the given lookup service.
         *
         *  After retrieving the snapshot S, the LookupTask queues an instance
         *  of this task for each service referenced in S. This task determines
         *  if the given service is an already-discovered service (is currently
         *  in this cache's serviceIdMap), or is a new service. This task 
         *  handles the service differently, depending on whether the service
         *  is a new or old. 
         *  
         *  a. if the item is old, then this task will:
         *     - compare the given item from the snapshot to the UN-filtered
         *       item in given itemReg
         *       if(same version but attributes have chaanged)
         *           send changed event
         *       else if( version has changed )
         *           send removed event followed by added event
         *       else
         *           do nothing
         *     - apply the filter to the given item
         *       if(filter fails) 
         *           send removed event
         *       else if(filter passes)
         *           set the filtered item in the itemReg in the map
         *       else if (filter is indefinite)
         *           discard item 
         *           send removed event
         *           queue another filter attempt for later
         *  b. if the given item is newly discovered, then this task will:
         *     - create a new ServiceItemReg containing the given item
         *     - place the new itemReg in the serviceIdMap
         *     - apply the filter to the given item
         *       if(filter fails) 
         *           remove the item from the map but
         *           send NO removed event
         *       else if(filter passes)
         *           send added event for the FILTERED item
         *       else if (filter is indefinite)
         *           discard item 
         *           queue another filter attempt for later but
         *           send NO removed event
         */
        private final class NewOldServiceTask extends ServiceIdTask {
            private ServiceItem srvcItem;
            private boolean matchMatchEvent;
            public NewOldServiceTask(ProxyReg reg,
                                     ServiceItem item,
                                     boolean matchMatchEvent,
                                     int seqN)
            {
                super(item.serviceID, reg, seqN);
                this.srvcItem = item;
                this.matchMatchEvent = matchMatchEvent;
            }//end constructor

            public void run() {
                logger.finest("ServiceDiscoveryManager - NewOldServiceTask "
                              +"started");
                ServiceItemReg itemReg;
                synchronized(serviceIdMap) {
                    itemReg = (ServiceItemReg)serviceIdMap.get(thisTaskSid);
                }//end sync(serviceIdMap)
                if(itemReg != null) {//a. old, previously discovered item
                    itemMatchMatchChange(reg.proxy, srvcItem,
                                         itemReg, matchMatchEvent);
                } else {//(itemReg == null) ==> b. newly discovered item
                    synchronized(serviceIdMap) {
                        if( !eventRegMap.containsKey(reg) ) {
                            /* reg must have been discarded, simply return */
                            logger.finest("ServiceDiscoveryManager - "
                                          +"NewOldServiceTask completed");
                            return;
                        }//endif
                        itemReg = new ServiceItemReg( reg.proxy, srvcItem );
                        serviceIdMap.put( thisTaskSid, itemReg );
                    }//end sync(serviceIdMap)
                    ServiceItem newFilteredItem =
                                  filterMaybeDiscard(srvcItem,reg.proxy,false);
                    if(newFilteredItem != null) {
                        addServiceNotify(newFilteredItem);
                    }//endif
                }//endif
                logger.finest("ServiceDiscoveryManager - NewOldServiceTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.NewOldServiceTask

	/** Task class used to asynchronously disassociate the given lookup
         *  service proxy from the given ServiceItemReg. This task is created
         *  and queued in the both the LookupTask, and the ProxyRegDropTask.
         *  
         *  When the LookupTask determines that the service referenced by the
         *  given ServiceItemReg is an "orphan", the LookupTask queues an
         *  instance of this task. A service is an orphan if it is referenced
         *  in the serviceIdMap, but is no longer registered in any of the
         *  lookup service(s) to which it is mapped in the serviceIdMap.
         *  Note that the existence of orphans is possible when events from
         *  a particular lookup service are missed; that is, there is a "gap"
         *  in the event sequence numbers.
         *  
         *  When a previously discovered lookup service is discarded, the
         *  ProxyRegDropTask is initiated, and that task creates and queues
         *  an instance of this task for each mapping in this cache's
         *  serviceIdMap.
         *  
         *  This task removes the given lookup service proxy from the set
         *  associated with the service item referenced in the given
         *  ServiceItemReg, and determines whether that service is still
         *  associated with at least one lookup service. If the service is
         *  no longer associated with any other lookup service in the managed
         *  set of lookup services, the mapping that references the given
         *  ServiceItemReg is removed from the serviceIdMap, and a 
         *  serviceRemoved event is sent.
         *
         *  In this way, other tasks from this cache operating on the same
         *  service will not concurrently modify any state related to that
         *  service.
         */
        private final class UnmapProxyTask extends ServiceIdTask {
            private ServiceItemReg itemReg;
            public UnmapProxyTask(ProxyReg       reg,
                                  ServiceItemReg itemReg,
                                  ServiceID      srvcId,
                                  int            seqN)
            {
                super(srvcId, reg, seqN);
                this.itemReg = itemReg;
            }//end constructor

            public void run() {
                logger.finest("ServiceDiscoveryManager - UnmapProxyTask "
                              +"started");

                ServiceItem item = null;
                synchronized(itemReg) {
                    itemReg.removeProxy(reg.proxy);//disassociate the LUS
                    if( itemReg.hasNoProxys() ) {//no more LUSs, remove map
                        item = itemReg.filteredItem;
                    }//endif
                }//end sync(itemReg)
                if(item != null) removeServiceIdMap(thisTaskSid,item);
                logger.finest("ServiceDiscoveryManager - UnmapProxyTask "
                              +"completed");
            }//end run
	}//end class LookupCacheImpl.UnmapProxyTask

	private final static int ITEM_ADDED   = 0;
	private final static int ITEM_REMOVED = 2;
	private final static int ITEM_CHANGED = 3;

	/* The listener that receives remote events from the lookup services */
        private LookupListener lookupListener;
	/* Exporter for the remote event listener (lookupListener) */
        private Exporter lookupListenerExporter;
	/* Proxy to the listener that receives remote events from lookups */
	private RemoteEventListener lookupListenerProxy;
        /** Task manager for the various tasks executed by this LookupCache */
        private TaskManager cacheTaskMgr;
	/* Flag that indicates if the LookupCache has been terminated. */
	private boolean bCacheTerminated = false;
	/* Contains the ServiceDiscoveryListener's that receive local events */
	private final ArrayList sItemListeners = new ArrayList(1);
	/* Map from ServiceID to ServiceItemReg */
	private final HashMap serviceIdMap = new HashMap();
	/* Map from ProxyReg to EventReg: (proxyReg, {lease,seqNo}) */
	private final HashMap eventRegMap = new HashMap();
	/* Template current cache instance should use for primary matching */
	private ServiceTemplate tmpl;
	/* Filter current cache instance should use for secondary matching */
	private ServiceItemFilter filter = null;
	/* Desired lease duration to request from lookups' event mechanisms */
	private long leaseDuration;
	/* Log the time when the cache gets created. This value is used to
         * calculate the time when the cache should expire.
	 */
	private final long startTime = System.currentTimeMillis();
        /** For tasks waiting on verification events after service discard */
        private TaskManager serviceDiscardTimerTaskMgr;
        /* Thread mutex used to interrupt all ServiceDiscardTimerTasks */
        private Object serviceDiscardMutex = new Object();
        /** Whenever a ServiceIdTask is created in this cache, it is assigned
         *  a unique sequence number to allow such tasks associated with the
         *  same ServiceID to be executed in the order in which they were
         *  queued in the TaskManager. This field contains the value of
         *  the sequence number assigned to the most recently created
         *  ServiceIdTask.
         */
        private int taskSeqN = 0;

	public LookupCacheImpl(ServiceTemplate tmpl, 
			       ServiceItemFilter filter,
			       ServiceDiscoveryListener sListener,
			       long leaseDuration)     throws RemoteException
        {
	    this.tmpl = copyServiceTemplate(tmpl);
	    this.leaseDuration = leaseDuration;
	    this.filter = filter;
            initCache();
            lookupListener = new LookupListener();
	    synchronized(sItemListeners) {
		if(sListener != null ) sItemListeners.add(sListener);
	    }//end sync(sItemListeners)
            ArrayList set;
            synchronized(proxyRegSet) {
                set = (ArrayList)proxyRegSet.clone();
            }//end sync(proxyRegSet)
	    for(int i=0; i<set.size(); i++) {
		ProxyReg reg = (ProxyReg)set.get(i);
                addProxyReg(reg);
	    }//end loop
	}//end constructor

	// This method's javadoc is inherited from an interface of this class
	public void terminate() {
	    synchronized(serviceIdMap) {
                if(bCacheTerminated) return;//allow for multiple terminations
                bCacheTerminated = true;
            }//end sync
	    synchronized(caches) {
		int index = caches.indexOf(this);
		if(index != -1) caches.remove(index);
	    }//end sync
            /* Terminate all tasks: first, terminate this cache's TaskManager*/
            terminateTaskMgr(cacheTaskMgr);
            /* Terminate ServiceDiscardTimerTasks running for this cache */
            synchronized(serviceDiscardMutex) {
                terminateTaskMgr(serviceDiscardTimerTaskMgr);
            }//end sync(serviceDiscardMutex)
            /* Cancel all event registration leases held by this cache. */
	    synchronized(serviceIdMap) {
		Set set = eventRegMap.entrySet();
		Iterator iter = set.iterator();
		while(iter.hasNext()) {
		    Map.Entry e = (Map.Entry)iter.next();
		    EventReg eReg = (EventReg)e.getValue();
		    cancelLease(eReg.lease);
		}//end loop
	    }//end sync(serviceIdMap)
            /* Un-export the remote listener for events from lookups. */
	    try {
                lookupListenerExporter.unexport(true);
	    } catch(IllegalStateException e) { 
                logger.log(Level.FINEST,
                           "IllegalStateException occurred while unexporting "
                           +"the cache's remote event listener",
                           e);
            }
            logger.finest("ServiceDiscoveryManager - LookupCache terminated");
	}//end LookupCacheImpl.terminate

	// This method's javadoc is inherited from an interface of this class
	public ServiceItem lookup(ServiceItemFilter myFilter) {
	    checkCacheTerminated();
	    ServiceItem[] ret = getServiceItems(myFilter);
	    if (ret.length == 0 )  return null;
	    int rand = Math.abs(random.nextInt()) % ret.length;
	    return ret[rand];
	}//end LookupCacheImpl.lookup

	// This method's javadoc is inherited from an interface of this class
	public ServiceItem[] lookup(ServiceItemFilter myFilter,int maxMatches){
	    checkCacheTerminated();
	    if (maxMatches < 1)
		throw new IllegalArgumentException("maxMatches must be > 0");
	    ArrayList items = new ArrayList(1);
	    ServiceItem[] sa = getServiceItems(myFilter);
	    int len = sa.length;
	    if (len == 0 )  return new ServiceItem[0];
	    int rand = Math.abs(random.nextInt()) % len;
	    for(int i=0; i<len; i++) {
		items.add(sa[(i+rand) % len ]);
		if(items.size() == maxMatches)
		    break;
	    }//end loop
	    ServiceItem[] ret = new ServiceItem[items.size()];
	    items.toArray(ret);
	    return ret;
	}//end LookupCacheImpl.lookup

	// This method's javadoc is inherited from an interface of this class
	public void discard(Object serviceReference) {
	    checkCacheTerminated();
            /* Loop through the serviceIdMap, looking for the itemReg that
             * corresponds to given serviceReference. If such an itemReg
             * exists, and it's not already discarded, then queue a task
             * to discard the given serviceReference.
             */
            boolean discardIt = false;
	    Iterator iter = getServiceIdMapEntrySetIterator();
	    while(iter.hasNext()) {
		Map.Entry e = (Map.Entry)iter.next();
		ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
		synchronized(itemReg) {
                    if((itemReg.filteredItem.service).equals(serviceReference))
                    {
                        if( itemReg.isDiscarded() ) return;//already discarded
                        itemReg.setDiscarded(true);
                        discardIt = true;
                        break;
                    }//endif
		}//end sync(itemReg)
	    }//end loop
            if(discardIt) {
                CacheTask t = new DiscardServiceTask(serviceReference);
                cacheTaskMgr.add(t);
            }//endif
	}//end LookupCacheImpl.discard

	/* Returns the iterator of entry set in the copy of the ServiceIdMap */
	private Iterator getServiceIdMapEntrySetIterator() {
	    HashMap serviceIdMapCopy;
	    synchronized(serviceIdMap) {
		serviceIdMapCopy = (HashMap)serviceIdMap.clone();
	    }
	    Set set = serviceIdMapCopy.entrySet();
	    return set.iterator();
	}//end LookupCacheImpl.getServiceIdMapEntrySetIterator

        /** This method returns a <code>ServiceItem</code> array containing
         *  elements that satisfy the following conditions:
         *   - is referenced by one of the <code>itemReg</code> elements
         *     contained in the <code>serviceIdMap</code>
         *   - is not currently discarded
         *   - satisfies the given <code>ServiceItemFilter</code>
         *    
         *  Note that the <code>filter</code> parameter is a "2nd stage"
         *  filter. That is, for each <code>itemReg</code> element in the
         *  <code>serviceIdMap</code>, the "1st stage" filter corresponding
         *  to the current instance of <code>LookupCache</code> has already
         *  been applied to the <code>ServiceItem</code> referenced in
         *  that <code>itemReg</code>. The <code>ServiceItemFilter</code>
         *  applied here is supplied by the entity interacting with the cache,
         *  and provides a second filtering process. Thus, this method
         *  applies the given <code>filter</code> parameter to the 
         *  <code>filteredItem</code> field (not the <code>item</code> field)
         *  of each non-discarded <code>itemReg</code> element in the
         *  <code>serviceIdMap</code>.
         * 
         *  This method returns all the instances of <code>ServiceItem</code>
         *  that pass the given <code>filter</code>; and it discards all the
         *  items that produce an indefinite result when that
         *  <code>filter</code> is applied. 
         */
	private ServiceItem[] getServiceItems(ServiceItemFilter filter2) {
	    ArrayList items = new ArrayList(1);
	    Iterator iter = getServiceIdMapEntrySetIterator();
	    while(iter.hasNext()) {
		Map.Entry e = (Map.Entry)iter.next();
		ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
		ServiceItem itemToFilter;
		ServiceItem itemToDiscard;
		synchronized(itemReg) {
                    if(    (itemReg.isDiscarded())
                        || (itemReg.filteredItem == null) ) continue;
                    /* Make a copy because the filter may change it to null */
		    itemToFilter = new ServiceItem
                                      ( (itemReg.filteredItem).serviceID,
                                        (itemReg.filteredItem).service,
                                        (itemReg.filteredItem).attributeSets );
                }//end sync(itemReg)
                Object serviceToDiscard = itemToFilter.service;
                /* Apply the filter */
                boolean pass = (    (filter2 == null)
                                 || (filter2.check(itemToFilter)) );
                /* Handle filter fail - skip to next item */
                if( !pass )  continue;
                /* Handle filter pass - add item to return set */
                if(itemToFilter.service != null) {
                    items.add(itemToFilter);
                    continue;
                }//endif(pass)
                /* Handle filter indefinite - discard the item */
                discard(serviceToDiscard);
	    }//end loop
	    ServiceItem[] ret = new ServiceItem[items.size()];
	    items.toArray(ret);
	    return ret;
	}//end LookupCacheImpl.getServiceItems

	// This method's javadoc is inherited from an interface of this class
	public void addListener(ServiceDiscoveryListener listener){
	    checkCacheTerminated();
            if(listener == null) {
                throw new NullPointerException("can't add null listener");
            }
	    synchronized(sItemListeners) {
		sItemListeners.add(listener);
	    }
	    ServiceItem[] items = getServiceItems(null);
	    for(int i=0; i<items.length; i++) {
                addServiceNotify(items[i],listener);
            }//end loop
	}//end LookupCacheImpl.addListener
	
	// This method's javadoc is inherited from an interface of this class
	public void removeListener(ServiceDiscoveryListener listener){
	    checkCacheTerminated();
	    if( listener == null) return;
	    synchronized(sItemListeners) {
		int index = sItemListeners.indexOf(listener);
		if(index != -1)  sItemListeners.remove(listener);
	    }
	}//end LookupCacheImpl.removeListener

	/** Add a new ProxyReg to the lookupCache. Called by the constructor
	 *  and the DiscMgrListener's discovered() method.
	 *  @param reg a ProxyReg to add.
	 */
	public void addProxyReg(ProxyReg reg) {
	    RegisterListenerTask treg = new RegisterListenerTask(reg);
            cacheTaskMgr.add(treg);
	}//end LookupCacheImpl.addProxyReg

	/** Remove a ProxyReg from the lookupCache. Called by DiscMgrListener's
	 *  discarded() method.
	 *  @param reg a ProxyReg to remove.
	 */
	public void removeProxyReg(ProxyReg reg) {
	    EventReg eReg;
	    synchronized(serviceIdMap) {
		//let the ProxyRegDropTask do the eventRegMap.remove
		eReg = (EventReg)eventRegMap.get(reg);
	    }//end sync(serviceIdMap)
	    if(eReg != null) cancelLease(eReg.lease);
	    ProxyRegDropTask t = new ProxyRegDropTask(reg);
	    removeUselessTask(reg);
            cacheTaskMgr.add(t);
	}//end LookupCacheImpl.removeProxyReg
	
	/* Throws IllegalStateException if this lookup cache has been
         * terminated
         */
	private void checkCacheTerminated() {
	    checkTerminated();
	    synchronized(serviceIdMap) {
		if(bCacheTerminated) {
		    throw new IllegalStateException
                                        ("this lookup cache was terminated");
                }//endif
            }//end sync(serviceIdMap)
	}//end LookupCacheImpl.checkCacheTerminated

	/** Called by the lookupListener's notify() method. Checks the event
         *  sequence number and, based on whether or not a "gap" is found in
         *  in the event sequence, creates and places on the queue, either a
         *  LookupTask (if a gap was found) or a NotifyTask.
         * 
         *  Recall that the Event specification states that if the sequence
         *  numbers of two successive events differ by only 1, then one can
         *  be assured that no events were missed. On the other hand, if
         *  the difference is greater than 1 (the sequence contains a "gap"),
         *  then one or more events may -- or may not -- have been missed.
         *  Thus, if a gap is found in the events, although it's possible that
         *  no events were missed, this method takes the conservative approach
         *  by assuming events were missed. When this method determines that
         *  an event may have been missed, it requests a current "snapshot"
         *  of the given ServiceRegistrar's state by queueing the execution
         *  of a LookupTask. Since this method can safely assume that no
         *  events have been missed if it finds no gaps in the event sequence,
         *  this method queues the notification of the entity by requesting
         *  the execution of a NotifyEventTask.
         *  
         *  Note that when a lookup service is discovered, this utility
         *  registers with that lookup service's event mechanism for service
         *  events related to the services of interest. Upon registering with
         *  the event mechanism, a data structure (of type EventReg) 
         *  containing information about that registration is placed in a
         *  Map for later processing when events do arrive. If the timing is
         *  right, it is possible that a service event may arrive between the
         *  time the registration is made and the time the EventReg is stored
         *  in the map. Thus, this method may find that the eventRegMap does
         *  not contain an element corresponding to the event this method is
         *  currently processing. In that case, this method will do nothing.
         *  It will simply return so that the service referenced in the event
         *  can be discovered using the snapshot returned by the LookupTask
         *  that is ultimately queued by the RegisterListenerTask (whose
         *  listener registration caused this method to be invoked in the
         *  first place).
	 */
	private void notifyServiceMap(ServiceRegistrar proxy, 
				      ServiceID sid,
				      ServiceItem item,
				      int transition,
				      long seqNo)
        {
            ProxyReg reg;
            synchronized(proxyRegSet) {
	        reg = findReg(proxy);
	    }//end sync
            if(reg == null) return;
            /* Look for gaps in the event sequence. */
            long prevSeqNo;
            synchronized(serviceIdMap) {
                EventReg eReg = (EventReg)eventRegMap.get(reg);
                if(eReg == null) return;//event arrived before eventReg in map
                prevSeqNo  = eReg.seqNo;
                eReg.seqNo = seqNo;
                CacheTask t;
                if(seqNo == (prevSeqNo+1)) {//no gap, handle current event
                    t = new NotifyEventTask
                                    (reg, sid, item, transition, taskSeqN++);
                } else {//gap in event sequence, request snapshot
                    t = new LookupTask(reg);
                }//endif
                cacheTaskMgr.add(t);
            }//end sync(serviceIdMap)
	}//end LookupCacheImpl.notifyServiceMap

	/** Removes from the cache's task manager, all pending tasks
         *  associated with the given ProxyReg. This method is called
         *  when the given ProxyReg has been discarded.
	 */
	private void removeUselessTask(ProxyReg reg) {
            ArrayList pendingTasks = cacheTaskMgr.getPending();
            for(int i=0;i<pendingTasks.size();i++) {
                CacheTask t = (CacheTask)pendingTasks.get(i);
                if(t.isFromProxy(reg)) cacheTaskMgr.remove(t);
            }//end loop
	}//end LookupCacheImpl.removeUselessTask

        /** For the given TaskManager, this method removes all pending and
         *  active tasks.
         */ 
        private void terminateTaskMgr(TaskManager taskMgr) {
            synchronized(taskMgr) {
                /* Remove all pending tasks */
                ArrayList pendingTasks = taskMgr.getPending();
                for(int i=0;i<pendingTasks.size();i++) {
                    taskMgr.remove((TaskManager.Task)pendingTasks.get(i));
                }//end loop
                /* Interrupt all active tasks, prepare the taskMgr for GC. */
                taskMgr.terminate();
                taskMgr = null;
            }//end sync(taskMgr)
        }//end LookupCacheImpl.terminateTaskMgr

	/** Removes an entry from the serviceIdMap, and sends a notification.*/
	private void removeServiceIdMap(ServiceID sid, ServiceItem item) {
            removeServiceIdMapSendNoEvent(sid);
	    removeServiceNotify(item);
	}//end LookupCacheImpl.removeServiceIdMap

	/** Removes an entry in the serviceIdMap, but sends no notification. */
	private void removeServiceIdMapSendNoEvent(ServiceID sid) {
	    synchronized(serviceIdMap) {
		serviceIdMap.remove(sid);
	    }
	}//end LookupCacheImpl.removeServiceIdMapSendNoEvent

	/** Returns the element in the given items array having the given
         *  ServiceID.
         */
	private ServiceItem findItem(ServiceID sid, ServiceItem[] items) {
	    if(items != null) {
		for(int i=0; i<items.length; i++) {
		    if(items[i].serviceID.equals(sid) ) return items[i];
		}//end loop
	    }//endif
	    return null;
	}//end LookupCacheImpl.findItem

	/** With respect to a given service (referenced by both the parameter
         *  newItem and the parameter itemReg), if either an event has been
         *  received from the given lookup service (referenced by the proxy
         *  parameter), or a snapshot of the given lookup service's state
         *  has been retrieved, this method determines whether the service's
         *  attributes have changed, or whether a new version of the service
         *  has been registered. After the appropriate determination has been
         *  made, this method applies the filter associated with the current
         *  cache and sends the appropriate local ServiceDiscoveryEvent(s).
         *
         * This method is called under the following conditions:
         *   - when a new lookup service is discovered, this method will
         *     be called for each previously discovered service
         *   - when a gap in the events from a previously discovered lookup
         *     service is discovered, this method will be called for each
         *     previously discovered service
         *   - when a MATCH_MATCH event is received, this method will
         *     be called for each previously discovered service
         *   - when a NOMATCH_MATCH event is received, this method will
         *     be called for each previously discovered service
         * Note that this method is never called when a MATCH_NOMATCH event
         * is received; such an event is always handled in NotifyEventTask.
         * 
         * When this method is called, it may send one of the following events
         * or combination of events:
         *  - a service changed event
         *  - a service removed event followed by a service added event
         *  - a service removed event
         *
         * A service removed event is sent when the service either fails the
         * filter, or the filter produces an indefinite result; in which
         * case, the service is also discarded.
         *
         * A service changed event is sent when the service passes the filter,
         * and it is determined that the service's attributes have changed. 
         * In this case, the old and new service proxies are treated as the
         * same if one of the following conditions is met:
         *  - this method was called because of the receipt of a
         *    MATCH_MATCH event
         *  - the old and new service proxies are byte-wise fully equal
         * (Note that the lookup service specification guarantees that the
         * proxies are the same when a MATCH_MATCH event is received.)
         *
         * A service removed event followed by a service added event is sent
         * when the service passes the filter, and the conditions for which
         * a service changed event would be considered are not met; that is,
         * this method was not called because of the receipt of a MATCH_MATCH
         * event; or the old and new service proxies are not byte-wise fully
         * equal.
         *
         *  The if-else-block contained in this method implements the logic
         *  just described. The parameter matchMatchEvent reflects the
         *  pertinent event state that causes this method to be called.
         *  That is, either a MATCH_MATCH event was received, or it wasn't,
         *  (and if it wasn't, then a full byte-wise comparison is performed
         *  to determine whether the proxies are still the same).
         *  
         *  To understand when the 'else' part of the if-else-block is
         *  executed, consider the following conditions:
         *   - there is more than one lookup service with which the service
         *     registers (ex. LUS-0 and LUS-1)
         *   - after the service registers with LUS-0, a NOMATCH_MATCH
         *     event is received and handled (so the service is now known
         *     to the cache)
         *   - before the service registers with LUS-1, the service is
         *     replaced with a new version
         *   - the NOMATCH_MATCH event resulting from the service's 
         *     registration with LUS-1 is received BEFORE receiving the 
         *     MATCH_NOMATCH/NOMATCH_MATCH event sequence that will 
         *     ultimately result from the re-registration of that new
         *     version with LUS-0
         *  When the above conditions occur, the NOMATCH_MATCH event that
         *  resulted from the service's registration with LUS-1 will cause
         *  this method to be invoked and the proxies to be fully compared
         *  (because the event was not a MATCH_MATCH event); and since the old
         *  service proxy and the new service proxy will not be fully equal,
         *  the else part of the if-else-block will be executed.
         *
         *  This method applies the filter only after the above comparisons
         *  and determinations have been completed.
         */ 
	private void itemMatchMatchChange(ServiceRegistrar proxy,
                                          ServiceItem newItem,
                                          ServiceItemReg itemReg,
                                          boolean matchMatchEvent )
        {
            /* Save the pre-event state. Update the post-event state after
             * applying the filter.
             */
	    ServiceItem oldItem;
	    ServiceItem oldFilteredItem;
	    synchronized(itemReg) {
                oldItem = itemReg.item;
                oldFilteredItem = itemReg.filteredItem;
		if(itemReg.isDiscarded()) {
                    itemReg.item = newItem;//capture changes for discard
                    itemReg.filteredItem = null;//so filter will be retried
                    return;
                }//endif
                itemReg.addProxy(proxy);
	    }//end sync(itemReg)
            /* For an explanation of the logic of the following if-else-block,
             * refer to the method description above.
             */
            boolean attrsChanged = false;
            boolean versionChanged = false;
	    if( matchMatchEvent || sameVersion(newItem,oldItem) ) {
                /* Same version, determine if the attributes have changed.
                 * But first, replace the new service proxy with the old
                 * service proxy so the client always uses the old proxy
                 * (at least, until the version is changed).
                 */
                newItem.service = oldItem.service;
                /* Now compare attributes */
                attrsChanged = !LookupAttributes.equal(newItem.attributeSets,
                                                       oldItem.attributeSets);
                if(!attrsChanged) return;//no change, no need to filter
            } else {//(!matchMatchEvent && !same version) ==> re-registration
                versionChanged = true;
            }//endif
            /* Now apply the filter, and send events if appropriate */
            ServiceItem newFilteredItem = filterMaybeDiscard
                                                       (newItem, proxy, true);
            if(newFilteredItem != null) {
                /* Passed the filter, okay to send event(s). */
                if(attrsChanged) changeServiceNotify(newFilteredItem,
                                                     oldFilteredItem);
                if(versionChanged) {
                    removeServiceNotify(oldFilteredItem);
                    addServiceNotify(newFilteredItem);
                }//endif
            }//endif
	}//end LookupCacheImpl.itemMatchMatchChange

	/** Convenience method that performs a byte-wise comparison, including
         *  codebases, of the services referenced by the given service items,
         *  and returns the result. If the services cannot be compared, it is
         *  assumed that the versions are not the same, and <code>false</code>
         *  is returned.
         */
	private boolean sameVersion(ServiceItem item0,ServiceItem item1) {
            boolean fullyEqual = false;
            try {
                MarshalledInstance mi0 = new MarshalledInstance(item0.service);
                MarshalledInstance mi1 = new MarshalledInstance(item1.service);
                fullyEqual = mi0.fullyEquals(mi1);
            } catch(IOException e) {
                logger.log(Level.INFO, "failure marshalling old and new "
                           +"services for equality check", e);
            }
            return fullyEqual;
	}//end LookupCacheImpl.sameVersion

	/** Gets the remaining time left on the current cache's "lifespan". */
	public long getLeaseDuration() {
	    if(leaseDuration == Long.MAX_VALUE)  return Long.MAX_VALUE;
	    return leaseDuration + startTime - System.currentTimeMillis();
	}//end LookupCacheImpl.getLeaseDuration

	/** Sends a notification to all listeners when a ServiceItem has
         *  been added.
         */
	private void addServiceNotify(ServiceItem item) {
	    serviceNotifyDo(null, item, ITEM_ADDED);
	} //end LookupCacheImpl.addServiceNotify

	/** Sends a notification to the given listener when a ServiceItem has
         *  been added.
         */
	private void addServiceNotify(ServiceItem item,
                                      ServiceDiscoveryListener srvcListener)
        {
	    serviceNotifyDo(null, item, ITEM_ADDED, srvcListener);
	} //end LookupCacheImpl.addServiceNotify

	/** Sends a notification when a ServiceItem has been removed. */
	private void removeServiceNotify(ServiceItem item) {
	    serviceNotifyDo(item, null, ITEM_REMOVED);
	}//end LookupCacheImpl.removeServiceNotify

	/** Sends a notification when a ServiceItem has been changed, but
         *  still matches.
         */
	private void changeServiceNotify(ServiceItem newItem,
                                         ServiceItem oldItem )
        {
	    serviceNotifyDo(oldItem, newItem, ITEM_CHANGED);
	}//end LookupCacheImpl.changeServiceNotify

	/** Common code for performing service notification to all listeners.*/
	private void serviceNotifyDo(ServiceItem oldItem,
				     ServiceItem item,
				     int action)
	{
	    ArrayList notifies;
	    synchronized(sItemListeners) {
		if(sItemListeners.isEmpty()) return;
	        notifies = (ArrayList)sItemListeners.clone();
	    }
	    Iterator iter = notifies.iterator();
	    while (iter.hasNext()) {
		ServiceDiscoveryListener sl
                                      = (ServiceDiscoveryListener)iter.next();
                serviceNotifyDo(oldItem,item,action,sl);
	    }//end loop
	}//end LookupCacheImpl.serviceNotifyDo

	/** Common code for performing service notification to one listener. */
	private void serviceNotifyDo(ServiceItem oldItem,
				     ServiceItem item,
				     int action,
                                     ServiceDiscoveryListener sl)
	{
	    ServiceDiscoveryEvent event = new ServiceDiscoveryEvent
                                                        (this, oldItem, item);
            switch(action) {
                case ITEM_ADDED:   sl.serviceAdded(event);break;
		case ITEM_REMOVED: sl.serviceRemoved(event);break;
	        case ITEM_CHANGED: sl.serviceChanged(event);break;
            }//end switch(action)
	}//end LookupCacheImpl.serviceNotifyDo

        private void initCache() throws RemoteException {
            /* Get the exporter for the remote event listener from the 
             * configuration.
             */
            try {
                Exporter defaultExporter =
                      new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                            new BasicILFactory(),
                                            false, false);
                lookupListenerExporter =
                  (Exporter)thisConfig.getEntry(COMPONENT_NAME,
                                                "eventListenerExporter",
                                                Exporter.class,
                                                defaultExporter );
            } catch(ConfigurationException e) {// exception, use default
                ExportException e1 = new ExportException
                                            ("Configuration exception while "
                                             +"retrieving exporter for "
                                             +"cache's remote event listener",
                                             e);
                throw e1;
            }
            /* Get a general-purpose task manager for this cache from the
             * configuration. This task manager will be used to manage the
             * various tasks executed by this instance of the lookup cache.
             */
            cacheTaskMgr = new TaskManager(10,(15*1000),1.0f);//default
            try {
                cacheTaskMgr = (TaskManager)thisConfig.getEntry
                                                           (COMPONENT_NAME,
                                                            "cacheTaskManager",
                                                            TaskManager.class,
                                                            cacheTaskMgr);
            } catch(ConfigurationException e) { /* use default */ }
            /* Get a special-purpose task manager for this cache from the
             * configuration. That task manager will be used to manage the
             * various instances of the special-purpose task, executed by
             * this instance of the lookup cache, that waits on verification
             * events after a previousy discovered service has been discarded.
             */
            serviceDiscardTimerTaskMgr = new TaskManager(10,(15*1000),1.0f);
            try {
                serviceDiscardTimerTaskMgr
                    = (TaskManager)thisConfig.getEntry
                                                  (COMPONENT_NAME,
                                                   "discardTaskManager",
                                                   TaskManager.class,
                                                   serviceDiscardTimerTaskMgr);
            } catch(ConfigurationException e) { /* use default */ }
        }//end LookupCacheImpl.initCache

	/** Applies the first-stage <code>filter</code> associated with
         *  the current instance of <code>LookupCache</code> to the given
         *  <code>item</code> and returns the resulting filtered item if
         *  the <code>filter</code> is passed (or is <code>null</code>);
         *  otherwise, returns <code>null</code> and sends a service removed
         *  event if the <code>sendEvent</code> parameter is <code>true</code>.
         *  <p>
         *  This method is called only when the <code>item</code> to be
         *  filtered corresponds to an element that currently exists in
         *  the <code>serviceIdMap</code>.
         *  <p>
         *  As described in the <code>ServiceItemFilter</code> specification,
         *  when the <code>item</code> passes the <code>filter</code>, the
         *  <code>service</code> field of the <code>item</code> is replaced
         *  with the filtered form of the object previously contained in
         *  that field. In this case, the <code>filteredItem</code> field
         *  of the corresponding <code>ServiceItemReg</code> element of the
         *  <code>serviceIdMap</code> is set to this new filtered item.
         *  <p>
         *  If the <code>filter</code> returns <code>indefinite</code>,
         *  then that specification states that the <code>service</code>
         *  field is replaced with <code>null</code>. In this case, the
         *  <code>filteredItem</code> field of the corresponding
         *  <code>ServiceItemReg</code> element of the
         *  <code>serviceIdMap</code> is left unchanged.
	 */
  	private ServiceItem filterMaybeDiscard(ServiceItem item,
                                               ServiceRegistrar proxy,
				               boolean sendEvent) 
        {
            if( (item == null) || (item.service == null) ) return null;
            if(filter == null) {
                addFilteredItemToMap(item,item);
                return item;
            }//endif
            /* Make a copy to filter because the filter may modify it. */
            ServiceItem filteredItem = new ServiceItem(item.serviceID,
                                                       item.service,
                                                       item.attributeSets);
            boolean pass = filter.check(filteredItem);
            /* Handle filter fail */
            if(!pass) {
                ServiceID srvcID = item.serviceID;
                ServiceItemReg itemReg = null;
                synchronized(serviceIdMap) {
                    itemReg = (ServiceItemReg)serviceIdMap.get(srvcID);
                }//end sync(serviceIdMap)
                if(itemReg != null) {
                    if(sendEvent) {
                        ServiceItem oldFilteredItem;
                        synchronized(itemReg) {
                            oldFilteredItem = itemReg.filteredItem;
                        }//end sycn(itemReg)
                        removeServiceIdMap(srvcID, oldFilteredItem);
                    } else {
                        removeServiceIdMapSendNoEvent(srvcID);
                    }//endif
                }//endif
                return null;
            }//endif(fail)
            /* Handle filter pass */
            if(filteredItem.service != null) {
                addFilteredItemToMap(item,filteredItem);
                return filteredItem;
            }//endif(pass)
            /* Handle filter indefinite */
            discardRetryLater(item, proxy, sendEvent);
            return null;
        }//end LookupCacheImpl.filterMaybeDiscard

	/** Convenience method called by <code>filterMaybeDiscard</code>
         *  and <code>ServiceDiscardTimerTask.run</code> that finds
         *  the <code>ServiceItemReg</code> element in the
         *  <code>serviceIdMap</code> that corresponds to the given
         *  <code>ServiceItem</code> and, if such an element is found,
         *  replaces the <code>item</code> field of that element with
         *  the given <code>item</code> parameter; and sets the
         *  <code>filteredItem</code> field of that element to the value
         *  contained in the <code>filteredItem</code> parameter.
	 */
  	private void addFilteredItemToMap(ServiceItem item,
                                          ServiceItem filteredItem)
       {
            ServiceItemReg itemReg = null;
            synchronized(serviceIdMap) {
                itemReg = (ServiceItemReg)serviceIdMap.get(item.serviceID);
            }//end sync(serviceIdMap)
            if(itemReg == null)  return;
            synchronized(itemReg) {
                itemReg.item = item;
                itemReg.filteredItem = filteredItem;
            }//end sync(itemReg)
        }//end LookupCacheImpl.addFilteredItemToMap

	/** Convenience method called by <code>filterMaybeDiscard</code>
         *  that finds in the <code>serviceIdMap</code>, the
         *  <code>ServiceItemReg</code> element corresponding to the
         *  given <code>ServiceItem</code>, sets a service removed event,
         *  and queues a <code>ServiceDiscardTimerTask</code> to retry the
         *  filter at a later time. If the <code>serviceIdMap</code> does not
         *  contain a <code>ServiceItemReg</code> corresponding to the
         *  given <code>ServiceItem</code>, then this method simply returns.
	 */
  	private void discardRetryLater(ServiceItem item, 
                                       ServiceRegistrar proxy,
                                       boolean sendEvent) {
            ServiceItemReg itemReg = null;
            synchronized(serviceIdMap) {
                itemReg = (ServiceItemReg)serviceIdMap.get(item.serviceID);
            }//end sync(serviceIdMap)
            if(itemReg == null) return;
            ServiceItem oldFilteredItem;
            synchronized(itemReg) {
                oldFilteredItem = itemReg.filteredItem;
                /* If there's been any change in what is being discarded for
                 * filter retry, then update the item field in the map to
                 * capture that change; and set the filteredItem field to
                 * to null to guarantee that the filter is re-applied to
                 * that changed item.
                 */
                if(itemReg.item != item) {
                    itemReg.item = item;
                    itemReg.filteredItem = null;
                }//endif
                itemReg.setDiscarded(true);
            }//end sync(itemReg)
            if(sendEvent)  removeServiceNotify(oldFilteredItem);
            serviceDiscardTimerTaskMgr.add
                              ( new ServiceDiscardTimerTask(item.serviceID) );
        }//end LookupCacheImpl.discardRetryLater

	/** Convenience method called by <code>NotifyEventTask.run</code> (only
         *  when a TRANSITION_MATCH_NOMATCH event is received) that removes
         *  the given <code>item</code> from the <code>serviceIdMap</code>
         *  and wakes up the <code>ServiceDiscardTimerTask</code> if the given
         *  <code>item</code> is discarded; otherwise, sends a removed event.
	 */
  	private void handleMatchNoMatch(ServiceRegistrar proxy,
                                        ServiceID srvcID,
                                        ServiceItem item)
        {
            ServiceItemReg itemReg = null;
            synchronized(serviceIdMap) {
                itemReg = (ServiceItemReg)serviceIdMap.get(srvcID);
            }//end sync(serviceIdMap)
            if(itemReg != null) {
                boolean itemRegHasNoProxys;
                boolean itemRegIsDiscarded;
                ServiceItem filteredItem;
                synchronized(itemReg) {
                    itemReg.removeProxy(proxy);
                    itemRegHasNoProxys = itemReg.hasNoProxys();
                    itemRegIsDiscarded = itemReg.isDiscarded();
                    filteredItem = itemReg.filteredItem;
                }//end sync(itemReg)
                if(itemRegHasNoProxys) {
                    if(itemRegIsDiscarded) {
                        /* Remove item from map and wake up the discard task */
                        removeServiceIdMapSendNoEvent(srvcID);
                        synchronized(serviceDiscardMutex) {
                            serviceDiscardMutex.notifyAll();
                        }//end sync
                    } else {//remove item from map and send removed event
                        removeServiceIdMap(srvcID, filteredItem);
                    }//endif
                }//endif
            }//endif
        }//end LookupCacheImpl.handleMatchNoMatch

    }//end class ServiceDiscoveryManager.LookupCacheImpl
    
    /* Name of this component; used in config entry retrieval and the logger.*/
    private static final String COMPONENT_NAME
                                   = "net.jini.lookup.ServiceDiscoveryManager";
    /* Logger used by this utility. */
    private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
    /* The discovery manager to use (passed in, or create one). */
    private DiscoveryManagement discMgr;
    /* Indicates whether the discovery manager was created internally or not */
    private boolean discMgrInternal = false;
    /* The listener added to discMgr that receives DiscoveryEvents */
    private DiscMgrListener discMgrListener = new DiscMgrListener();
    /* The LeaseRenewalManager to use (passed in, or create one). */
    private LeaseRenewalManager leaseRenewalMgr;
    /* Contains all of the discovered lookup services (ServiceRegistrar). */
    private final ArrayList proxyRegSet = new ArrayList(1);
    /* Contains all of the DiscoveryListener's employed in lookup discovery. */
    private final ArrayList listeners = new ArrayList(1);
    /* Random number generator for use in lookup. */
    private final Random random = new Random();
    /* Contains all of the instances of LookupCache that are requested. */
    private final ArrayList caches = new ArrayList(1);

    /* Flag to indicate if the ServiceDiscoveryManager has been terminated. */
    private boolean bTerminated = false;
    /* Object used to obtain the configuration items for this utility. */
    private Configuration thisConfig;
    /* Preparer for the proxies to the lookup services that are discovered
     * and used by this utility.
     */
    private ProxyPreparer registrarPreparer;
    /* Preparer for the proxies to the leases returned to this utility when
     * it registers with the event mechanism of any of the discovered lookup
     * services.
     */
    private ProxyPreparer eventLeasePreparer;
    /* Wait value used when handling the "service discard problem". */
    private long discardWait = 2*(5*60*1000);

    /* Listener class for lookup service discovery notification. */
    private class DiscMgrListener implements DiscoveryListener {
	/* New or previously discarded proxy has been discovered. */
	public void discovered(DiscoveryEvent e) {
	    ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
	    ArrayList newProxys = new ArrayList(1);
	    ArrayList notifies  = null;
	    for(int i=0; i<proxys.length; i++) {
                /* Prepare each lookup service proxy before using it. */
                try {
                    proxys[i]
                          = (ServiceRegistrar)registrarPreparer.prepareProxy
                                                                   (proxys[i]);
                    logger.log(Level.FINEST, "ServiceDiscoveryManager - "
                              +"discovered lookup service proxy prepared: {0}",
                               proxys[i]);
                } catch(Exception e1) {
                    logger.log(Level.INFO,
                               "failure preparing discovered ServiceRegistrar "
                               +"proxy, discarding the proxy",
                               e1);
                    discard(proxys[i]);
                    continue;
                }
		ProxyReg reg = new ProxyReg(proxys[i]);
		synchronized(proxyRegSet) {
		    proxyRegSet.add(reg);
		    newProxys.add(reg);
		}//end sync(proxyRegSet)		
	    }//end loop
	    synchronized(listeners) {
		if(!listeners.isEmpty())
		    notifies = (ArrayList)listeners.clone();
	    }//end sync(listeners)
	    Iterator iter = newProxys.iterator();
	    while(iter.hasNext()) {
		ProxyReg reg = (ProxyReg)iter.next();
		cacheAddProxy(reg);
		if(notifies != null)  listenerDiscovered(reg.proxy, notifies);
	    }//end loop
	}//end DiscMgrListener.discovered

	/* Previously discovered proxy has been discarded. */
	public void discarded(DiscoveryEvent e) {
	    ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
	    ArrayList notifies;
	    ArrayList drops = new ArrayList(1);
	    synchronized(proxyRegSet) {
		for(int i=0; i<proxys.length; i++) {
		    ProxyReg reg = findReg(proxys[i]);
		    if(reg != null ) { // this check can be removed.
			proxyRegSet.remove(proxyRegSet.indexOf(reg));
			drops.add(reg);
		    } else {
			throw new RuntimeException("discard error");
                    }//endif
		}//end loop
	    }//end sync(proxyRegSet)
	    Iterator iter = drops.iterator();
	    while(iter.hasNext()) {
		dropProxy((ProxyReg)iter.next());
            }//end loop
	    synchronized(listeners) {
		if(listeners.isEmpty()) return;
		notifies = (ArrayList)listeners.clone();
	    }//end sync(listeners)
	    listenerDropped(drops, notifies);
	}//end DiscMgrListener.discarded

    }//end class ServiceDiscoveryManager.DiscMgrListener

    /** Adds the given proxy to all the caches maintained by the SDM. */
    private void cacheAddProxy(ProxyReg reg) {
	synchronized(caches) {
	    Iterator iter = caches.iterator();
	    while (iter.hasNext()) {
		LookupCacheImpl cache = (LookupCacheImpl)iter.next();
		cache.addProxyReg(reg);
	    }//end loop
	}
    }//end cacheAddProxy
    
    /** Removes the given proxy from all the caches maintained by the SDM. */
    private void dropProxy(ProxyReg reg ) {
	synchronized(caches) {
	    Iterator iter = caches.iterator();
	    while (iter.hasNext()) {
		LookupCacheImpl cache= (LookupCacheImpl)iter.next();
		cache.removeProxyReg(reg);
	    }//end loop
	}
    }//end dropProxy

    /**
     * Constructs an instance of <code>ServiceDiscoveryManager</code> which
     * will, on behalf of the entity that constructs this class, discover and
     * manage a set of lookup services, as well as discover and manage sets
     * of services registered with those lookup services. The entity indicates
     * which lookup services to discover and manage through the parameters
     * input to this constructor.
     * <p>
     * As stated in the class description, this class has three usage patterns:
     * <p>
     * <ul>
     *   <li> the entity uses a {@link net.jini.lookup.LookupCache
     *        LookupCache} to locally store and manage discovered services
     *        so that those services can be accessed quickly
     *   <li> the entity registers with the event mechanism provided by a 
     *        {@link net.jini.lookup.LookupCache LookupCache} to be notified
     *        when services of interest are discovered
     *   <li> the entity uses the <code>ServiceDiscoveryManager</code> to
     *        perform remote queries of the lookup services, employing richer 
     *        semantics than that provided through the standard
     *        {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
     *        interface
     * </ul>
     * <p>
     * Although the first two usage patterns emphasize the use of a cache 
     * object, that cache is acquired only through an instance of the
     * <code>ServiceDiscoveryManager</code> class.
     * <p>
     * It is important to note that some of the methods of this class 
     * ({@link net.jini.lookup.ServiceDiscoveryManager#createLookupCache
     * createLookupCache} and the <i>blocking</i> versions of
     * {@link net.jini.lookup.ServiceDiscoveryManager#lookup lookup} to
     * be exact) can throw a {@link java.rmi.RemoteException} when invoked.
     * This is because each of these methods may attempt to register with
     * the event mechanism of at least one lookup service, a process that
     * requires a remote object (a listener) to be exported to the lookup
     * service(s). Both the process of registering with a lookup service's
     * event mechanism and the process of exporting a remote object are
     * processes that can result in a {@link java.rmi.RemoteException}. 
     * <p>
     * In order to facilitate the exportation of the remote listener
     * just described, the <code>ServiceDiscoveryManager</code> class
     * instantiates an inner class that implements the 
     * {@link net.jini.core.event.RemoteEventListener RemoteEventListener}
     * interface. Although this class defines, instantiates, and exports this
     * remote listener, <i>it is the entity's responsibility</i> to provide a
     * mechanism for any lookup service to acquire the proxy to the exported
     * listener. One way to do this is to configure this utility to export
     * the listener using the Jini(TM) Extensible Remote Invocation (Jini ERI)
     * communication framework. When the listener is exported to use Jini ERI,
     * and no proxy customizations (such as a custom invocation handler or
     * transport endpoint) are used, no other action is necessary to make the
     * proxy to the listener available to the lookup service(s) with which
     * that listener is registered.
     * <p>
     * The <a href="#eventListenerExporter">default exporter</a> for this
     * utility will export the remote event listener under Jini ERI,
     * specifying that the port and object ID with which the listener is
     * to be exported should be chosen by the Jini ERI framework, not the
     * deployer.
     * <p>
     * If it is required that the remote event listener be exported under
     * JRMP instead of Jini ERI, then the entity that employs this utility
     * must specify this in its configuration. For example, the entity's 
     * configuration would need to contain something like the following:
     * <p>
     * <blockquote>
     * <pre>
     * import net.jini.jrmp.JrmpExporter;
     * 
     * application.configuration.component.name {
     *    .......
     *    .......
     *    // configuration items specific to the application
     *    .......
     *    .......
     * }//end application.configuration.component.name
     * 
     * net.jini.lookup.ServiceDiscoveryManager {
     * 
     *    serverExporter = new JrmpExporter();
     *
     * }//end net.jini.lookup.ServiceDiscoveryManager
     * </pre>
     * </blockquote>
     * <p>
     * It is important to note that when the remote event listener is exported
     * under JRMP, unlike Jini ERI, the JRMP remote communication framework
     * does <b><i>not</i></b> provide a mechanism that automatically makes
     * the listener proxy available to the lookup service(s) with which the
     * listener is registered; the deployer of the entity, or the entity
     * itself, must provide such a mechanism.
     * <p>
     * When exported under JRMP, one of the more common mechanisms for making
     * the listener proxy available to the lookup service(s) with which the
     * listener is registered consists of the following:
     * <p>
     * <ul><li> store the necessary class files in a JAR file
     *     <li> make the class files in the JAR file <i>preferred</i>
     *          (see {@link net.jini.loader.pref} for details)
     *     <li> run an HTTP server to serve up the JAR file to any requesting
     *          lookup service
     *     <li> advertise the location of that JAR file by setting the
     *          <code>java.rmi.server.codebase</code> property of the entity
     *          to "point" at the JAR file
     * </ul>
     * <p>
     * For example, suppose an application consists of an entity that intends
     * to use the <code>ServiceDiscoveryManager</code> will run on a host named
     * <b><i>myHost</i></b>. And suppose that the <i>down-loadable</i> JAR
     * file named <b><i>sdm-dl.jar</i></b> that is provided in the
     * distribution is located in the directory <b><i>/files/jini/lib</i></b>,
     * and will be served by an HTTP server listening on port
     * <b><i>8082</i></b>. If the application is run with its codebase
     * property set to 
     * <code>-Djava.rmi.server.codebase="http://myHost:8082/sdm-dl.jar"</code>,
     * the lookup service(s) should then be able to access the remote listener
     * exported under JRMP by the <code>ServiceDiscoveryManager</code> on
     * behalf of the entity.
     * <p>
     * If a mechanism for lookup services to access the remote listener
     * exported by the <code>ServiceDiscoveryManager</code> is not provided
     * (either by the remote communication framework itself, or by some other
     * means), the remote methods of the <code>ServiceDiscoveryManager</code>
     * - the methods involved in the two most important usage patterns of
     * that utility - will be of no use.
     * <p>
     * This constructor takes two arguments: an object that implements the
     * <code>DiscoveryManagement</code> interface and a reference to a
     * <code>LeaseRenewalManager</code> object. The constructor throws an
     * <code>IOException</code> because construction of a
     * <code>ServiceDiscoveryManager</code> may initiate the multicast
     * discovery process, a process that can throw an
     * <code>IOException</code>.
     *
     * @param discoveryMgr the <code>DiscoveryManagement</code> 
     *  		implementation through which notifications 
     *  		that indicate a lookup service has been 
     *  		discovered or discarded will be received.
     *  		If the value of the argument is <code>null</code>,
     *                  then an instance of the
     *  		<code>LookupDiscoveryManager</code> utility
     *  		class will be constructed to listen for events
     *  		announcing the discovery of only those lookup
     *  		services that are members of the public group.
     * 
     * @param leaseMgr the <code>LeaseRenewalManager</code> to use. A 
     *  		value of <code>null</code> may be passed as the
     *  		<code>LeaseRenewalManager</code> argument. If
     *  		the value of the argument is <code>null</code>,
     *  		an instance of the
     *  		<code>LeaseRenewalManager</code> class will be
     *  		created, initially managing no
     *  		<code>Lease</code> objects.
     * 
     * @throws IOException because construction of a  
     *  		<code>ServiceDiscoveryManager</code> may initiate
     *  		the multicast discovery process which can throw
     *  		an <code>IOException</code>.
     *
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.core.event.RemoteEventListener
     * @see net.jini.core.lookup.ServiceRegistrar
     */
    public ServiceDiscoveryManager(DiscoveryManagement discoveryMgr,
                                   LeaseRenewalManager leaseMgr)
                                                            throws IOException
    {
        try {
            init(discoveryMgr, leaseMgr, EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs an instance of this class, which is configured using the
     * items retrieved through the given <code>Configuration</code>, that
     * will, on behalf of the entity that constructs this class, discover and
     * manage a set of lookup services, as well as discover and manage sets
     * of services registered with those lookup services. Through the
     * parameters input to this constructor, the client of this utility
     * indicates which lookup services to discover and manage, and how it
     * wants the utility additionally configured.
     * <p>
     * For a more details, refer to the description of the alternate
     * constructor of this class.
     * <p>
     * This constructor takes three arguments: an object that implements the
     * <code>DiscoveryManagement</code> interface, a reference to an instance
     * of the <code>LeaseRenewalManager</code> class, and a 
     * <code>Configuration</code> object. The constructor throws an
     * <code>IOException</code> because construction of a
     * <code>ServiceDiscoveryManager</code> may initiate the multicast
     * discovery process, a process that can throw an
     * <code>IOException</code>. The constructor also throws a
     * <code>ConfigurationException</code> when an exception occurs while
     * retrieving an item from the given <code>Configuration</code>
     *
     * @param discoveryMgr the <code>DiscoveryManagement</code> 
     *  		implementation through which notifications 
     *  		that indicate a lookup service has been 
     *  		discovered or discarded will be received.
     *  		If the value of the argument is <code>null</code>,
     *                  then an instance of the
     *  		<code>LookupDiscoveryManager</code> utility
     *  		class will be constructed to listen for events
     *  		announcing the discovery of only those lookup
     *  		services that are members of the public group.
     * 
     * @param leaseMgr the <code>LeaseRenewalManager</code> to use. A 
     *  		value of <code>null</code> may be passed as the
     *  		<code>LeaseRenewalManager</code> argument. If
     *  		the value of the argument is <code>null</code>,
     *  		an instance of the
     *  		<code>LeaseRenewalManager</code> class will be
     *  		created, initially managing no
     *  		<code>Lease</code> objects.
     * 
     * @throws IOException because construction of a  
     *  		<code>ServiceDiscoveryManager</code> may initiate
     *  		the multicast discovery process which can throw
     *  		an <code>IOException</code>.
     *
     * @throws net.jini.config.ConfigurationException indicates
     *         an exception occurred while retrieving an item from the given
     *         <code>Configuration</code>
     *
     * @throws java.lang.NullPointerException if <code>null</code> is input
     *         for the configuration
     *
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.core.event.RemoteEventListener
     * @see net.jini.core.lookup.ServiceRegistrar
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
    public ServiceDiscoveryManager(DiscoveryManagement discoveryMgr,
                                   LeaseRenewalManager leaseMgr,
                                   Configuration config)
                                                throws IOException,
                                                       ConfigurationException
    {
        init(discoveryMgr, leaseMgr, config);
    }//end constructor

    /** Sends discarded event to each listener waiting for discarded lookups.*/
    private void listenerDropped(ArrayList drops, ArrayList notifies) {
	ServiceRegistrar[] proxys = new ServiceRegistrar[drops.size()];
	drops.toArray(proxys);
	listenerDropped(proxys, notifies);
    }//end listenerDropped

    /** Sends discarded event to each listener waiting for discarded lookups.*/
    private void listenerDropped(ServiceRegistrar[] proxys,ArrayList notifies){
	Iterator iter = notifies.iterator();
	while (iter.hasNext()) {
	    DiscoveryEvent evt = new DiscoveryEvent
                                        ( this,
                                          (ServiceRegistrar[])proxys.clone() );
	    ((DiscoveryListener)iter.next()).discarded(evt);
	}//end loop
    }//end listenerDropped

    /** Sends discovered event to each listener listening for new lookups. */
    private void listenerDiscovered(ServiceRegistrar proxy,ArrayList notifies){
	Iterator iter = notifies.iterator();
	while (iter.hasNext()) {
	    DiscoveryEvent evt = new DiscoveryEvent
                                        ( this,
                                          new ServiceRegistrar[]{proxy} );
	    ((DiscoveryListener)iter.next()).discovered(evt);
	}//end loop
    }//end listenerDiscovered
   
    /** Returns array of ServiceRegistrar created from the proxyRegSet */
    private ServiceRegistrar[] buildServiceRegistrar() {
	int k = 0;
	ServiceRegistrar[] proxys = new ServiceRegistrar[proxyRegSet.size()];
	Iterator iter = proxyRegSet.iterator();
	while(iter.hasNext()) {
	    ProxyReg reg = (ProxyReg)iter.next();
	    proxys[k++] = reg.proxy;
	}//end loop
	return proxys;
    }//end buildServiceRegistrar

    /**
     * Queries each available lookup service in the set of lookup services
     * managed by the <code>ServiceDiscoveryManager</code> (the <i>managed
     * set</i>) for a service reference that matches criteria defined by the
     * entity that invokes this method. The semantics of this method are
     * similar to the semantics of the <code>lookup</code> method provided
     * by the <code>ServiceRegistrar</code> interface; employing the same 
     * template-matching scheme. Additionally, this method allows any entity
     * to supply an object referred to as a <i>filter</i>. Such an object is
     * a non-remote object that defines additional matching criteria that the
     * <code>ServiceDiscoveryManager</code> applies when searching for the
     * entity's services of interest. This filtering facility is particularly
     * useful to entities that wish to extend the capabilities of standard
     * template-matching.
     * <p>
     * Entities typically employ this method when they need infrequent access
     * to services, and when the cost of making remote queries is outweighed
     * by the overhead of maintaining a local cache (for example, because of
     * resource limitations).
     * <p>
     * This version of <code>lookup</code> returns a <i>single</i> instance
     * of <code>ServiceItem</code> corresponding to one of possibly many
     * service references that satisfy the matching criteria. If multiple
     * services matching the input criteria happen to exist, it is arbitrary
     * as to which reference is actually returned. It is for this reason that
     * entities that invoke this method typically care only that <i>a</i>
     * service is returned, not <i>which</i> service.
     * <p>
     * Note that, unlike other versions of <code>lookup</code> provided
     * by the <code>ServiceDiscoveryManager</code>, this version does not 
     * <i>block</i>. That is, this version will return immediately upon
     * failure (or success) to find a service matching the input criteria.
     *
     * @param tmpl   an instance of <code>ServiceTemplate</code> corresponding
     *               to the object to use for template-matching when searching
     *               for desired services. If <code>null</code> is input to
     *               this parameter, this method will use a <i>wildcarded</i>
     *               template (will match all services) when performing
     *               template-matching. Note that the effects of modifying
     *               contents of this parameter before this method returns
     *               are unpredictable and undefined.
     * @param filter an instance of <code>ServiceItemFilter</code> containing
     *               matching criteria that should be applied in addition to
     *               the template-matching employed when searching for desired
     *               services. If <code>null</code> is input to this parameter,
     *               then only template-matching will be employed to find the 
     *               desired services.
     *
     * @return a single instance of <code>ServiceItem</code> corresponding to
     *         a reference to a service that matches the criteria represented
     *         in the input parameters; or <code>null</code> if no matching
     *         service can be found. Note that if multiple services matching
     *         the input criteria exist, it is arbitrary as to which reference
     *         is returned.
     *
     * @see net.jini.core.lookup.ServiceRegistrar#lookup
     * @see net.jini.core.lookup.ServiceTemplate
     * @see net.jini.lookup.ServiceItemFilter
     */
    public ServiceItem lookup(ServiceTemplate tmpl, ServiceItemFilter filter) {
	checkTerminated();
	ServiceRegistrar[] proxys;
	synchronized(proxyRegSet) {
	    proxys =  buildServiceRegistrar();
	}
	int len = proxys.length;
	if(len == 0 ) return null;
	int rand = Math.abs(random.nextInt()) % len;
	for(int i=0; i<len; i++) {
	    ServiceRegistrar proxy = proxys[(i + rand) % len];
	    ServiceItem sItem = null;
	    try {
                int maxMatches = ( (filter != null) ? Integer.MAX_VALUE : 1 );
		ServiceMatches sm = proxy.lookup(tmpl, maxMatches);
		sItem = getMatchedServiceItem(sm, filter);
	    } catch(Exception e) {
                logger.log(Level.INFO,
                           "Exception occurred during query, discarding proxy",
                           e);
		discard(proxy);
            }
	    if(sItem != null) return sItem;
	}//end loop
	return null;
    }//end lookup

    /**
     * Queries each available lookup service in the managed set for a service
     * that matches the input criteria. The semantics of this method are
     * similar to the semantics of the <code>lookup</code> method provided by
     * the <code>ServiceRegistrar</code> interface; employing the same 
     * template-matching scheme. Additionally, this method allows any entity
     * to supply an object referred to as a <i>filter</i>. Such an object is
     * a non-remote object that defines additional matching criteria that the
     * <code>ServiceDiscoveryManager</code> applies when searching for the
     * entity's services of interest. This filtering facility is particularly
     * useful to entities that wish to extend the capabilities of standard
     * template-matching.
     * <p>
     * This version of <code>lookup</code> returns a <i>single</i> instance
     * of <code>ServiceItem</code> corresponding to one of possibly many
     * service references that satisfy the matching criteria. If multiple
     * services matching the input criteria happen to exist, it is arbitrary
     * as to which reference is actually returned. It is for this reason that
     * entities that invoke this method typically care only that <i>a</i>
     * service is returned, not <i>which</i> service.
     * <p>
     * Note that this version of <code>lookup</code> provides a 
     * <i>blocking</i> feature that is controlled through the
     * <code>waitDur</code> parameter. That is, this version will not return
     * until either a service that matches the input criteria has been
     * found, or the amount of time contained in the <code>waitDur</code>
     * parameter has passed. If, while waiting for the service of interest
     * to be found, the entity decides that it no longer wishes to wait the
     * entire period for this method to return, the entity may interrupt this
     * method by invoking the interrupt method from the <code>Thread</code>
     * class. The intent of this mechanism is to allow the entity to interrupt
     * this method in the same way it would a sleeping thread.
     * <p>
     * Entities typically employ this method when they need infrequent access
     * to services, are willing (or forced) to wait for those services to be
     * found, and consider the cost of making remote queries for those
     * services is outweighed by the overhead of maintaining a local cache
     * (for example, because of resource limitations).
     *
     * @param tmpl    an instance of <code>ServiceTemplate</code> corresponding
     *                to the object to use for template-matching when searching
     *                for desired services. If <code>null</code> is input to
     *                this parameter, this method will use a <i>wildcarded</i>
     *                template (will match all services) when performing
     *                template-matching. Note that the effects of modifying
     *                contents of this parameter before this method returns
     *                are unpredictable and undefined.
     * @param filter  an instance of <code>ServiceItemFilter</code> containing
     *                matching criteria that should be applied in addition
     *                to the template-matching employed when searching for 
     *                desired services. If <code>null</code> is input to this
     *                parameter, then only template-matching will be employed
     *                to find the desired services.
     * @param waitDur the amount of time (in milliseconds) to wait before
     *                ending the "search" and returning <code>null</code>.
     *                If a non-positive value is input to this parameter,
     *                then this method will not wait; it will simply query
     *                the available lookup services and return a matching 
     *                service reference or <code>null</code>.
     *
     * @return a single instance of <code>ServiceItem</code> corresponding to
     *         a reference to a service that matches the criteria represented
     *         in the input parameters; or <code>null</code> if no matching
     *         service can be found. Note that if multiple services matching
     *         the input criteria exist, it is arbitrary as to which reference
     *         is returned.
     * 
     * @throws java.lang.InterruptedException this exception occurs when the
     *         entity interrupts this method by invoking the interrupt method
     *         from the <code>Thread</code> class.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         a RemoteException occurs either as a result of an attempt
     *         to export a remote listener, or an attempt to register with the
     *         event mechanism of a lookup service.
     *
     * @see net.jini.core.lookup.ServiceRegistrar#lookup
     * @see net.jini.core.lookup.ServiceTemplate
     * @see net.jini.lookup.ServiceItemFilter
     * @see java.lang.Thread
     */
    public ServiceItem lookup(ServiceTemplate tmpl,
                              ServiceItemFilter filter,
			      long waitDur)  throws InterruptedException,
                                                    RemoteException
    {
        /* First query each lookup for the desired service */
        ServiceItem sm = lookup(tmpl,filter);//checkTerminated() is done here
	if(sm != null) return sm;
        /* If the desired service is not in any of the lookups, wait for it. */
	ServiceDiscoveryListener cacheListener
                                        = new ServiceDiscoveryListenerImpl();
        LookupCacheImpl cache = null;
        try {
            /* The cache must be created inside the listener sync block,
             * otherwise a race condition can occur. This is because the
             * creation of a cache results in event registration which
             * will ultimately result in the invocation of the serviceAdded()
             * method in the cache's listener, and the interruption of any
             * objects waiting on the cache's listener. If the notifications
             * happen to occur before commencing the wait on the listener
             * object (see below), then the wait will never be interrupted
             * because the interrupts were sent before the wait() method 
             * was invoked. Synchronizing on the listener and the listener's
             * serviceAdded() method, and creating the cache only after the
             * lock has been acquired, together will prevent this situation
             * since event registration cannot occur until the cache is
             * created, and the lock that allows entry into the serviceAdded()
             * method (which is invoked once the events do arrive) is not
             * released until the wait() method is invoked .
             */
            synchronized(cacheListener) {
	        cache = createLookupCache(tmpl,filter,cacheListener,waitDur);
                long duration = cache.getLeaseDuration();
                while ( duration > 0 ) {
                    cacheListener.wait(duration);
                    sm = cache.lookup(null);
                    if(sm != null )  return sm;
                    duration = cache.getLeaseDuration();
                }//end loop
            }//end sync(cacheListener)
            return sm;
        } finally {
            if(cache != null) cache.terminate();
        }
    }//end lookup

    /**
     * The <code>createLookupCache</code> method allows the client-like
     * entity to request that the <code>ServiceDiscoveryManager</code>
     * create a new managed set (or cache) and populate it with
     * services, which match criteria defined by the entity, and whose
     * references are registered with one or more of the lookup
     * services the entity has targeted for discovery.
     * <p>
     * This method returns an object of type <code>LookupCache</code>.
     * Through this return value, the entity can query the cache for
     * services of interest, manage the cache's event mechanism for
     * service discoveries, or terminate the cache.
     * <p>
     * An entity typically uses the object returned by this method to
     * provide local storage of, and access to, references to services
     * that it is interested in using. Entities needing frequent access
     * to numerous services will find the object returned by this
     * method quite useful because acquisition of those service
     * references is provided through local method invocations.
     * Additionally, because the object returned by this method provides
     * an event mechanism, it is also useful to entities wishing to
     * simply monitor, in an event-driven manner, the state changes that
     * occur in the services of interest.
     * <p>
     * Although not required, a common usage pattern for entities that
     * wish to use the <code>LookupCache</code> class to store and manage
     * "discovered" services is to create a separate cache for each service
     * type of interest.
     * 
     * @param tmpl template to match. It uses template-matching
     *        semantics to identify the service(s) to acquire from 
     *        lookup services in the managed set. If this value is 
     *        <code>null</code>, it is the equivalent of passing a 
     *        <code>ServiceTemplate</code> constructed with all 
     *        <code>null</code> arguments (all wildcards).
     * @param filter used to apply additional matching criteria to any 
     *        <code>ServiceItem</code> found through template-matching.
     *        If this value is <code>null</code>, no additional filtering
     *        will be applied beyond the template-matching. 
     * @param listener object that will receive notifications when 
     *        services matching the input criteria are discovered for 
     *        the first time, or have encountered a state change such as 
     *        removal from all lookup services or attribute set changes. 
     *        If this value is <code>null</code>, the cache resulting from 
     *        that invocation will send no such notifications.
     *
     * @return LookupCache used to query the cache for services of
     * 	      interest, manage the cache's event mechanism for service
     *        discoveries, or terminate the cache.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         a RemoteException occurs as a result of an attempt to export 
     *         the remote listener that receives service events from the
     *         lookup services in the managed set.
     *
     * @see net.jini.lookup.ServiceItemFilter
     */
    public LookupCache createLookupCache(ServiceTemplate tmpl, 
					 ServiceItemFilter filter,
					 ServiceDiscoveryListener listener)
                                                        throws RemoteException
    {
	checkTerminated();
	return createLookupCache(tmpl, filter, listener, Long.MAX_VALUE);
    }//end createLookupCache

    /** The <code>getDiscoveryManager</code> method will return an
     *  object that implements the <code>DiscoveryManagement</code>
     *  interface. The object returned by this method provides the
     *  <code>ServiceDiscoveryManager</code> with the ability to set
     *  discovery listeners and to discard previously discovered lookup
     *  services when they are found to be unavailable.
     *
     *  @return DiscoveryManagement implementation
     *  @see net.jini.discovery.DiscoveryManagement
     */
    public DiscoveryManagement getDiscoveryManager() {
	checkTerminated();
	return discMgr;
    }//end getDiscoveryManager

    /** 
     * The <code>getLeaseRenewalManager</code> method will return an
     * instance of the <code>LeaseRenewalManager</code> class. The
     * object returned by this method manages the leases requested and
     * held by the <code>ServiceDiscoveryManager</code>. In general, these 
     * leases correspond to the registrations made by the 
     * <code>ServiceDiscoveryManager</code> with the event mechanism of
     * each lookup service in the managed set. 
     *
     * @return LeaseRenewalManager for this instance of the
     *         <code>ServiceDiscoveryManager</code>.
     * @see net.jini.lease.LeaseRenewalManager
     */
    public LeaseRenewalManager getLeaseRenewalManager() {
	checkTerminated();
	return leaseRenewalMgr;
    }//end getLeaseRenewalManager
    
    /** 
     * The <code>terminate</code> method performs cleanup duties
     * related to the termination of the event mechanism for lookup
     * service discovery, the event mechanism for service discovery,
     * and the cache management duties of the
     * <code>ServiceDiscoveryManager</code>.  
     * <p> 
     * For each instance of <code>LookupCache</code> created and 
     * managed by the <code>ServiceDiscoveryManager</code>, the
     * <code>terminate</code> method will do the following:
     * <ul>
     * <li>Either remove all listener objects registered for receipt
     * of <code>DiscoveryEvent</code> objects or, if the discovery
     * manager employed by the <code>ServiceDiscoveryManager</code> was
     * created by the <code>ServiceDiscoveryManager</code> itself,
     * terminate all discovery processing being performed by that
     * manager object on behalf of the entity.
     * <p> 
     * <li>Cancel all event leases granted by each lookup service in
     * the managed set of lookup services.
     * <p>
     * <li>Un-export all remote listener objects registered with each 
     * lookup service in the managed set.  
     * <p> 
     * <li>Terminate all threads involved in the process of retrieving
     * and storing references to discovered services of interest.
     * </ul> 
     * Calling any method after the termination will result in an
     * <code>IllegalStateException</code>.
     *
     * @see net.jini.lookup.LookupCache
     * @see net.jini.discovery.DiscoveryEvent
     */
    public void terminate() {
	synchronized(this) {
            if(bTerminated) return;//allow for multiple terminations
	    bTerminated = true;
            /* Terminate lookup service discovery processing */
            discMgr.removeDiscoveryListener(discMgrListener);
            if(discMgrInternal) discMgr.terminate();
	}//end sync
        /* Terminate all caches: cancel event leases, un-export listeners */
        boolean terminateCaches = false;
	ArrayList cachesClone = null;
	synchronized(caches) {
	    if( !caches.isEmpty() ) {
                terminateCaches = true;
	        cachesClone = (ArrayList)caches.clone();
            }
	}//end sync
        if(terminateCaches) {
            Iterator iter = cachesClone.iterator();
            while (iter.hasNext()) {
                LookupCacheImpl cache = (LookupCacheImpl)iter.next();
                cache.terminate();
            }//end loop
        }//endif(terminateCaches)
    }//end terminate

    /**
     * Queries each available lookup service in the managed set for service(s)
     * that match the input criteria. The semantics of this method are
     * similar to the semantics of the <code>lookup</code> method provided by
     * the <code>ServiceRegistrar</code> interface; employing the same 
     * template-matching scheme. Additionally, this method allows any entity
     * to supply an object referred to as a <i>filter</i>. Such an object is
     * a non-remote object that defines additional matching criteria that the
     * <code>ServiceDiscoveryManager</code> applies when searching for the
     * entity's services of interest. This filtering facility is particularly
     * useful to entities that wish to extend the capabilities of standard
     * template-matching.
     * <p>
     * Entities typically employ this method when they need infrequent access
     * to multiple instances of services, and when the cost of making remote
     * queries is outweighed by the overhead of maintaining a local cache
     * (for example, because of resource limitations).
     * <p>
     * This version of <code>lookup</code> returns an <i>array</i> of instances
     * of <code>ServiceItem</code> in which each element corresponds to a
     * service reference that satisfies the matching criteria. The number
     * of elements in the returned set will be no greater than the value of
     * the <code>maxMatches</code> parameter, but may be less.
     * <p>
     * Note that this version of <code>lookup</code> does not provide a 
     * <i>blocking</i> feature. That is, this version will return immediately
     * with whatever number of service references it can find, up to
     * the number indicated in the <code>maxMatches</code> parameter. If
     * no services matching the input criteria can be found on the first
     * attempt, an empty array is returned.
     *
     * @param tmpl       an instance of <code>ServiceTemplate</code>
     *                   corresponding to the object to use for 
     *                   template-matching when searching for desired services.
     *                   If <code>null</code> is input to this parameter, 
     *                   this method will use a <i>wildcarded</i> template 
     *                   (will match all services) when performing
     *                   template-matching. Note that the effects of modifying
     *                   contents of this parameter before this method returns
     *                   are unpredictable and undefined.
     * @param maxMatches this method will return no more than this number of
     *                   service references
     * @param filter     an instance of <code>ServiceItemFilter</code> 
     *                   containing matching criteria that should be applied 
     *                   in addition to the template-matching employed when 
     *                   searching for desired services. If <code>null</code> 
     *                   is input to this parameter, then only 
     *                   template-matching will be employed to find the 
     *                   desired services.
     *
     * @return an array of instances of <code>ServiceItem</code> where each
     *         element corresponds to a reference to a service that matches
     *         the criteria represented in the input parameters; or an
     *         empty array if no matching service can be found.
     *
     * @see net.jini.core.lookup.ServiceRegistrar#lookup
     * @see net.jini.core.lookup.ServiceTemplate
     * @see net.jini.lookup.ServiceItemFilter
     */
    public ServiceItem[] lookup(ServiceTemplate tmpl,
                                int maxMatches,
			        ServiceItemFilter filter)
    { 
	checkTerminated();
	if (maxMatches < 1)
	    throw new IllegalArgumentException("maxMatches must be > 0");
        /* retrieve the lookup service(s) to query for matching service(s) */
	ServiceRegistrar[] proxys;
	synchronized(proxyRegSet) {
	    proxys =  buildServiceRegistrar();
	}
	int len = proxys.length;
	ArrayList sItemSet = new ArrayList(len);
	if(len > 0) {
            /* loop thru the set of lookups, randomly selecting each lookup */
	    int rand = (Math.abs(random.nextInt())) % len;
	    for(int i=0; i<len; i++) {
                int max = maxMatches;
		ServiceRegistrar proxy = proxys[(i + rand) % len];
		try {
                    /* If a filter is to be applied (filter != null), then
                     * the value of the maxMatches parameter will not 
                     * suffice when querying the current lookup service.
                     * This is because although services returned from a
                     * query of the lookup service will match the template,
                     * some of those services may get filtered out. Thus,
                     * asking for exactly maxMatches may result in fewer
                     * matching services than actually are contained in
                     * the lookup. Thus, all matching services are 
                     * requested by passing in "infinity" for the maximum
                     * number of matches (Integer.MAX_VALUE).
                     */
                    if(filter != null) max = Integer.MAX_VALUE;
                    /* Query the current lookup for matching service(s). */
		    ServiceMatches sm = proxy.lookup(tmpl, max);
		    int nItems = sm.items.length;
		    if(nItems == 0) continue;//no matches, query next lookup
                    /* Loop thru the matching services, randomly selecting
                     * each service, applying the filter if appropriate,
                     * and making sure the service has not already been
                     * selected (it may have been returned from a previously
                     * queried lookup).
                     */
		    int r = (Math.abs(random.nextInt())) % nItems;
		    for(int j=0; j<nItems; j++) {
			ServiceItem sItem = sm.items[(j+r) % nItems];
			if(sItem == null)  continue;
                        if( !filterPassFail(sItem,filter) ) continue;
			if(!isArrayContainsServiceItem(sItemSet, sItem))
			    sItemSet.add(sItem);
			if(sItemSet.size() >= maxMatches) {
                            return (ServiceItem [])(sItemSet.toArray
                                           (new ServiceItem[sItemSet.size()]));
			}
		    }//end loop(j)
		} catch(Exception e) {
                    logger.log(Level.INFO,
                               "Exception occurred during query, "
                               +"discarding proxy",
                               e);
		    discard(proxy);
                }
	    }//end loop(i)
	}//endif(len>0)
        /* Will reach this return statement only when less than the number
         * of services requested have been found in the loop above.
         */
        return (ServiceItem [])(sItemSet.toArray
                                           (new ServiceItem[sItemSet.size()]));
    }//end lookup

    /**
     * Queries each available lookup service in the managed set for service(s)
     * that match the input criteria. The semantics of this method are
     * similar to the semantics of the <code>lookup</code> method provided by
     * the <code>ServiceRegistrar</code> interface; employing the same 
     * template-matching scheme. Additionally, this method allows any entity
     * to supply an object referred to as a <i>filter</i>. Such an object is
     * a non-remote object that defines additional matching criteria that the
     * <code>ServiceDiscoveryManager</code> applies when searching for the
     * entity's services of interest. This filtering facility is particularly
     * useful to entities that wish to extend the capabilities of standard
     * template-matching.
     * <p>
     * This version of <code>lookup</code> returns an <i>array</i> of instances
     * of <code>ServiceItem</code> in which each element corresponds to a
     * service reference that satisfies the matching criteria. The number
     * of elements in the returned set will be no greater than the value of
     * the <code>maxMatches</code> parameter, but may be less.
     * <p>
     * Note that this version of <code>lookup</code> provides a 
     * <i>blocking</i> feature that is controlled through the
     * <code>waitDur</code> parameter in conjunction with the
     * <code>minMatches</code> and the <code>maxMatches</code> parameters.
     * This method will not return until one of the following occurs:
     * <p>
     *  <ul>
     *    <li> the number of matching services found on the first attempt is
     *         greater than or equal to the value of the
     *         <code>minMatches</code> parameter, in which case this method
     *         returns each of the services found up to the value of 
     *         the <code>maxMatches</code> parameter
     *    <li> the number of matching services found <i>after</i> the first
     *         attempt (that is, after the method enters the "wait state")
     *         is at least as great as the value of the
     *         <code>minMatches</code> parameter in which case this method
     *         returns each of the services found up to the value of 
     *         the <code>maxMatches</code> parameter
     *    <li> the amount of time that has passed since this method entered
     *         the wait state exceeds the value of the <code>waitDur</code>
     *         parameter, in which case this method returns all of the
     *         currently discovered services
     *  </ul>
     * <p>
     * The purpose of the <code>minMatches</code> parameter is to allow the
     * entity to balance its need for multiple matching service references
     * with its need to minimize the time spent in the wait state; time that
     * most would consider wasted if an acceptable number of matching service
     * references were found, but this method continued to wait until the end
     * of the designated time period.
     * <p>
     * If, while waiting for the minimum number of desired services to 
     * be discovered, the entity decides that it no longer wishes to wait the
     * entire period for this method to return, the entity may interrupt this
     * method by invoking the interrupt method from the <code>Thread</code>
     * class. The intent of this mechanism is to allow the entity to interrupt
     * this method in the same way it would a sleeping thread.
     * <p>
     * Entities typically employ this method when they need infrequent access
     * to multiple instances of services, are willing (or forced) to
     * wait for those services to be found, and consider the cost of making
     * remote queries for those services is outweighed by the overhead
     * of maintaining a local cache (for example, because of resource
     * limitations).
     *
     * @param tmpl        an instance of <code>ServiceTemplate</code>
     *                    corresponding to the object to use for 
     *                    template-matching when searching for desired
     *                    services. If <code>null</code> is input to this
     *                    parameter, this method will use a
     *                    <i>wildcarded</i> template  (will match all 
     *                    services) when performing template-matching. Note 
     *                    that the effects of modifying contents of this 
     *                    parameter before this method returns are 
     *                    unpredictable and undefined.
     * @param minMatches  this method will immediately exit the wait state
     *                    and return once this number of service references
     *                    is found
     * @param maxMatches  this method will return no more than this number of
     *                    service references
     * @param filter      an instance of <code>ServiceItemFilter</code> 
     *                    containing matching criteria that should be applied 
     *                    in addition to the template-matching employed when 
     *                    searching for desired services. If <code>null</code> 
     *                    is input to this parameter, then only 
     *                    template-matching will be employed to find the 
     *                    desired services.
     * @param waitDur     the amount of time (in milliseconds) to wait before
     *                    ending the "search" and returning an empty array.
     *                    If a non-positive value is input to this parameter,
     *                    then this method will not wait; it will simply query
     *                    the available lookup services and return whatever
     *                    matching service reference(s) it could find, up
     *                    to <code>maxMatches</code>.
     *
     * @return an array of instances of <code>ServiceItem</code> where each
     *         element corresponds to a reference to a service that matches
     *         the criteria represented in the input parameters; or an
     *         empty array if no matching service can be found within the
     *         time allowed.
     * 
     * @throws java.lang.InterruptedException this exception occurs when the
     *         entity interrupts this method by invoking the interrupt method
     *         from the <code>Thread</code> class.
     * 
     * @throws java.lang.IllegalArgumentException this exception occurs when
     *         one of the following conditions is satisfied:
     * <p><ul> <li>the <code>minMatches</code> parameter is non-positive
     *         <li>the <code>maxMatches</code> parameter is non-positive
     *         <li>the value of <code>maxMatches</code> is <i>less than</i>
     *             the value of <code>minMatches</code>
     *    </ul>
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         a RemoteException occurs either as a result of an attempt
     *         to export a remote listener, or an attempt to register with the
     *         event mechanism of a lookup service.
     *
     * @see net.jini.core.lookup.ServiceRegistrar#lookup
     * @see net.jini.core.lookup.ServiceTemplate
     * @see net.jini.lookup.ServiceItemFilter
     * @see java.lang.Thread
     */
    public ServiceItem[] lookup(ServiceTemplate tmpl,
                                int minMatches,
                                int maxMatches,
			        ServiceItemFilter filter,
                                long waitDur )  throws InterruptedException,
                                                       RemoteException
    {
	checkTerminated();
	if (minMatches < 1)
	    throw new IllegalArgumentException("minMatches must be > 0");
	if (maxMatches < minMatches)
	    throw new IllegalArgumentException
                                     ("maxMatches must be > minMatches");

	ServiceItem [] sItems = lookup(tmpl, maxMatches, filter);
	if(sItems.length >= minMatches) return sItems;
	ArrayList sItemSet = new ArrayList(sItems.length);
	for(int i=0; i<sItems.length; i++) {
	    //if(!sItemSet.contains(sItems[i])
	    sItemSet.add(sItems[i]);
	}//end loop
	ServiceDiscoveryListenerImpl cacheListener =
                                           new ServiceDiscoveryListenerImpl();
        /* The cache must be created inside the listener sync block,
         * otherwise a race condition can occur. This is because the
         * creation of a cache results in event registration which
         * will ultimately result in the invocation of the serviceAdded()
         * method in the cache's listener, and the interruption of any
         * objects waiting on the cache's listener. If the notifications
         * happen to occur before commencing the wait on the listener
         * object (see below), then the wait will never be interrupted
         * because the interrupts were sent before the wait() method 
         * was invoked. Synchronizing on the listener and the listener's
         * serviceAdded() method, and creating the cache only after the
         * lock has been acquired, together will prevent this situation
         * since event registration cannot occur until the cache is
         * created, and the lock that allows entry into the serviceAdded()
         * method (which is invoked once the events do arrive) is not
         * released until the wait() method is invoked.
         */
        LookupCacheImpl cache = null;
        synchronized(cacheListener) {
            cache = createLookupCache(tmpl,filter,cacheListener,waitDur);
            long duration = cache.getLeaseDuration();
            while ( duration > 0 ) {
                cacheListener.wait(duration);
                ServiceItem items[] = cacheListener.getServiceItem();
                for(int i=0; i<items.length; i++) {
                    if(!isArrayContainsServiceItem(sItemSet, items[i])) {
                        sItemSet.add(items[i]);
                    }//endif
                }//end loop
                if(sItemSet.size() == minMatches)  break;
                duration = cache.getLeaseDuration();
            }//end loop
        }//end sync(cacheListener)
	cache.terminate();
	ServiceItem [] r = new ServiceItem[sItemSet.size()];
	sItemSet.toArray(r);
	return r; 
    }//end lookup

    /** From the given set of ServiceMatches, randomly selects and returns
     *  a ServiceItem that matches the given filter (if applicable).
     */    
    private ServiceItem getMatchedServiceItem(ServiceMatches sm, 
					      ServiceItemFilter filter)
    {
	int len = sm.items.length;
	if(len > 0) {
	    int rand = Math.abs(random.nextInt()) % len;
	    for(int i=0; i<len; i++) {
		ServiceItem sItem = sm.items[(i+rand) % len];
		if(sItem == null)  continue;
                if( !filterPassFail(sItem,filter) ) continue;
		return sItem;
	    }//end loop
	}//endif
	return null;
    }//end getMatchedServiceItem

    /** Creates a LookupCache with specific lease duration. */
    private LookupCacheImpl createLookupCache
                                        (ServiceTemplate tmpl, 
					 ServiceItemFilter filter,
					 ServiceDiscoveryListener listener,
					 long leaseDuration)
                                                       throws RemoteException
    {
	if(tmpl == null) tmpl = new ServiceTemplate(null, null, null);
	LookupCacheImpl cache = new LookupCacheImpl(tmpl, filter,
                                                    listener, leaseDuration);
	synchronized(caches) {
	    caches.add(cache);
	}
        logger.finest("ServiceDiscoveryManager - LookupCache created");
	return cache;
    }//end createLookupCache

    /** Returns element from proxyRegSet that corresponds to the given proxy.*/
    private ProxyReg findReg(ServiceRegistrar proxy) {
	Iterator iter = proxyRegSet.iterator();
	while(iter.hasNext()) {
	    ProxyReg reg =(ProxyReg)iter.next();
	    if(reg.proxy.equals(proxy))  return reg;
	}//end loop
    	return null;
    }//end findReg

    /** Convenience method invoked when failure occurs in the cache
     *  tasks executed in this utility. If the appropriate logging level
     *  is enabled, this method will log the stack trace of the given
     *  <code>Throwable</code>; noting the given source class and method,
     *  and displaying the given message. Additionally, this method will
     *  discard the given lookup service proxy. Note that if the utility
     *  itself has already been terminated, or if the cache in which the
     *  failure occurred has been terminated, then the failure is logged
     *  at the HANDLED level, and the lookup service proxy is not discarded.
     *  
     *  Also, note that if the discovery manager employed by this utility has
     *  already been terminated, then the attempt to discard the given lookup
     *  service proxy will result in an <code>IllegalStateException</code>.
     *  Since this method is called from within the tasks run by this 
     *  utility, and since propagating an <code>IllegalStateException</code>
     *  out into the ThreadGroup of those tasks is undesirable, this method
     *  does not propagate <code>IllegalStateException</code>s that occur as
     *  a result of an attempt to discard a lookup service proxy from the
     *  discovery manager.
     * 
     *  For more information, refer to Bug 4490358 and 4858211.
     */
    private void fail(Throwable e,
                      ServiceRegistrar proxy,
                      String sourceClass,
                      String sourceMethod,
                      String msg,
                      boolean cacheTerminated)
    {
        Level logLevel = Level.INFO;
        boolean discardProxy = true;
        synchronized(this) {
            if(bTerminated || cacheTerminated) {
                logLevel = Levels.HANDLED;
                discardProxy = false;
            }//endif
        }//end sync(this)
        if( (e != null) && (logger.isLoggable(logLevel)) ) {
            logger.logp(logLevel, sourceClass, sourceMethod, msg, e);
        }//endif
        try {
            if(discardProxy)  discard(proxy);
        } catch(IllegalStateException e1) {
            if(logger.isLoggable(logLevel) ) {
                logger.logp(logLevel,
                            sourceClass,
                            sourceMethod,
                            "failure discarding lookup service proxy, "
                            +"discovery manager already terminated",
                            e1);
            }//endif
        }
    }//end fail

    /** Discards a ServiceRegistrar through the discovery manager.*/
    private void discard(ServiceRegistrar proxy) {
	discMgr.discard(proxy);
    }//end discard

    /** Cancels the given event lease. */
    private void cancelLease(Lease lease ) {
	try {
	    leaseRenewalMgr.cancel(lease);
	} catch(Exception e) {
           logger.log(Level.FINER,
                      "exception occurred while cancelling an event "
                      +"registration lease",
                      e);
        }
    }//end cancelLease

    /** Registers for events from the lookup service associated with the
     *  given proxy, and returns both the lease and the event sequence number
     *  from the event registration wrapped in the locally-defined class,
     *  <code>EventReg</code>.
     * 
     *  This method is called from the <code>RegisterListenerTask</code>. If
     *  a <code>RemoteException</code> occurs during the event registration
     *  attempt, this method discards the lookup service and returns
     *  <code>null</code>.
     */
    private EventReg registerListener(ServiceRegistrar proxy,
			              ServiceTemplate tmpl,
			              RemoteEventListener listenerProxy,
			              long duration)  throws RemoteException
    {
        /* Register with the event mechanism of the given lookup service */
        EventRegistration e = null;
        int transition = (   ServiceRegistrar.TRANSITION_NOMATCH_MATCH
                           | ServiceRegistrar.TRANSITION_MATCH_NOMATCH
                           | ServiceRegistrar.TRANSITION_MATCH_MATCH   );
        e = proxy.notify(tmpl, transition, listenerProxy, null, duration);
        /* Proxy preparation -
         * 
         * Prepare the proxy to the lease on the event registration just
         * returned. Because lease management (renewal and cancellation)
         * involves remote calls, lease proxies should be prepared before
         * management of the associated leases begins. This allows one to
         * verify trust in the lease, and ensures that the appropriate
         * constraints are attached to the lease.
         */
        Lease eventLease = e.getLease();
        eventLease = (Lease)eventLeasePreparer.prepareProxy(eventLease);
        logger.log(Level.FINEST, "ServiceDiscoveryManager - proxy to event "
                   +"registration lease prepared: {0}", eventLease);
        /* Management the lease on the event registration */
        leaseRenewalMgr.renewFor(eventLease,
                                 duration,
                                 new LeaseListenerImpl(proxy));
        /* Wrap the lease and event sequence number in EventReg, and return. */
        return ( new EventReg(eventLease, e.getSequenceNumber()) );
    }//end registerListener

    /** Throws an IllegalStateException if the current instance of the
     *  ServiceDiscoveryManager has been terminated.
     */
    private void checkTerminated() {
	synchronized(this) {
	    if(bTerminated) {
                throw new IllegalStateException
                                 ("service discovery manager was terminated");
            }//endif
	}//end sync
    }//end checkTerminated

    /** Returns a "non-shallow" (not just a clone) copy of the given
     *  template.
     */
    static private ServiceTemplate copyServiceTemplate(ServiceTemplate tmpl) {
	Class[] serviceTypes = null;
	Entry[] attributeSetTemplates = null;
	if(tmpl.serviceTypes != null) {
	    int len = tmpl.serviceTypes.length;
	    serviceTypes = new Class[len];
	    System.arraycopy(tmpl.serviceTypes, 0, serviceTypes, 0, len );
	}
 	if(tmpl.attributeSetTemplates != null) {
	    int len =  tmpl.attributeSetTemplates.length;
	    attributeSetTemplates = new Entry[len];
	    System.arraycopy(tmpl.attributeSetTemplates, 0,
                             attributeSetTemplates, 0, len); 
	}
	return new ServiceTemplate(tmpl.serviceID,
                                   serviceTypes,
                                   attributeSetTemplates);
    }//end copyServiceTemplate

    /** Determines if the given ServiceItem is an element of the given array.*/
    static private boolean isArrayContainsServiceItem(ArrayList a,
                                                      ServiceItem s)
    {
	Iterator iter = a.iterator();
	while(iter.hasNext()) {
	    Object o = iter.next();
	    if ( !(o instanceof ServiceItem )) continue;
	    ServiceItem sa = (ServiceItem)o;
	    if(    sa.serviceID.equals(s.serviceID)
                && LookupAttributes.equal(sa.attributeSets,s.attributeSets)
                && (sa.service.equals(s.service)) )
		return true;
	}//end loop
	return false;
    }//end isArrayContainsServiceItems

    /* Convenience method that encapsulates the retrieval of the configurable
     * items from the given <code>Configuration</code> object.
     */
    private void init(DiscoveryManagement discoveryMgr,
                      LeaseRenewalManager leaseMgr,
                      Configuration config)
                                    throws IOException, ConfigurationException
    {
        /* Retrieve configuration items if applicable */
        if(config == null)  throw new NullPointerException("config is null");
        thisConfig = config;
        /* Proxy preparers */
        registrarPreparer = (ProxyPreparer)thisConfig.getEntry
                                                    (COMPONENT_NAME,
                                                     "registrarPreparer",
                                                     ProxyPreparer.class,
                                                     new BasicProxyPreparer());
        eventLeasePreparer = (ProxyPreparer)thisConfig.getEntry
                                                   (COMPONENT_NAME,
                                                    "eventLeasePreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        /* Lease renewal manager */
        leaseRenewalMgr = leaseMgr;
	if(leaseRenewalMgr == null) {
            leaseRenewalMgr = new LeaseRenewalManager(thisConfig);//default
            try {
                leaseRenewalMgr
                   = (LeaseRenewalManager)thisConfig.getEntry
                                                 (COMPONENT_NAME,
                                                  "leaseManager",
                                                  LeaseRenewalManager.class,
                                                  leaseRenewalMgr);
            } catch(ConfigurationException e) { /* use default */ }
        }//endif
        /* Wait value for the "service discard problem". */
        discardWait = ((Long)thisConfig.getEntry
                                          (COMPONENT_NAME,
                                           "discardWait",
                                           long.class,
                                           new Long(discardWait))).longValue();
        /* Discovery manager */
        discMgr = discoveryMgr;
	if(discMgr == null) {//create discovery mgr configured for public group
	    discMgrInternal = true;
            discMgr = new LookupDiscoveryManager
                                   (new String[] {""}, null, null, thisConfig);
            try {
                discMgr = (DiscoveryManagement)thisConfig.getEntry
                                                   (COMPONENT_NAME,
                                                    "discoveryManager",
                                                    DiscoveryManagement.class,
                                                    discMgr);
            } catch(ConfigurationException e) { /* use default */ }
	}//endif
	discMgr.addDiscoveryListener(discMgrListener); 
    }//end init


    /** Applies the given <code>filter</code> to the given <code>item</code>,
     *  and returns <code>true</code> if the <code>filter</code> returns a
     *  <code>pass</code> value; otherwise, returns <code>false</code>. 
     *  <p>
     *  Note that as described in the specification of 
     *  <code>ServiceItemFilter</code>, when the <code>item</code> passes
     *  the <code>filter</code>, the <code>service</code> field of
     *  the <code>item</code> is replaced with the filtered form of the
     *  object previously contained in that field. Additionally, if the 
     *  <code>filter</code> returns <code>indefinite</code>, then as specified,
     *  the <code>service</code> field is replaced with <code>null</code>
     *  (in which case, this method returns <code>false</code>).
     *  <p>
     *  This method is used by the non-blocking version(s) of the 
     *  <code>lookup</code> method of the <code>ServiceDiscoveryManager</code>,
     *  as well as when second-stage filtering is performed in the
     *  <code>LookupCache</code>.
     */    
    private boolean filterPassFail(ServiceItem item, ServiceItemFilter filter){
        if( (item == null) || (item.service == null) )  return false;
        if(filter == null)  return true;
        boolean pass = filter.check(item);
        if( pass && (item.service != null) )  return true;
        return false;
    }//end filterPassFail

}//end class ServiceDiscoveryManager
