/*
 * 
 * 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.constants.ThrowableConstants;
import com.sun.jini.lookup.entry.LookupAttributes;
import com.sun.jini.thread.RetryTask;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.thread.WakeupManager;

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.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;

import net.jini.core.entry.Entry;
import net.jini.core.lease.Lease;
import net.jini.core.lease.UnknownLeaseException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A goal of any well-behaved service is to advertise the facilities and
 * functions it provides by requesting residency within at least one lookup
 * service. Making such a request of a lookup service is known as registering
 * with, or <i>joining</i>, a lookup service. To demonstrate this good
 * behavior, a service must comply with both the multicast discovery protocol
 * and the unicast discovery protocol in order to discover the lookup services
 * it is interested in joining. The service must also comply with the join
 * protocol to register with the desired lookup services. 
 * <p>
 * In order for a service to maintain its residency in the lookup services
 * it has joined, the service must provide for the coordination, systematic
 * renewal, and overall management of all leases on that residency. In
 * addition to handling all discovery and join duties, as well as managing
 * all leases on lookup service residency, the service must also provide
 * for the coordination and management of any attribute sets with which
 * it may have registered with the lookup services in which it resides.
 * <p>
 * This class performs all of the functions related to discovery, joining,
 * service lease renewal, and attribute management which is required of a
 * well-behaved service. Each of these activities is intimately involved
 * with the maintenance of a service's residency in one or more lookup
 * services (the service's join state), thus the name <code>JoinManager</code>.
 * <p>
 * This class should be employed by services, not clients. The use of this
 * class in a wide variety of services can help minimize the work resulting
 * from having to repeatedly implement this required functionality in each
 * service. Note that this class is not remote. Services that wish to use
 * this class will create an instance of this class in the service's address
 * space to manage the entity's join state locally.
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * The following implementation-specific items are discussed below:
 * <ul><li> <a href="#jmConfigEntries">Configuring JoinManager</a>
 *     <li> <a href="#jmLogging">Logging</a>
 * </ul>
 *
 * <a name="jmConfigEntries">
 * <p>
 * <b><font size="+1">Configuring JoinManager</font></b>
 * <p>
 * </a>
 *
 * This implementation of <code>JoinManager</code> supports the following
 * configuration entries; where each configuration entry name is associated
 * with the component name <code>net.jini.lookup.JoinManager</code>. Note
 * that the configuration entries specified here are specific to this
 * implementation of <code>JoinManager</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.
 * 
 * <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.
 * </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 each service lease returned
 *            to this utility when the service is registered with the
 *            the various discovered lookup services. This entry will
 *            be retrieved from the configuration only if no lease 
 *            renewal manager is specified in the constructor.
 * </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. 
 *          <p>
 *          The following methods of the proxy returned by this preparer are
 *          invoked by this utility:
 *       <ul>
 *         <li>{@link net.jini.core.lookup.ServiceRegistrar#register register}
 *       </ul>
 * </table>
 *
 * <a name="registrationPreparer">
 * <table summary="Describes the registrationPreparer 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>registrationPreparer</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 registrations returned to
 *          this utility upon registering the service with each discovered
 *          lookup service.
 *          <p>
 *          The following methods of the proxy returned by this preparer are
 *          invoked by this utility:
 *       <ul>
 *         <li>{@link net.jini.core.lookup.ServiceRegistration#getServiceID
 *                                                           getServiceID}
 *         <li>{@link net.jini.core.lookup.ServiceRegistration#getLease
 *                                                           getLease}
 *         <li>{@link net.jini.core.lookup.ServiceRegistration#addAttributes
 *                                                           addAttributes}
 *         <li>{@link net.jini.core.lookup.ServiceRegistration#modifyAttributes
 *                                                           modifyAttributes}
 *         <li>{@link net.jini.core.lookup.ServiceRegistration#setAttributes
 *                                                           setAttributes}
 *       </ul>
 * </table>
 *
 * <a name="serviceLeasePreparer">
 * <table summary="Describes the serviceLeasePreparer 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>serviceLeasePreparer</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 to this utility through
 *          the registrations with each discovered lookup service with
 *          which this utility has registered the service.
 *          <p>
 *          Currently, none of the methods on the service lease returned
 *          by this preparer are invoked by this implementation of the utility.
 * </table>
 *
 * <a name="taskManager">
 * <table summary="Describes the taskManager 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>taskManager</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}(15, (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 this utility. The default manager creates a
 *            maximum of 15 threads, waits 15 seconds before removing
 *            idle threads, and uses a load factor of 1.0 when
 *            determining whether to create a new thread. This object
 *            should not be shared with other components in the
 *            application that employs this utility.
 * </table>
 *
 * <a name="wakeupManager">
 * <table summary="Describes the wakeupManager 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>wakeupManager</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> {@link com.sun.jini.thread.WakeupManager}
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code>new 
 *     {@link com.sun.jini.thread.WakeupManager#WakeupManager(
 *          com.sun.jini.thread.WakeupManager.ThreadDesc)
 *     WakeupManager}(new 
 *     {@link com.sun.jini.thread.WakeupManager.ThreadDesc}(null,true))</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> Object that pools and manages the various tasks that are
 *            initially executed by the object corresponding to the
 *            <a href="#taskManager"><code>taskManager</code></a> entry
 *            of this component, but which fail during that initial execution.
 *            This object schedules the re-execution of such a failed task -
 *            in the <a href="#taskManager"><code>taskManager</code></a>
 *            object - at various times in the future, until either the
 *            task succeeds or the task has been executed the maximum
 *            number of allowable times, corresponding to the 
 *            <a href="#wakeupRetries"><code>wakeupRetries</code></a>
 *            entry of this component. This object should not be shared
 *            with other components in the application that employs this
 *            utility.
 * </table>
 *
 * <a name="wakeupRetries">
 * <table summary="Describes the wakeupRetries
 *                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>wakeupRetries</code></font>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Type: <td> <code>int</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Default: <td> <code>6</code>
 * 
 *   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *     Description:
 *       <td> The maximum number of times a failed task is allowed to
 *            be executed by the object corresponding to the 
 *            <a href="#wakeupManager"><code>wakeupManager</code></a>
 *            entry of this component.
 * </table>
 *
 * <a name="jmLogging">
 * <p>
 * <b><font size="+1">Logging</font></b>
 * <p>
 * </a>
 *
 * This implementation of <code>JoinManager</code> uses the
 * {@link Logger} named <code>net.jini.lookup.JoinManager</code>
 * to log information at the following logging levels: <p>
 *
 * <table border="1" cellpadding="5"
 *         summary="Describes the information logged by JoinManager,
 *                 and the levels at which that information is logged">
 *
 * <caption halign="center" valign="top">
 *   <b><code>net.jini.lookup.JoinManager</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 a task is stopped because of a definite exception</td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#INFO INFO}</td>
 *   <td>
 *     when a task is stopped because it has exceeded the maximum number of
 *     times the task is allowed to be tried/re-tried
 *   </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#FINER FINER}</td>
 *   <td>
 *     when any exception (other than the more serious exceptions logged
 *     at higher levels) occurs in a task
 *   </td>
 * </tr>
 * <tr>
 *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
 *   <td>
 *     when an <code>IllegalStateException</code> occurs upon attempting to
 *     discard a lookup service
 *   </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 proxy is prepared</td>
 * </tr>
 * </table>
 * <p>
 *
 * @author Sun Microsystems, Inc.
 *
 * @see net.jini.discovery.DiscoveryManagement
 * @see net.jini.lease.LeaseRenewalManager
 * @see java.util.logging.Level
 * @see java.util.logging.Logger
 */
public class JoinManager {

    /** Implementation Note:
     *
     *  This class executes a number of tasks asynchronously. Each task is
     *  initially queued in a <code>com.sun.jini.thread.TaskManager</code>
     *  instance, which executes each task in a separate thread. In this
     *  way, an upper bound is placed on the number of threads executing
     *  concurrently at any one time; that is, the number of concurrent
     *  threads is "throttled".
     *
     *  In addition to throttling the number of concurrent threads, the
     *  use of a task manager, in conjunction with the task configuration,
     *  provides a level of resiliency with respect to down/unresponsive
     *  lookup services.
     *
     *  Recall from the specification that the primary function of a join
     *  manager is to maintain and update the state (registrations,
     *  attribute augmentations/replacements/changes, etc.) of the join
     *  manager's single associated service with all of the desired lookup
     *  services. Because updating a service's state in a lookup service
     *  involves remote communication, such update operations are performed
     *  asynchronously, in separate tasks. If those operations are not
     *  performed in separate tasks, then a communication problem with
     *  one of the lookup services while performing one operation could
     *  prevent (or significantly slow) the execution of all future
     *  state update operations; causing all processing in the join manager
     *  to degrade or even hang indefinitely.
     *
     *  Although performing each update operation in a separate task thread
     *  prevents a down/unresponsive lookup service from blocking other
     *  processing in the join manager, it does not guarantee consistency
     *  of the service's state in each "up" lookup service, with the state
     *  expected by the client that requested the state changes.
     *
     *  For example, suppose the client first requests that the service's
     *  attributes be replaced with a new set, and then immediately after
     *  the attribute replacement request, the client requests that the
     *  service's attributes be augmented by yet another set of attributes.
     *  Unless control is imposed on the order of execution of the tasks
     *  that perform the attribute replacement and augmentation, there is
     *  a possibility that the service's state in one or more of the lookup
     *  service's will have experienced the attribute augmentation prior to
     *  the attribute replacement, rather than the replacement followed by
     *  the augmentation, as the client would have expected. Thus, not only
     *  can the service's state in one or more of the lookup services be
     *  inconsistent with what the client expects, but it can also be
     *  inconsistent with one or more of the other lookup services with
     *  which the service is registered.
     *  
     *  To prevent the possibility of inconsistencies such as those just
     *  described, the state update operations are grouped according to
     *  the particular lookup service with which they are associated. Each
     *  such grouping is implemented as a task, executed by the task
     *  manager employed by this implementation of <code>JoinManager</code>,
     *  containing a queue of sub-tasks (the actual state update operations)
     *  that are executed by the main task in the same order in which they
     *  were requested by the client.
     *  
     *  Each main task executed by this join manager's task manager is a
     *  sub-class of the abstract class <code>RetryTask</code>, defined in
     *  the package <code>com.sun.jini.thread</code>, which implements
     *  the <code>com.sun.jini.thread.TaskManager.Task</code> interface.
     *  The association of each such task with a particular lookup service,
     *  and with a unique sequence number is reflected in the fields of this
     *  class. The unique sequence number associated with each main task
     *  is exploited by the <code>runAfter</code> method defined in 
     *  <code>RetryTask</code> to guarantee that the service managed by
     *  this <code>JoinManager</code> is assigned only one service ID.
     *   
     *  Because of the relationship between the main tasks and 
     *  <code>RetryTask</code>, those concrete main tasks created in this
     *  implementation of <code>JoinManager</code> can be processed by either
     *  a task manager or a wakeup manager; and this fact is exploited to
     *  provide a failure recovery mechanism that can tolerate task "storms".
     *  Task storms can occur in systems that contain numerous services,
     *  where each service employs its own join manager to handle its join
     *  state with the desired lookup services. A "registration request storm"
     *  is an example of one type of task storm.
     *
     *  A registration request storm can occur when a system is initially
     *  started, or when it recovers from a system-wide failure, when each
     *  service's join manager attempts to register with each discovered
     *  lookup service; resulting in a flood - or "storm" - of registration
     *  requests at each lookup service. Because the accept queue under some
     *  operating systems is - by default - very small, it is possible that,
     *  in the face of such a large number of concurrent requests, one or
     *  more of the lookup services will not be able to process the requests
     *  as fast as they are arriving in the queue. When this happens, the
     *  associated task in the join manager typically encounters a failure
     *  such as a <code>java.rmi.ConnectException</code>.
     *
     *  To deal with situations such as that described above, this
     *  <code>JoinManager</code> implementation employs a wakeup manager
     *  in addition to a task manager. When a main task is created, it is
     *  initially executed by a task manager. If a failure is encountered,
     *  and if the nature of that failure indicates that retrying the task
     *  will <i>definitely</i> fail again, then the associated lookup service
     *  is discarded so that the task can be retried when the lookup service
     *  is rediscovered. But if it is not clear that retrying the task will
     *  again fail, (that is, the failure is <i>indefinite</i>), then the
     *  task is queued for re-execution at a later time in a wakeup manager.
     *  This process is repeated - employing a "backoff" strategy - until one
     *  of the following events occurs:
     *
     *  <p><ul>
     *     <li> the task succeeds 
     *     <li> a definite failure is encountered
     *     <li> the task has been executed the maximum number times allowed 
     *  </ul><p>
     *  
     *  The maximum number of times a task is allowed to be retried before
     *  giving up and discarding the associated lookup service can be changed
     *  from its default value by setting the <code>wakeupRetries</code>
     *  congiguration entry for this component.
     *
     *  @see com.sun.jini.thread.TaskManager
     *  @see com.sun.jini.thread.WakeupManager
     *  @see com.sun.jini.thread.TaskManager.Task
     *  @see com.sun.jini.thread.RetryTask
     *  @see com.sun.jini.constants.ThrowableConstants
     */

    /** Abstract base class from which all of the task classes are derived. */
    private class ProxyRegTask extends RetryTask {
        private final long[] sleepTime = { 5*1000, 10*1000, 15*1000,
                                          20*1000, 25*1000, 30*1000 };
        protected int tryIndx  = 0;
        protected int nRetries = 0;
        protected ProxyReg proxyReg;
        protected int seqN;

        /** Basic constructor; simply stores the input parameters */
        ProxyRegTask(ProxyReg proxyReg, int seqN) {
            super(JoinManager.this.taskMgr,JoinManager.this.wakeupMgr);
            this.proxyReg = proxyReg;
            this.seqN = seqN;
        }//end constructor

        /** Executes the current instance of this task once, queuing it
         *  for retry at a later time and returning <code>false</code>
         *  upon failure. This method attempts to execute all of the tasks
         *  associated with the lookup service referenced in this task's 
         *  <code>proxyReg</code> field. Order of execution is important,
         *  and this method executes the tasks in the <code>proxyReg</code>'s
         *  <code>taskList</code> in a FIFO order.
         *
         *  Note that tasks may be added to the <code>taskList</code> of
         *  the <code>proxyReg</code> during the execution of this method.
         *
         *  Upon successfully executing all of the tasks in the 
         *  <code>taskList</code>, this method returns <code>true</code>
         *  and the current instance of this task is not executed again.
         *  For each unsuccessful execution of a task in the 
         *  <code>taskList</code>, this method returns <code>false</code>,
         *  which causes the task to be scheduled by the
         *  <code>WakeupManager</code> to be executed again at a later
         *  time, as indicated by the value returned by <code>retryTime</code>.
         */
        public boolean tryOnce() {
            while(true) {
                JoinTask t = null;
                synchronized(proxyReg.taskList) {
                    if(proxyReg.taskList.isEmpty()) {
                        proxyReg.proxyRegTask = null;
                        return true;
                    }//endif
                    t = (JoinTask)proxyReg.taskList.get(0);
                }//end sync
                try {
                    t.run();
                    synchronized(proxyReg.taskList) {
                        if( !proxyReg.taskList.isEmpty() ) {
                            proxyReg.taskList.remove(0);
                        }//endif
                    }//end sync
                    /* reset the retry info for the next task in the list */
                    tryIndx  = 0;
                    nRetries = 0;
                } catch (Exception e) {
                    return stopTrying(e);
                }
            }//end loop
	}//end tryOnce

        /** Returns the next absolute time (in milliseconds) at which another
         *  execution of this task should be made (after the previous
         *  attempt has failed).
         */
        public long retryTime() {
	    long nextTryTime = System.currentTimeMillis() + sleepTime[tryIndx];
	    if(tryIndx < sleepTime.length-1)  tryIndx++;//don't go past end
            nRetries++;
            return nextTryTime;
        }//end retryTime

        /** Returns true if the current instance of this task must be run
         *  after any task already in the task manager queue.
         *  
         *  It is important that when the join manager is constructed with
         *  a <code>null</code> service ID (where it is desired that 
         *  a unique service ID be generated on the service's behalf),
         *  that only the first task in the task manager's queue be run; no
         *  other tasks in the queue should be run while that first task
         *  is running. This is because the first sub-task executed by
         *  the first main task in the task manager's queue will always be
         *  a <code>RegisterTask</code>. And during the execution of that
         *  first sub-task (if the service ID has not yet been set), the
         *  service ID generated by the associated lookup service is retrieved
         *  and stored for use in all future lookup service registration
         *  tasks, Once the service ID is set by that first registration
         *  sub-task, all future main tasks (and their associated registration
         *  sub-tasks) can be run in parallel; each using the same service ID.
         *  If this is not done, then the registration sub-tasks would be
         *  run in parallel, each assigning a different ID to the service.
         *
         *  This method guarantees that until the service's ID is set,
         *  only one registration sub-task is run; that is, one task
         *  doesn't start until the currently running task has completed,
         *  and a non-<code>null</code> service ID is assigned to the service.
         *  
         *  Executing the main tasks sequentially until the service ID is 
         *  retrieved and stored must also be guaranteed because the currently
         *  running registration task may fail to register the service
         *  (because of a <code>RemoteException</code>), and thus may fail
         *  to obtain an ID for the service. This method guarantees then
         *  that each main task (and thus, each registration sub-task) will
         *  run in sequence until one of those tasks completes successfully;
         *  and from that point on, this method guarantees that all other
         *  queued tasks will run in parallel.
         *  
         *  @param tasks the tasks with which to compare the current task.
         *  @param size  elements with index less than size are considered.
         */
        public boolean runAfter(List tasks, int size) {
            /* If the service's ID has already been set, then it's okay
             * to run all ProxyRegTask's in parallel, otherwise, the
             * ProxyRegTask with the lowest sequence number should be run.
             */
            synchronized(serviceItem) {
                if(serviceItem.serviceID != null)  return false;
                /* For task with lowest seq #, run it now; else run it later */
                for(int i=0; i<size; i++) {
                    TaskManager.Task t = (TaskManager.Task)tasks.get(i);
                    int nextTaskSeqN = ((ProxyRegTask)t).getSeqN();
                    if( seqN > nextTaskSeqN )  return true;
                }//end loop
                return false;
	    }//end sync(serviceItem)
        }//end runAfter

        /** Accessor method that returns the instance of <code>ProxyReg</code>
         *  (the lookup service) associated with the task represented by
         *  the current instance of this class.
         */
        public ProxyReg getProxyReg() {
            return proxyReg;
        }//end getProxy

        /** Accessor method that returns the unique sequence number associated
         *  with the task represented by the current instance of this class.
         */
        public int getSeqN() {
            return seqN;
        }//end getSeqN

        /** Convenience method called by the child tasks when they encounter
         *  an exception. If the given exception indicates that retrying the
         *  task would definitely fail, or if the maximum allowable number
         *  of retries of the task has been exceeded, then this method will
         *  do the following:
         *    - remove all pending tasks that are to be run after this task
         *    - cancel this task
         *    - discard the look service associated with this task
         *    - return <code>true</code> (which stops the wakeup manager
         *      from retrying this task
         *  otherwise, this method returns <code>false</code>, which indicates
         *  that the wakeup manager should not stop trying to successfully
         *  execute the task.
         */
        protected boolean stopTrying(Exception e) {
            int exCat = ThrowableConstants.retryable(e);
            if(    (exCat != ThrowableConstants.INDEFINITE)
                || (nRetries >= maxNRetries) )
            {
                synchronized(joinSet) {
                    removeTasks(proxyReg);//cancel and clear all related tasks
                }//end sync(joinSet)
                proxyReg.fail(e);
                return true;//don't try again
            }//endif
            logger.log(Level.FINER,
                       "JoinManager - failure, will retry later", e);
            return false;//try this task again later
        }//end stopTrying
    }//end class ProxyRegTask

    /** Abstract base class from which all the sub-task classes are derived. */
    private abstract class JoinTask {

        /** Data structure referencing the task's associated lookup service */
        protected ProxyReg proxyReg;

        /** Basic constructor; simply stores the input parameters */
        JoinTask(ProxyReg proxyReg) {
            this.proxyReg = proxyReg;
        }//end constructor

        /** Method executed in a separate thread created by the task manager */
	public abstract void run() throws Exception;

    }//end class JoinTask

    /** Task that asynchronously registers the service associated with this
     *  join manager with the lookup service referenced by the current
     *  instance of this class.
     */
    private class RegisterTask extends JoinTask {
        /** Attributes with which to register the service. These attributes
         *  must not change during the registration process performed in
         *  this this task.
         */
        Entry[] regAttrs;

        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   with which the service is to be registered
         *  @param regAttrs  array of Entry objects whose elements are the
         *                   attributes with which to register the service.
         *                   The caller of this constructor should take steps
         *                   to guarantee that the contents of this parameter
         *                   do not change during the registration process
         *                   performed in this task.
         */
        RegisterTask(ProxyReg proxyReg, Entry[] regAttrs) {
            super(proxyReg);
            this.regAttrs = regAttrs;
	}//end constructor

        /** Attempts to register this join manager's service with the lookup
         *  service referenced in this task's proxyReg field.
         */
        public void run() throws Exception {
            logger.finest("JoinManager - RegisterTask started");
            proxyReg.register(regAttrs);
            logger.finest("JoinManager - RegisterTask completed");
        }//end run

    }//end class RegisterTask

    /** Task that asynchronously re-registers the service associated with this
     *  join manager with the lookup service referenced by the current
     *  instance of this class.
     *  
     *  This task is typically executed when the service's lease with the
     *  referenced lookup service has expired.
     */
    private class LeaseExpireNotifyTask extends JoinTask {
        /** Attributes with which to re-register the service. These attributes
         *  must not change during the registration process performed in
         *  this this task.
         */
        Entry[] regAttrs;
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   with which the service is to be re-registered
         *  @param regAttrs  array of Entry objects whose elements are the
         *                   attributes with which to re-register the service.
         *                   The caller of this constructor should take steps
         *                   to guarantee that the contents of this parameter
         *                   do not change during the registration process
         *                   performed in this task.
         */
        LeaseExpireNotifyTask(ProxyReg proxyReg, Entry[] regAttrs) {
            super(proxyReg);
            this.regAttrs = regAttrs;
	}//end constructor

        /** Attempts to re-register this join manager's service with the
         *  lookup service referenced by the current instance of this class.
         */
        public void run() throws Exception {
            logger.finest("JoinManager - LeaseExpireNotifyTask started");
            boolean tryIt = false;
            synchronized(joinSet) {
                tryIt = joinSet.contains(proxyReg);
            }//end sync(joinSet)
            if(tryIt)  proxyReg.register(regAttrs);
            logger.finest("JoinManager - LeaseExpireNotifyTask completed");
	}//end run

    }//end class LeaseExpireNotifyTask

    /** Task that asynchronously removes the <code>ProxyReg</code> object --
     *  corresponding to the lookup service referenced by the current instance
     *  of this class -- from the <code>joinSet</code>, and cancels the lease
     *  on the associated <code>ServiceRegistration</code>.
     *  
     *  Instances of this task are typically executed when one or more of
     *  the previously discovered lookup services are discarded.
     */
    private class DiscardProxyTask extends JoinTask {
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the discarded
         *                   lookup service whose corresponding
         *                   <code>ProxyReg</code> reference is to be removed
         *                   from the join set
         */
        DiscardProxyTask(ProxyReg proxyReg) {
            super(proxyReg);
	}//end constructor

        /** Removes from the <code>joinSet</code>, the <code>ProxyReg</code>
         *  object that corresponds to the lookup service referenced in the
         *  current instance of this class, and cancels the lease on each of
         *  the associated <code>ServiceRegistration</code>.
         */
        public void run() {
            logger.finest("JoinManager - DiscardProxyTask started");
            removeJoinSet(proxyReg);
            logger.finest("JoinManager - DiscardProxyTask completed");
	}//end run

    }//end class DiscardProxyTask

    /** Task that asynchronously augments the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private class AddAttributesTask extends JoinTask {
        /** The new attribute values with which the service's current
         *  attributes will be augmented, replaced, or changed.
         */
	protected Entry[] attrSets;

        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   in which the service's attributes should be
         *                   augmented 
         *  @param newAttrs  the attributes with which to augment the 
         *                   service's current set of attributes 
         */
        AddAttributesTask(ProxyReg proxyReg, Entry[] newAttrs) {
            super(proxyReg);
	    this.attrSets = (Entry[])newAttrs.clone();
	}//end constructor

        /** Performs the actual attribute augmentation, replacement, or
         *  modification work. This method is typically overridden by
         *  sub-classes of this class.
         */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - AddAttributesTask started");
	    proxyReg.addAttributes(attrSets);
            logger.finest("JoinManager - AddAttributesTask completed");
        }//end AddAttributesTask.doAttributes

        /** Attempts to either augment, replace, or modify the attributes
         *  of this join manager's service in the lookup service referenced
         *  by the current instance of this class. Which action is taken --
         *  augmentation, replacement, or modification -- is dependent on the
         *  definition of the <code>doAttributes/code> method.
         */
        public void run() throws Exception {
            doAttributes(proxyReg);
	}//end run

    }//end class AddAttributesTask

    /** Task that asynchronously replaces the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private final class SetAttributesTask extends AddAttributesTask {
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg         data structure that references the lookup
         *                          service in which the service's attributes
         *                          should be replaced 
         *  @param replacementAttrs the attributes with which to replace the 
         *                          service's current set of attributes 
         */
        SetAttributesTask(ProxyReg proxyReg, Entry[] replacementAttrs){
            super(proxyReg, replacementAttrs);
	}//end constructor

        /** Performs the actual attribute replacement work. */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - SetAttributesTask started");
	    proxyReg.setAttributes(attrSets);
            logger.finest("JoinManager - SetAttributesTask completed");
	}//end SetAttributesTask.doAttributes

    }//end class SetAttributesTask

    /** Task that asynchronously modifies the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private final class ModifyAttributesTask extends AddAttributesTask {
	private Entry[] attrSetTemplates;
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg         data structure that references the lookup
         *                          service in which the service's attributes
         *                          should be changed 
         *  @param attrSetTemplates the attribute templates that are used to
         *                          select (through attribute matching) the
         *                          attributes to change in the service's
         *                          current set of attributes 
         *  @param attrChanges      the attributes containing the changes to
         *                          make to the attributes in the service's
         *                          current set that are selected through
         *                          attribute matching with the
         *                          attrSetTemplates parameter
         */
        ModifyAttributesTask( ProxyReg proxyReg,
                              Entry[] attrSetTemplates,
                              Entry[] attrChanges )
        {
            super(proxyReg, attrChanges);
	    this.attrSetTemplates = (Entry[])attrSetTemplates.clone();
	}//end constructor

        /** Performs the actual attribute modification work. */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - ModifyAttributesTask started");
	    proxyReg.modifyAttributes(attrSetTemplates, attrSets);
            logger.finest("JoinManager - ModifyAttributesTask completed");
	}//end ModifyAttributesTask.doAttributes

    }//end class ModifyAttributesTask

    /** Wrapper class in which each instance corresponds to a lookup
     *  service to discover, and with which this join manager's service
     *  should be registered.
     */
    private class ProxyReg {
        /** Class that is registered as a listener with this join manager's
         *  lease renewal manager. That lease renewal manager manages the
         *  lease granted to this join manager's associated service by the
         *  lookup service referenced in the proxy object associated with
         *  this class (<code>ProxyReg</code>).
         *
         *  If the lease expires in the lookup service before the lease 
         *  renewal manager requests renewal of the lease, then upon sending
         *  that renewal request, the lease renewal manager will receive an
         *  <code>UnknownLeaseException</code> from the lookup service.
         *  As a result, the lease renewal manager then sends to this
         *  listener a <code>LeaseRenewalEvent</code>, containing an
         *  <code>UnknownLeaseException</code>.
         *
         *  Next, suppose that at the time the lease renewal manager is
         *  about to request the renewal of the lease, the lease renewal
         *  manager determines that the expiration time of the lease has
         *  actually passed. In this case, since there is no reason to send
         *  the renewal request, the lease renewal manager instead sends
         *  a <code>LeaseRenewalEvent</code> to this listener to indicate
         *  that the lease has expired. The difference between this case,
         *  and the case described previously is that in this case the
         *  <code>LeaseRenewalEvent</code> contains no exception (that is,
         *  <code>LeaseRenewalEvent.getException</code> returns 
         *  <code>null</code>).
         *
         *  Since both situations described above indicate that the lease
         *  has expired, for either situation this class will queue a
         *  <code>LeaseExpireNotifyTask</code> which attempts to re-register
         *  the associated lookup service proxy. For any other exceptional
         *  condition, this class will perform failure processing.
         */
	private class DiscLeaseListener implements LeaseListener {
  	    public void notify(LeaseRenewalEvent e) {
                Throwable ex = e.getException();
		if ( (ex == null) || (ex instanceof UnknownLeaseException) ) {
                    synchronized(joinSet) {
                        removeTasks(ProxyReg.this);
                        addTask(new LeaseExpireNotifyTask
                                  (ProxyReg.this,(Entry[])lookupAttr.clone()));
                    }//end sync(joinSet)
		} else {
		    fail(ex);
                }//endif
	    }//end notify
	}//end class DiscLeaseListener

        /** The <code>ProxyRegTask</code> that instantiated this
         *  <code>ProxyReg</code>.
         */
        public ProxyRegTask proxyRegTask;
        /** The <i>prepared</i> proxy to the lookup service referenced by
         *  this class, and with which this join manager's service will be
         *  registered.
         */
	public ServiceRegistrar proxy;
        /** The <i>prepared</i> registration proxy returned by this class'
         *  associated lookup service when this join manager registers its
         *  associated service.
         */
	public ServiceRegistration srvcRegistration = null;
        /* The <i>prepared</i> proxy to the lease on the registration of this
         * join manager's service with the this class' associated lookup
         * service.
         */
	public Lease serviceLease = null;
        /** The set of sub-tasks that are to be executed in order for the
         *  lookup service associated with the current instance of this class.
         */
        public List taskList = new ArrayList(1);
        /** The instance of <code>DiscLeaseListener</code> that is registered
         *  with the lease renewal manager that handles the lease of this join
         *  manger's service.
         */
	private DiscLeaseListener dListener = new DiscLeaseListener();

        /** Constructor that associates this class with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg data structure that references the lookup service
         *                  on which the sub-tasks referenced in this class
         *                  will be executed in order
         */
	public ProxyReg(ServiceRegistrar proxy) {
	    if(proxy == null)  throw new IllegalArgumentException
                                                      ("proxy can't be null");
	    this.proxy = proxy;
	}//end constructor	    

        /** Convenience method that adds new sub-tasks to this class' 
         *  task queue.
         *
         *  @param task the task to add to the task queue
         */
        public void addTask(JoinTask task) {
            synchronized(JoinManager.this) {
                if(bTerminated) return;
            }//end sync
            synchronized(taskList) {
                taskList.add(task);
                if(this.proxyRegTask == null) {
                    this.proxyRegTask = new ProxyRegTask(this,taskSeqN++);
                    synchronized (taskMgr) {
                        taskMgr.add(this.proxyRegTask);
                    }//end sync(taskMgr)
                }//endif
            }//end sync(taskList)
        }//end addTask

        /** Registers the service associated with this join manager with the
         *  the lookup service corresponding to this class. Additionally,
         *  this method retrieves the lease granted by the lookup service
         *  on the service's registration, and passes that lease to the
         *  lease renewal manager. If a <code>ServiceIDListener</code> 
         *  has been registered with this join manager, this method will
         *  send to that listener a notification containing the service's ID.
         */
        public void register(Entry[] srvcAttrs) throws Exception {
            if(proxy == null) throw new RuntimeException("proxy is null");
            /* The lookup service proxy was already prepared at discovery */
            ServiceItem tmpSrvcItem = null;
            synchronized(joinSet) {
                srvcRegistration = null;
                tmpSrvcItem = new ServiceItem(serviceItem.serviceID,
                                              serviceItem.service,
                                              srvcAttrs);
            }//end sync(joinSet)
            /* Retrieve and prepare the proxy to the service registration */
            ServiceRegistration tmpSrvcRegistration 
                                 = proxy.register(tmpSrvcItem, Long.MAX_VALUE);
            try {
                tmpSrvcRegistration = 
                   (ServiceRegistration)registrationPreparer.prepareProxy
                                                       ( tmpSrvcRegistration );
                logger.finest
                          ("JoinManager - ServiceRegistration proxy prepared");
            } catch(Exception e) {
                logger.log(Level.WARNING,
                           "failure during preparation of "
                           +"ServiceRegistration proxy",
                           e);
                throw e; //rethrow the exception since proxy may be unusable
            }
            /* Retrieve and prepare the proxy to the service lease */
            serviceLease = tmpSrvcRegistration.getLease();
            try {
                serviceLease = 
                       (Lease)serviceLeasePreparer.prepareProxy(serviceLease);
                logger.finest("JoinManager - service lease proxy prepared");
            } catch(Exception e) {
                logger.log(Level.WARNING,
                           "failure during preparation of service lease proxy",
                           e);
                throw e; //rethrow the exception since proxy may be unusable
            }
            leaseRenewalMgr.renewUntil(serviceLease,Long.MAX_VALUE,dListener);
            synchronized(joinSet) {
                srvcRegistration = tmpSrvcRegistration;
            }//end sync(joinSet)
            ServiceID tmpID = null;
            synchronized(serviceItem) {
                if(serviceItem.serviceID == null) {
                    serviceItem.serviceID = srvcRegistration.getServiceID();
                    tmpID = serviceItem.serviceID;
                }//endif
            }//end sync(serviceItem)
            if( (tmpID != null) && (callback != null) )  {
                callback.serviceIDNotify(tmpID);
            }//endif
        }//end ProxyReg.register

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method associates with that service a new set of attributes -- in
         *  addition to that service's current set of attributes.
         */
        public void addAttributes(Entry[] attSet) throws Exception {
            srvcRegistration.addAttributes(attSet);
	}//end ProxyReg.addAttributes

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method changes that service's current attributes by selecting
         *  the attributes to change using the given first parameter;
         *  and identifying the desired changes to make through the
         *  contents of the second parameter.
         */
        public void modifyAttributes(Entry[] templ, Entry[] attSet)
                                                             throws Exception
        {
            srvcRegistration.modifyAttributes(templ, attSet);
	}//end ProxyReg.modifyAttributes		    

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method replaces that service's current attributes with a new
         *  set of attributes.
         */
        public void setAttributes(Entry[] attSet) throws Exception {
            srvcRegistration.setAttributes(attSet);
	}//end ProxyReg.setAttributes

        /** Convenience method that encapsulates appropriate behavior when
         *  failure is encountered related to the current instance of this
         *  class. This method discards the lookup service proxy associated
         *  with this object, and logs the stack trace of the given
         *  <code>Throwable</code> according to the logging levels specified
         *  for this utility.
         *  
         *  Note that if the discovery manager employed by this join manager
         *  has been terminated, then the attempt to discard the lookup 
         *  service proxy will result in an <code>IllegalStateException</code>.
         *  Since this method is called only within the tasks run by
         *  this join manager, and since propagating an
         *  <code>IllegalStateException</code> out into the
         *  <code>ThreadGroup</code> of those tasks is undesirable, this
         *  method will not propagate the <code>IllegalStateException</code>
         *  that occurs as a result of an attempt to discard a lookup
         *  service proxy from the discovery manager employed by this
         *  join manager.
         * 
         * For more information, refer to Bug 4490355.
         */
	public void fail(Throwable e) {
            synchronized(this) {
                if(bTerminated) {
                    return;
                } else {
                    logger.log(Level.INFO, "JoinManager - failure", e);
                    try {
                        discMgr.discard(proxy);
                    } catch(IllegalStateException e1) {
                       logger.log(Level.FINEST,
                                  "JoinManager - cannot discard lookup, "
                                  +"discovery manager already terminated",
                                  e1);
                    }
                }//endif
            }//end sync(this)
	}//end ProxyReg.fail

        /** Returns true if the both objects' associated proxies are equal. */
	public boolean equals(Object obj) {
	    if (obj instanceof ProxyReg) {
		return proxy.equals( ((ProxyReg)obj).proxy );
	    } else {
                return false;
            }//endif
	}//end ProxyReg.equals

        /** Returns the hash code of the proxy referenced in this class. */
	public int hashCode() {
	    return proxy.hashCode();
	}//end hashCode

    }//end class ProxyReg

    /* Listener class for discovery/discard notification of lookup services. */
    private class DiscMgrListener implements DiscoveryListener {
        /* Invoked when new or previously discarded lookup is discovered. */
	public void discovered(DiscoveryEvent e) {
            ArrayList taskList = null;
            synchronized(joinSet) {
                ServiceRegistrar[] proxys
                                       = (ServiceRegistrar[])e.getRegistrars();
                for(int i=0;i<proxys.length;i++) {
                    /* Prepare the proxy to the discovered lookup service
                     * before interacting with it.
                     */
                    try {
                        proxys[i]
                          = (ServiceRegistrar)registrarPreparer.prepareProxy
                                                                   (proxys[i]);
                        logger.log(Level.FINEST, "JoinManager - discovered "
                                   +"lookup service proxy prepared: {0}",
                                   proxys[i]);
                    } catch(Exception e1) {
                        logger.log(Level.INFO,
                                   "failure preparing discovered "
                                   +"ServiceRegistrar proxy",
                                   e1);
                        discMgr.discard(proxys[i]);
                        continue;
                    }
		    /* If the serviceItem is a lookup service, don't need to
                     * register it with itself since a well-defined lookup
                     * service will always register with itself.
                     */
                    if( !proxys[i].equals(serviceItem.service) ) {
		        ProxyReg proxyReg = new ProxyReg(proxys[i]);
		        if( !joinSet.contains(proxyReg) ) {
	                    joinSet.add(proxyReg);
                            proxyReg.addTask(new RegisterTask
                                                (proxyReg,
                                                 (Entry[])lookupAttr.clone()));
                        }//endif
		    }//endif
                }//end loop
            }//end sync(joinSet)
	}//end discovered

        /* Invoked when previously discovered lookup is discarded. */
	public void discarded(DiscoveryEvent e) {
            ArrayList taskList = null;
            synchronized(joinSet) {
                ServiceRegistrar[] proxys
                                      = (ServiceRegistrar[])e.getRegistrars();
                for(int i=0;i<proxys.length;i++) {
                    ProxyReg proxyReg = findReg(proxys[i]);
		    if(proxyReg != null) {
                        removeTasks(proxyReg);
                        proxyReg.addTask(new DiscardProxyTask(proxyReg));
		    }//endif
                }//end loop
            }//end sync(joinSet)
	}//end discarded
    }//end class DiscMgrListener

    /* Name of this component; used in config entry retrieval and the logger.*/
    private static final String COMPONENT_NAME = "net.jini.lookup.JoinManager";
    /* Logger used by this utility. */
    private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
    /** Maximum number of concurrent tasks that can be run in any default
     * task manager created by this class.
     */
    private static final int MAX_N_TASKS = 15;
    /** Whenever a task is created in this join manager, it is assigned a
     *  unique sequence number so that the task is not run prior to the
     *  execution, and completion of, any other tasks with which that task
     *  is associated (tasks are grouped by the <code>ProxyReg</code> with
     *  which each task is associated). This field contains the value of
     *  the sequence number assigned to the most recently created task.
     */
    private int taskSeqN = 0;
    /** Task manager for the various tasks executed by this join manager.
     *  On the first attempt to execute any task is managed by this
     *  <code>TaskManager</code> so that the number of concurrent threads
     *  can be bounded. If one or more of those attempts fails, a
     *  <code>WakeupManager</code> is used (through the use of a
     *  <code>RetryTask</code>) to schedule - at a later time (employing a
     *  "backoff strategy") - the re-execution of each failed task in this
     *  <code>TaskManager</code>.
     */
    private TaskManager taskMgr;
    /** Maximum number of times a failed task is allowed to be re-executed. */
    private int maxNRetries = 6;
    /** Wakeup manager for the various tasks executed by this join manager.
     *  After an initial failure of any task executed by this join manager,
     *  the failed task is managed by this <code>WakeupManager</code>; which
     *  schedules the re-execution of the failed task - in the task manager -
     *  at various times in the future until either the task succeeds or the
     *  task has been executed the maximum number of allowable times. This
     *  wakeup manager is supplied to the <code>RetryTask</code>) that
     *  performs the actual task execution so that when termination of this
     *  join manager is requested, all tasks scheduled for retry by this
     *  wakeup manager can be cancelled.
     */
    private WakeupManager wakeupMgr;
    /** Contains the reference to the service that is to be registered with
     *  all of the desired lookup services referenced by <code>discMgr</code>.
     */
    private ServiceItem serviceItem;
    /** Contains the attributes with which to associate the service in each
     *  of the lookup services with which this join manager registers the
     *  service.
     */
    private Entry[] lookupAttr = null;
    /** Contains the listener -- instantiated by the entity that constructs
     *  this join manager -- that will receive an event containing the
     *  service ID assigned to this join manager's service by one of the
     *  lookup services with which that service is registered.
     */
    private ServiceIDListener callback;
    /** Contains elements of type <code>ProxyReg</code> where each element
     *  references a proxy to one of the lookup services with which this
     *  join manager's service is registered.
     */
    private final ArrayList joinSet = new ArrayList(1);
    /** Contains the discovery manager that discovers the lookup services
     *  with which this join manager will register its associated service.
     */
    private DiscoveryManagement discMgr = null;
    /** Contains the discovery listener registered by this join manager with
     *  the discovery manager so that this join manager is notified whenever
     *  one of the desired lookup services is discovered or discarded.
     */
    private DiscMgrListener discMgrListener = new DiscMgrListener();
    /** Flag that indicate whether the discovery manager employed by this
     *  join manager was created by this join manager itself, or by the
     *  entity that constructed this join manager.
     */
    private boolean bCreateDiscMgr = false;
    /** Contains the lease renewal manager that renews all of the leases
     *  this join manager's service holds with each lookup service with which
     *  it has been registered.
     */
    private LeaseRenewalManager leaseRenewalMgr = null;
    /** Flag that indicates if this join manager has been terminated. */
    private boolean bTerminated = false;
    /* 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 registrations returned to this utility
     * upon registering the service with each discovered lookup service.
     */
    private ProxyPreparer registrationPreparer;
    /* Preparer for the proxies to the leases returned to this utility through
     * the registrations with each discovered lookup service with which this
     * utility has registered the service.
     */
    private ProxyPreparer serviceLeasePreparer;

    /** 
     * Constructs an instance of this class that will notify the service
     * when a service ID is assigned by the first lookup service with which
     * the service is registered. This constructor is typically used by
     * services that have not yet been assigned a service ID.
     * <p>
     * Note that this form of the constructor applies the same semantics
     * to the <code>attrSets</code>, <code>discoveryMgr</code>, and
     * <code>leaseMgr</code> parameters as is applied by all other forms
     * of the constructor.
     *
     * @param obj          a reference to the service requesting the services
     *                     of this class.
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service.
     * @param callback     a reference to an object that implements the
     *                     <code>ServiceIDListener</code> interface, which 
     *                     will be sent an event containing the service ID
     *                     assigned to the service by the lookup service.
     * @param discoveryMgr a reference to an object that implements the 
     *                     <code>DiscoveryManagement</code> interface which 
     *                     this class will use to manage the service's 
     *                     discovery duties. If <code>null</code> is input
     *                     to this argument, an instance of the utility class
     *                     <code>LookupDiscoveryManager</code> will be used to
     *                     listen for events announcing the discovery of only
     *                     those lookup services that are members of the public
     *                     group.
     * @param leaseMgr     an instance of the <code>LeaseRenewalManager</code>
     *                     utility class. If <code>null</code> is input
     *                     to this argument, an instance of the
     *                     <code>LeaseRenewalManager</code> class will be
     *                     created, initially managing no leases. This 
     *                     feature allows a service that employs the 
     *                     <code>JoinManager</code> to use either a single
     *                     entity to manage all of its leases, or to use
     *                     separate entities: one to manage the leases
     *                     unrelated to the join process, and one to manage the
     *                     leases that result from the join process and which
     *                     are accessible only within the instance of
     *                     <code>JoinManager</code> created by this
     *                     constructor.
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @see net.jini.lookup.ServiceIDListener
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object obj,
                        Entry[] attrSets,
			ServiceIDListener callback,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(null, obj, attrSets, callback, 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
     * notify the service when a service ID is assigned by the first lookup
     * service with which the service is registered. This constructor is
     * typically used by services that have not yet been assigned a service ID.
     * <p>
     * Note that this form of the constructor applies the same semantics
     * to the <code>attrSets</code>, <code>discoveryMgr</code>, and
     * <code>leaseMgr</code> parameters as is applied by all other forms
     * of the constructor.
     *
     * @param obj          a reference to the service requesting the services
     *                     of this class.
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service.
     * @param callback     a reference to an object that implements the
     *                     <code>ServiceIDListener</code> interface, which 
     *                     will be sent an event containing the service ID
     *                     assigned to the service by the lookup service.
     * @param discoveryMgr a reference to an object that implements the 
     *                     <code>DiscoveryManagement</code> interface which 
     *                     this class will use to manage the service's 
     *                     discovery duties. If <code>null</code> is input
     *                     to this argument, an attempt will first be made to
     *                     retrieve from the given <code>Configuration</code>,
     *                     a discovery manager entry as described above. If
     *                     such an object is successfully retrieved, that 
     *                     object will be used to perform the lookup service
     *                     discovery processing required by this utility. If
     *                     <code>null</code> is input to this argument, and
     *                     no discovery manager entry can be retrieved from
     *                     the given <code>Configuration</code> (because of 
     *                     failure or non-existence), then an instance of the
     *                     utility class <code>LookupDiscoveryManager</code>
     *                     will be used to listen for events announcing the
     *                     discovery of only those lookup services that are
     *                     members of the public group.
     * @param leaseMgr     an instance of the <code>LeaseRenewalManager</code>
     *                     utility class. If <code>null</code> is input
     *                     to this argument, an attempt will first be made to
     *                     retrieve from the given <code>Configuration</code>,
     *                     a lease renewal manager entry as described above.
     *                     If such an object is successfully retrieved, that 
     *                     object will be used to perform the lease management
     *                     processing required by this utility. If
     *                     <code>null</code> is input to this argument, and
     *                     no lease renewal manager entry can be retrieved from
     *                     the given <code>Configuration</code> (because of 
     *                     failure or non-existence), then an instance of the
     *                     utility class <code>LeaseRenewalManager</code> that
     *                     takes the given <code>Configuration</code> will be
     *                     created and used to perform all required lease
     *                     renewal management.
     * @param config       an instance of <code>Configuration</code>, used to
     *                     obtain the objects needed to configure the current
     *                     instance of this class
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @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.lookup.ServiceIDListener
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
     public JoinManager(Object obj,
                        Entry[] attrSets,
			ServiceIDListener callback,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(null, obj, attrSets, callback, leaseMgr, config);
    }//end constructor

    /** 
     * Constructs an instance of this class that will register the service 
     * with all discovered lookup services, using the supplied service ID.
     * This constructor is typically used by services that have already been
     * assigned a service ID (possibly by the service provider itself or as
     * a result of a prior registration with some lookup service).
     * <p>
     * Note that this form of the constructor applies the same semantics
     * to the <code>attrSets</code>, <code>discoveryMgr</code>, and
     * <code>leaseMgr</code> parameters as is applied by all other forms
     * of the constructor.
     *
     * @param obj          a reference to the service requesting the services
     *                     of this class.
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service.
     * @param serviceID    an instance of <code>ServiceID</code> with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr a reference to an object that implements the 
     *                     <code>DiscoveryManagement</code> interface which 
     *                     this class will use to manage the service's 
     *                     discovery duties. If <code>null</code> is input
     *                     to this argument, an instance of the utility class
     *                     <code>LookupDiscoveryManager</code> will be used to
     *                     listen for events announcing the discovery of only
     *                     those lookup services that are members of the public
     *                     group.
     * @param leaseMgr     an instance of the <code>LeaseRenewalManager</code>
     *                     utility class. If <code>null</code> is input
     *                     to this argument, an instance of the
     *                     <code>LeaseRenewalManager</code> class will be
     *                     created, initially managing no leases. This 
     *                     feature allows a service that employs the 
     *                     <code>JoinManager</code> to use either a single
     *                     entity to manage all of its leases, or to use
     *                     separate entities: one to manage the leases
     *                     unrelated to the join process, and one to manage the
     *                     leases that result from the join process and which
     *                     are accessible only within the instance of
     *                     <code>JoinManager</code> created by this
     *                     constructor.
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @see net.jini.core.lookup.ServiceID
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object obj,
                        Entry[] attrSets,
			ServiceID serviceID,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(serviceID, obj, attrSets, callback, 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
     * register the service with all discovered lookup services, using the
     * supplied service ID. This constructor is typically used by services
     * that have already been assigned a service ID (possibly by the service
     * provider itself or as a result of a prior registration with some lookup
     * service).
     * <p>
     * Note that this form of the constructor applies the same semantics
     * to the <code>attrSets</code>, <code>discoveryMgr</code>, and
     * <code>leaseMgr</code> parameters as is applied by all other forms
     * of the constructor.
     *
     * @param obj          a reference to the service requesting the services
     *                     of this class.
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service.
     * @param serviceID    an instance of <code>ServiceID</code> with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr a reference to an object that implements the 
     *                     <code>DiscoveryManagement</code> interface which 
     *                     this class will use to manage the service's 
     *                     discovery duties. If <code>null</code> is input
     *                     to this argument, an attempt will first be made to
     *                     retrieve from the given <code>Configuration</code>,
     *                     a discovery manager entry as described above. If
     *                     such an object is successfully retrieved, that 
     *                     object will be used to perform the lookup service
     *                     discovery processing required by this utility. If
     *                     <code>null</code> is input to this argument, and
     *                     no discovery manager entry can be retrieved from
     *                     the given <code>Configuration</code> (because of 
     *                     failure or non-existence), then an instance of the
     *                     utility class <code>LookupDiscoveryManager</code>
     *                     will be used to listen for events announcing the
     *                     discovery of only those lookup services that are
     *                     members of the public group.
     * @param leaseMgr     an instance of the <code>LeaseRenewalManager</code>
     *                     utility class. If <code>null</code> is input
     *                     to this argument, an attempt will first be made to
     *                     retrieve from the given <code>Configuration</code>,
     *                     a lease renewal manager entry as described above.
     *                     If such an object is successfully retrieved, that 
     *                     object will be used to perform the lease management
     *                     processing required by this utility. If
     *                     <code>null</code> is input to this argument, and
     *                     no lease renewal manager entry can be retrieved from
     *                     the given <code>Configuration</code> (because of 
     *                     failure or non-existence), then an instance of the
     *                     utility class <code>LeaseRenewalManager</code> that
     *                     takes the given <code>Configuration</code> will be
     *                     created and used to perform all required lease
     *                     renewal management.
     * @param config       an instance of <code>Configuration</code>, used to
     *                     obtain the objects needed to configure the current
     *                     instance of this class
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @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.core.lookup.ServiceID
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
     public JoinManager(Object obj,
                        Entry[] attrSets,
			ServiceID serviceID,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(serviceID,obj,attrSets,callback,leaseMgr,config);
    }//end constructor

    /** 
     * Returns the instance of <code>DiscoveryManagement</code> that was
     * either passed into the constructor, or that was created as a result
     * of <code>null</code> being input to that parameter.
     * <p>
     * The object returned by this method encapsulates the mechanism by which
     * either the <code>JoinManager</code> or the entity itself can set
     * discovery listeners and discard previously discovered lookup services
     * when they are found to be unavailable.
     *
     * @return the instance of the <code>DiscoveryManagement</code> interface
     *         that was either passed into the constructor, or that was
     *         created as a result of <code>null</code> being input to that
     *         parameter.
     * 
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     */
    public DiscoveryManagement getDiscoveryManager(){
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	return discMgr; 
    }//end getDiscoveryManager

    /** 
     * Returns the instance of the <code>LeaseRenewalManager</code> class
     * that was either passed into the constructor, or that was created
     * as a result of <code>null</code> being input to that parameter.
     * <p>
     * The object returned by this method manages the leases requested and
     * held by the <code>JoinManager</code>. Although it may also manage
     * leases unrelated to the join process that are requested and held by
     * the service itself, the leases with which the <code>JoinManager</code>
     * is concerned are the leases that correspond to the service registration
     * requests made with each lookup service the service wishes to join.
     *
     * @return the instance of the <code>LeaseRenewalManager</code> class
     *         that was either passed into the constructor, or that was
     *         created as a result of <code>null</code> being input to that
     *         parameter.
     * 
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.lease.LeaseRenewalManager
     */
    public LeaseRenewalManager getLeaseRenewalManager(){
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	return leaseRenewalMgr;
    }//end getLeaseRenewalManager

    /** 
     * Returns an array of <code>ServiceRegistrar</code> objects, each
     * corresponding to a lookup service with which the service is currently
     * registered (joined). If there are no lookup services with which the
     * service is currently registered, this method returns the empty array.
     * This method returns a new array upon each invocation.
     *
     * @return array of instances of <code>ServiceRegistrar</code>, each
     *         corresponding to a lookup service with which the service is
     *         currently registered 
     * 
     * @see net.jini.core.lookup.ServiceRegistrar
     */
    public ServiceRegistrar[] getJoinSet() {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	synchronized(joinSet) {
            ArrayList retList = new ArrayList(joinSet.size());
	    int k = 0;
	    for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
                ProxyReg proxyReg = (ProxyReg)iter.next();
                if(proxyReg.srvcRegistration != null) {//test registration flag
                    retList.add(proxyReg.proxy);
                }//endif
	    }//end loop
            return ( (ServiceRegistrar[])(retList.toArray
                                 (new ServiceRegistrar[retList.size()]) ) );
	}//end sync(joinSet)
    }//end getJoinSet

    /** 
     * Returns an array containing the set of attributes currently associated
     * with the service. If the service is not currently associated with an
     * attribute set, this method returns the empty array. This method returns
     * a new array upon each invocation.
     *
     * @return array of instances of <code>Entry</code> consisting of the
     *         set of attributes with which the service is registered in
     *         each lookup service that it has joined
     * 
     * @see net.jini.core.entry.Entry
     * @see #setAttributes
     */
    public Entry[] getAttributes() {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	synchronized(joinSet) {
	    return (Entry[])lookupAttr.clone();
	}//end sync(serviceItem)
    }//end getAttributes

    /** 
     * Associates a new set of attributes with the service, in addition to
     * the service's current set of attributes. The association of this new
     * set of attributes with the service will be propagated to each lookup
     * service with which the service is registered. Note that this
     * propagation is performed asynchronously, thus there is no guarantee
     * that the propagation of the attributes to all lookup services with
     * which the service is registered will have completed upon return from
     * this method.
     * <p>
     * An invocation of this method with duplicate elements in the 
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that 
     * invoke this method must take care not to modify the contents of the 
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either <code>null</code> is input to the <code>attrSets</code>
     *         parameter, or one or more of the elements of the
     *         <code>attrSets</code> parameter is <code>null</code>.
     *
     * @see net.jini.core.entry.Entry
     */
    public void addAttributes(Entry[] attrSets) {
	addAttributes(attrSets, false);
    }//end addAttributes

    /** 
     * Associates a new set of attributes with the service, in addition to
     * the service's current set of attributes. The association of this new
     * set of attributes with the service will be propagated to each lookup
     * service with which the service is registered. Note that this
     * propagation is performed asynchronously, thus there is no guarantee
     * that the propagation of the attributes to all lookup services with
     * which the service is registered will have completed upon return from
     * this method.
     * <p>
     * An invocation of this method with duplicate elements in the 
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that 
     * invoke this method must take care not to modify the contents of the 
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     * <p>
     * A service typically employs this version of <code>addAttributes</code> 
     * to prevent clients or other services from attempting to add what are
     * referred to as "service controlled attributes" to the service's set.
     * A service controlled attribute is an attribute that implements the
     * <code>ServiceControlled</code> marker interface.
     * <p>
     * Consider a printer service. With printers, there are often times error
     * conditions, that only the printer can detect (for example, a paper
     * jam or a toner low condition). To report conditions such as these to
     * interested parties, the printer typically adds an attribute to its
     * attribute set, resulting in an event being sent that notifies clients
     * that have registered interest in such events. When the condition is
     * corrected, the printer would then remove the attribute from its set
     * by invoking the <code>modifyAttributes</code> method in the appropriate
     * manner.
     * <p>
     * Attributes representing conditions that only the service can know about
     * or control are good candidates for being defined as service controlled
     * attributes. That is, the service provider (the developer of the printer
     * service for example) would define the attributes that represent
     * conditions such as those just described to implement the
     * <code>ServiceControlled</code> marker interface. Thus, when other
     * entities attempt to add new attributes, services that wish to employ
     * such service controlled attributes should ultimately invoke only this
     * version of <code>addAttributes</code> (with the <code>checkSC</code>
     * parameter set to <code>true</code>), resulting in a
     * <code>SecurityException</code> if any of the attributes being added
     * happen to be service controlled attributes. In this way, only the
     * printer itself would be able to set a "paper jammed" or "toner low"
     * attribute, not some arbitrary client.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     * @param checkSC  <code>boolean</code> flag indicating whether the
     *                 elements of the set of attributes to add should be
     *                 checked to determine if they are service controlled
     *                 attributes
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either <code>null</code> is input to the <code>attrSets</code>
     *         parameter, or one or more of the elements of the
     *         <code>attrSets</code> parameter is <code>null</code>.
     *
     * @throws java.lang.SecurityException this exception occurs when
     *         the <code>checkSC</code> parameter is <code>true</code>, and
     *         at least one of the attributes to be added is an instance
     *         of the <code>ServiceControlled</code> marker interface.
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.lookup.entry.ServiceControlled
     */
    public void addAttributes(Entry[] attrSets, boolean checkSC) {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	synchronized(joinSet) {
	    lookupAttr = LookupAttributes.add(lookupAttr, attrSets, checkSC);
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new AddAttributesTask(proxyReg,attrSets));
            }//end loop
	}//end sync(joinSet)
    }//end addAttributes

    /** 
     * Replaces the service's current set of attributes with a new set of
     * attributes. The association of this new set of attributes with the
     * service will be propagated to each lookup service with which the
     * service is registered. Note that this propagation is performed
     * asynchronously, thus there is no guarantee that the propagation of
     * the attributes to all lookup services with which the service is
     * registered will have completed upon return from this method.
     * <p>
     * An invocation of this method with duplicate elements in the 
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that 
     * invoke this method must take care not to modify the contents of the 
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to replace the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         at least one of the elements of the <code>attrSets</code>
     *         parameter is <code>null</code>.
     *
     * @see net.jini.core.entry.Entry
     * @see #getAttributes
     */
    public void setAttributes(Entry[] attrSets) {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
        testForNullElement(attrSets);
	synchronized(joinSet) {
            lookupAttr = (Entry[]) attrSets.clone();
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new SetAttributesTask(proxyReg,attrSets));
            }//end loop
	}//end sync(joinSet)
    }//end setAttributes

    /** 
     * Changes the service's current set of attributes using the same
     * semantics as the <code>modifyAttributes</code> method of the
     * <code>ServiceRegistration</code> class.
     * <p>
     * The association of the new set of attributes with the service will
     * be propagated to each lookup service with which the service is
     * registered. Note that this propagation is performed asynchronously,
     * thus there is no guarantee that the propagation of the attributes to
     * all lookup services with which the service is registered will have
     * completed upon return from this method.
     * <p>
     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * <code>IllegalArgumentException</code> will be thrown and propagated
     * through this method.
     * <p>
     * Note also that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that 
     * invoke this method must take care not to modify the contents of the 
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSetTemplates array of <code>Entry</code> used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of <code>Entry</code> containing the
     *                         actual modifications to make in the matching
     *                         sets found using the 
     *                         <code>attrSetTemplates</code> parameter
     *
     * @throws java.lang.IllegalArgumentException this exception occurs when
     *         the array containing the templates does not equal the length
     *         of the array containing the modifications.
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
     */
    public void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets) {
	modifyAttributes(attrSetTemplates, attrSets, false);
    }//end modifyAttributes

    /** 
     * Changes the service's current set of attributes using the same
     * semantics as the <code>modifyAttributes</code> method of the
     * <code>ServiceRegistration</code> class.
     * <p>
     * The association of the new set of attributes with the service will
     * be propagated to each lookup service with which the service is
     * registered. Note that this propagation is performed asynchronously,
     * thus there is no guarantee that the propagation of the attributes to
     * all lookup services with which the service is registered will have
     * completed upon return from this method.
     * <p>
     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * <code>IllegalArgumentException</code> will be thrown and propagated
     * through this method.
     * <p>
     * Note also that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that 
     * invoke this method must take care not to modify the contents of the 
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     * <p>
     * A service typically employs this version of 
     * <code>modifyAttributes</code> to prevent clients or other services
     * from attempting to modify what are referred to as "service controlled
     * attributes" in the service's set. A service controlled attribute is an
     * attribute that implements the <code>ServiceControlled</code> marker
     * interface.
     * <p>
     * Attributes representing conditions that only the service can know about
     * or control are good candidates for being defined as service controlled
     * attributes. When other entities attempt to modify a service's
     * attributes, if the service wishes to employ such service controlled
     * attributes, the service should ultimately invoke only this version 
     * of <code>modifyAttributes</code> (with the <code>checkSC</code>
     * parameter set to <code>true</code>), resulting in a
     * <code>SecurityException</code> if any of the attributes being modified
     * happen to be service controlled attributes.
     *
     * @param attrSetTemplates array of <code>Entry</code> used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of <code>Entry</code> containing the
     *                         actual modifications to make in the matching
     *                         sets found using the 
     *                         <code>attrSetTemplates</code> parameter
     * @param checkSC          <code>boolean</code> flag indicating whether the
     *                         elements of the set of attributes to modify
     *                         should be checked to determine if they are
     *                         service controlled attributes
     *
     * @throws java.lang.IllegalArgumentException this exception occurs when
     *         the array containing the templates does not equal the length
     *         of the array containing the modifications.
     *
     * @throws java.lang.SecurityException this exception occurs when
     *         the <code>checkSC</code> parameter is <code>true</code>, and
     *         at least one of the attributes to be modified is an instance
     *         of the <code>ServiceControlled</code> marker interface.
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
     * @see net.jini.lookup.entry.ServiceControlled
     */
    public void modifyAttributes(Entry[] attrSetTemplates,
                                 Entry[] attrSets,
                                 boolean checkSC)
    {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	synchronized(joinSet) {
	    lookupAttr = LookupAttributes.modify(lookupAttr, attrSetTemplates,
                                                 attrSets, checkSC);
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new ModifyAttributesTask(proxyReg,
                                                          attrSetTemplates,
                                                          attrSets));
            }//end loop
	}//end sync(joinSet)
    }//end modifyAttributes

    /**
     * Performs cleanup duties related to the termination of the lookup
     * service discovery event mechanism, as well as the lease and 
     * thread management performed by the <code>JoinManager</code>. This
     * method will cancel all of the service's managed leases that were
     * granted by the lookup services with which the service is registered,
     * and will terminate all threads that have been created.
     * <p>
     * Note that if the discovery manager employed by the instance of this
     * class that is being terminated was created by the instance itself,
     * this method will terminate all discovery processing being performed by
     * that manager object on behalf of the service; otherwise, the discovery
     * manager supplied by the service is still valid.
     * <p>
     * Whether an instance of the <code>LeaseRenewalManager</code> class was
     * supplied by the service or created by the <code>JoinManager</code>
     * itself, any reference to that object obtained by the service prior to
     * termination will still be valid after termination.
     * Note also this class makes certain concurrency guarantees with respect
     * to an invocation of the terminate method while other method invocations
     * are in progress. The termination process will not begin until
     * completion of all invocations of the methods defined in the public
     * interface of this class. Furthermore, once the termination process has
     * begun, no further remote method invocations will be made by this class,
     * and all other method invocations made on this class will not return
     * until the termination process has completed.
     * <p>
     * Upon completion of the termination process, the semantics of all
     * current and future method invocations on the instance of this class
     * that was just terminated are undefined; although the reference to the
     * <code>LeaseRenewalManager</code> object employed by that instance
     * of <code>JoinManager</code> is still valid.
     */
    public void terminate() {
        synchronized(this) {
            if(bTerminated) return;//allow for multiple terminations
            bTerminated = true;
            /* Terminate discovery and task management */
            discMgr.removeDiscoveryListener(discMgrListener);
            if(bCreateDiscMgr)  discMgr.terminate();
        }//end sync(this)
        terminateTaskMgr();
        /* Clear the joinSet and cancel all leases held by the service */
        ArrayList srvcLeases = null;//store leases for use outside of sync blk
	synchronized(joinSet) {
            srvcLeases = new ArrayList(joinSet.size());
	    for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
                srvcLeases.add
                    ( (((ProxyReg)iter.next()).serviceLease) );
	    }//end loop
	    joinSet.clear();
	}//end sync(joinSet)
        /* Must cancel leases outside of sync block because of remote call */
        if(srvcLeases == null) return;
        for(int i=0;i<srvcLeases.size();i++) {
            try {
                leaseRenewalMgr.cancel((Lease)srvcLeases.get(i));
            } catch (Exception e) { }
        }//end loop
    }//end terminate

    /** Convenience method invoked by the constructors of this class that
     *  uses the given <code>Configuration</code> to initialize the current
     *  instance of this utility, and initiates all join processing for
     *  the given parameters. This method handles the various configurations
     *  allowed by the different constructors.
     */
    private void createJoinManager(ServiceID serviceID, 
                                   Object obj,
                                   Entry[] attrSets, 
                                   ServiceIDListener callback,
                                   LeaseRenewalManager leaseMgr,
                                   Configuration config) 
                                    throws IOException, ConfigurationException
    {
	if(!(obj instanceof java.io.Serializable)) {
            throw new IllegalArgumentException("obj must be Serializable");
	}//endif

        /* Retrieve configuration items if applicable */
        if(config == null)  throw new NullPointerException("config is null");
        /* Proxy preparers */
        registrarPreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "registrarPreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        registrationPreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "registrationPreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        serviceLeasePreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "serviceLeasePreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        /* Task manager */
        taskMgr = new TaskManager(MAX_N_TASKS,(15*1000),1.0f);//default
        try {
            taskMgr = (TaskManager)config.getEntry(COMPONENT_NAME,
                                                   "taskManager",
                                                   TaskManager.class,
                                                   taskMgr);
        } catch(ConfigurationException e) { /* use default */ }
        /* Wakeup manager */
        wakeupMgr = new WakeupManager(new WakeupManager.ThreadDesc(null,true));
        try {
            wakeupMgr = (WakeupManager)config.getEntry(COMPONENT_NAME,
                                                       "wakeupManager",
                                                       WakeupManager.class,
                                                       wakeupMgr);
        } catch(ConfigurationException e) { /* use default */ }
        /* Max number of times to re-schedule tasks in thru wakeup manager */
        maxNRetries = ((Integer)config.getEntry
                                        (COMPONENT_NAME,
                                         "wakeupRetries",
                                         int.class,
                                         new Integer(maxNRetries))).intValue();
        if(attrSets == null) {
            lookupAttr = new Entry[0];
        } else {
            LookupAttributes.check(attrSets,false);//null elements NOT ok
            lookupAttr = (Entry[])attrSets.clone();
        }//endif
	serviceItem = new ServiceItem(serviceID, obj, lookupAttr);
        /* Lease renewal manager */
        leaseRenewalMgr = leaseMgr;
	if(leaseRenewalMgr == null) {
            leaseRenewalMgr = new LeaseRenewalManager(config);//default
            try {
                leaseRenewalMgr = (LeaseRenewalManager)config.getEntry
                                                  (COMPONENT_NAME,
                                                   "leaseManager",
                                                   LeaseRenewalManager.class,
                                                   leaseRenewalMgr);
            } catch(ConfigurationException e) { /* use default */ }
        }//endif
	this.callback = callback;
        /* Discovery manager */
	if(discMgr == null) {
	    bCreateDiscMgr = true;
            discMgr = new LookupDiscoveryManager
                                     (new String[] {""}, null, null, config);
            try {
                discMgr = (DiscoveryManagement)config.getEntry
                                                 (COMPONENT_NAME,
                                                  "discoveryManager",
                                                  DiscoveryManagement.class,
                                                  discMgr);
            } catch(ConfigurationException e) { /* use default */ }
	}//endif
	discMgr.addDiscoveryListener(discMgrListener);
    }//end createJoinManager

    /** Called when one of the (previously discovered) lookup services with
     *  which this join manager's service is registered has been discarded.
     *  Removes the given <code>ProxyReg</code> from the <code>joinSet</code>,
     *  and cancels the lease on the associated 
     *  <code>ServiceRegistration</code>.
     */
    private void removeJoinSet(ProxyReg reg) {
	if(reg.proxy == null) {
	    throw new IllegalArgumentException("bad proxy");
        }//endif
        Lease srvcLease = null;//store lease for use outside of sync block
	synchronized(joinSet) {
	    joinSet.remove(reg);
            if(reg.srvcRegistration != null) {// is discovered AND registered
                srvcLease = reg.serviceLease;
            }//endif
	}//end sync(joinSet)
        /* Must cancel lease outside of sync block because of remote call */
        if(srvcLease == null) return;
        try {
            leaseRenewalMgr.cancel(srvcLease);
        } catch (Exception e) { }
    }//end removeJoinSet

    /** For the given lookup service proxy, searches the <code>joinSet</code>
     *  for the corresponding <code>ProxyReg</code> element, and upon finding
     *  such an element, returns that element; otherwise returns
     *  <code>null</code>.
     */
    private ProxyReg findReg(ServiceRegistrar proxy) {
	for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
	    ProxyReg reg =(ProxyReg)iter.next();
	    if(reg.proxy.equals(proxy))  return reg;
	}//end loop
	return null;
    }//end findReg

    /** Removes (from the task manager) and cancels (in the wakeup manager)
     *  all tasks associated with the given instance of <code>ProxyReg</code>.
     */
    private void removeTasks(ProxyReg proxyReg) {
        if(proxyReg == null) return;
        if(taskMgr == null) return;
        synchronized(proxyReg.taskList) {
            if(proxyReg.proxyRegTask != null) {
                synchronized(taskMgr) {
                    taskMgr.remove(proxyReg.proxyRegTask);
                }//end sync(taskMgr)
                proxyReg.proxyRegTask.cancel();//cancel retry in WakeupMgr
                proxyReg.proxyRegTask = null;  //don't reuse because of seq#
            }//endif
            proxyReg.taskList.clear();
        }//end sync(proxyReg.taskList)
    }//end removeTasks

    /** Removes from the task manager, all pending tasks regardless of the
     *  the instance of <code>ProxyReg</code> with which the task is
     *  associated, and then terminates the task manager, and makes it
     *  a candidate for garbage collection.
     */
    private void terminateTaskMgr() {
        synchronized(wakeupMgr) {
            /* Cancel all tasks scheduled for future retry by the wakeup mgr */
            wakeupMgr.cancelAll();//cancel all tickets
            wakeupMgr.stop();//stop execution of the wakeup manager
            synchronized(taskMgr) {
                /* Remove all pending tasks */
                ArrayList pendingTasks = taskMgr.getPending();
                for(int i=0;i<pendingTasks.size();i++) {
                    RetryTask pendingTask = (RetryTask)pendingTasks.get(i);
                    pendingTask.cancel();//cancel wakeup ticket
                    taskMgr.remove(pendingTask);//remove from task mgr
                }//end loop
                /* Interrupt all active tasks, prepare taskMgr for GC. */
                taskMgr.terminate();
                taskMgr = null;
            }//end sync(taskMgr)
            wakeupMgr = null;
        }//end sync(wakeupMgr)
    }//end terminateTaskMgr

    /** Examines the elements of the input set and, upon finding at least one
     *  <code>null</code> element, throws a <code>NullPointerException</code>.
     */
    private void testForNullElement(Object[] a) {
        if(a == null) return;
        for(int i=0;i<a.length;i++) {
            if(a[i] == null) {
                throw new NullPointerException
                          ("input array contains at least one null element");
            }//endif
        }//end loop
    }//end testForNullElement

}//end class JoinManager
