text:
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 com.sun.jini.logging.LogUtil;

import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.config.NoSuchEntryException;
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 joining, 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.
 *

 * 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.
 *

 * 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 JoinManager.
 *

 * 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
 *
 * The following implementation-specific items are discussed below:
 *

 Configuring JoinManager
 *
 Logging
 *

 *
 *
 *

 * Configuring JoinManager
 *

 *
 *
 * This implementation of JoinManager supports the following
 * configuration entries; where each configuration entry name is associated
 * with the component name net.jini.lookup.JoinManager. Note
 * that the configuration entries specified here are specific to this
 * implementation of JoinManager. 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.
 *
 *
 *

 *

 *      •
 *
 *     discoveryManager
 *
 *
  &nbsp
 *     Type:  {@link net.jini.discovery.DiscoveryManagement}
 *
 *
  &nbsp
 *     Default:   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 java.lang.String[] {""},
 *                       new {@link net.jini.core.discovery.LookupLocator}[0],
 *                       null, config)
 *
 *
  &nbsp
 *     Description:
 *        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.
 *

 *
 *
 *

 *

 *      •
 *
 *     leaseManager
 *
 *
  &nbsp
 *     Type:  {@link net.jini.lease.LeaseRenewalManager}
 *
 *
  &nbsp
 *     Default:   new
 *       {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
 *         net.jini.config.Configuration) LeaseRenewalManager}(config)
 *
 *
  &nbsp
 *     Description:
 *        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.
 *

 *
 *
 *

 *

 *      •
 *
 *     maxLeaseDuration
 *
 *
  &nbsp
 *     Type:  long
 *
 *
  &nbsp
 *     Default:  Lease.FOREVER
 *
 *
  &nbsp
 *     Description:
 *        The maximum lease duration (in milliseconds) that is requested
 *            from each discovered lookup service on behalf of the service;
 *            both when the lease is initially requested, as well as when
 *            renewal of that lease is requested. Note that as this value is
 *            made smaller, renewal requests will be made more frequently
 *            while the service is up, and lease expiration will occur sooner
 *            when the service goes down.
 *

 *
 *
 *

 *

 *      •
 *
 *     registrarPreparer
 *
 *
  &nbsp
 *       Type:  {@link net.jini.security.ProxyPreparer}
 *
 *
  &nbsp
 *       Default:  new {@link net.jini.security.BasicProxyPreparer}()
 *
 *
 *
  &nbsp
 *   Description:
 *      Preparer for the proxies to the lookup services that are
 *          discovered and used by this utility.
 *

 *          The following methods of the proxy returned by this preparer are
 *          invoked by this utility:
 *

 *
{@link net.jini.core.lookup.ServiceRegistrar#register register}
 *

 *

 *
 *
 *

 *

 *      •
 *
 *     registrationPreparer
 *
 *
  &nbsp
 *       Type:  {@link net.jini.security.ProxyPreparer}
 *
 *
  &nbsp
 *       Default:  new {@link net.jini.security.BasicProxyPreparer}()
 *
 *
 *
  &nbsp
 *   Description:
 *      Preparer for the proxies to the registrations returned to
 *          this utility upon registering the service with each discovered
 *          lookup service.
 *

 *          The following methods of the proxy returned by this preparer are
 *          invoked by this utility:
 *

 *
{@link net.jini.core.lookup.ServiceRegistration#getServiceID
 *                                                           getServiceID}
 *
{@link net.jini.core.lookup.ServiceRegistration#getLease
 *                                                           getLease}
 *
{@link net.jini.core.lookup.ServiceRegistration#addAttributes
 *                                                           addAttributes}
 *
{@link net.jini.core.lookup.ServiceRegistration#modifyAttributes
 *                                                           modifyAttributes}
 *
{@link net.jini.core.lookup.ServiceRegistration#setAttributes
 *                                                           setAttributes}
 *

 *

 *
 *
 *

 *

 *      •
 *
 *     serviceLeasePreparer
 *
 *
  &nbsp
 *       Type:  {@link net.jini.security.ProxyPreparer}
 *
 *
  &nbsp
 *       Default:  new {@link net.jini.security.BasicProxyPreparer}()
 *
 *
 *
  &nbsp
 *   Description:
 *      Preparer for the leases returned to this utility through
 *          the registrations with each discovered lookup service with
 *          which this utility has registered the service.
 *

 *          Currently, none of the methods on the service lease returned
 *          by this preparer are invoked by this implementation of the utility.
 *

 *
 *
 *

 *

 *      •
 *
 *     taskManager
 *
 *
  &nbsp
 *     Type:  {@link com.sun.jini.thread.TaskManager}
 *
 *
  &nbsp
 *     Default:  new
 *             {@link com.sun.jini.thread.TaskManager#TaskManager()
 *                                   TaskManager}(15, (15*1000), 1.0f)
 *
 *
  &nbsp
 *     Description:
 *        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.
 *

 *
 *
 *

 *

 *      •
 *
 *     wakeupManager
 *
 *
  &nbsp
 *     Type:  {@link com.sun.jini.thread.WakeupManager}
 *
 *
  &nbsp
 *     Default:  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))
 *
 *
  &nbsp
 *     Description:
 *        Object that pools and manages the various tasks that are
 *            initially executed by the object corresponding to the
 *            taskManager entry
 *            of this component, but which fail during that initial execution.
 *            This object schedules the re-execution of such a failed task -
 *            in the taskManager
 *            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
 *            wakeupRetries
 *            entry of this component. This object should not be shared
 *            with other components in the application that employs this
 *            utility.
 *

 *
 *
 *

 *

 *      •
 *
 *     wakeupRetries
 *
 *
  &nbsp
 *     Type:  int
 *
 *
  &nbsp
 *     Default:  6
 *
 *
  &nbsp
 *     Description:
 *        The maximum number of times a failed task is allowed to
 *            be executed by the object corresponding to the
 *            wakeupManager
 *            entry of this component.
 *

 *
 *
 *

 * Logging
 *

 *
 *
 * This implementation of JoinManager uses the
 * {@link Logger} named net.jini.lookup.JoinManager
 * to log information at the following logging levels:

 *
 *

 *
 *

 *   net.jini.lookup.JoinManager
 *

 *
 *
  Level
 *       Description
 *

 *
 *

 *   {@link java.util.logging.Level#INFO INFO}
 *   when a task is stopped because of a definite exception
 *

 *

 *   {@link java.util.logging.Level#INFO INFO}
 *
 *     when a task is stopped because it has exceeded the maximum number of
 *     times the task is allowed to be tried/re-tried
 *
 *

 *

 *   {@link java.util.logging.Level#INFO INFO}
 *   when any exception occurs while attempting to prepare a proxy
 *

 *

 *   {@link java.util.logging.Level#FINER FINER}
 *
 *     when any exception (other than the more serious exceptions logged
 *     at higher levels) occurs in a task
 *
 *

 *

 *   {@link java.util.logging.Level#FINEST FINEST}
 *
 *     when an IllegalStateException occurs upon attempting to
 *     discard a lookup service
 *
 *

 *

 *   {@link java.util.logging.Level#FINEST FINEST}
 *   whenever any task is started
 *

 *
 *

 *   {@link java.util.logging.Level#FINEST FINEST}
 *   whenever any task completes successfully
 *

 *
 *

 *   {@link java.util.logging.Level#FINEST FINEST}
 *   whenever a proxy is prepared
 *

 *

 *

 *
 * @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 com.sun.jini.thread.TaskManager
     *  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 JoinManager,
     *  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 RetryTask, defined in
     *  the package com.sun.jini.thread, which implements
     *  the com.sun.jini.thread.TaskManager.Task 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 runAfter method defined in
     *  RetryTask to guarantee that the service managed by
     *  this JoinManager is assigned only one service ID.
     *
     *  Because of the relationship between the main tasks and
     *  RetryTask, those concrete main tasks created in this
     *  implementation of JoinManager 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 java.rmi.ConnectException.
     *
     *  To deal with situations such as that described above, this
     *  JoinManager 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 definitely 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 indefinite), 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:
     *
     *

     *
 the task succeeds
     *
 a definite failure is encountered
     *
 the task has been executed the maximum number times allowed
     *

     *
     *  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 wakeupRetries
     *  configuration 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 false
         *  upon failure. This method attempts to execute all of the tasks
         *  associated with the lookup service referenced in this task's
         *  proxyReg field. Order of execution is important,
         *  and this method executes the tasks in the proxyReg's
         *  taskList in a FIFO order.
         *
         *  Note that tasks may be added to the taskList of
         *  the proxyReg during the execution of this method.
         *
         *  Upon successfully executing all of the tasks in the
         *  taskList, this method returns true
         *  and the current instance of this task is not executed again.
         *  For each unsuccessful execution of a task in the
         *  taskList, this method returns false,
         *  which causes the task to be scheduled by the
         *  WakeupManager to be executed again at a later
         *  time, as indicated by the value returned by retryTime.
         */
        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 null 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 RegisterTask. 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-null 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 RemoteException), 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) {//accessing serviceItem.serviceID
                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 ProxyReg
         *  (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 true (which stops the wakeup manager
         *      from retrying this task
         *  otherwise, this method returns false, 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 ProxyReg 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 ProxyReg 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 requests the cancellation of the lease
     *  on the ServiceRegistration referenced by the given
     *  ProxyReg object. The lease to be cancelled was granted
     *  by the lookup service whose proxy is also referenced by the given
     *  ProxyReg object. This task is executed whenever that
     *  lookup service is discarded.
     *
     *  Note that although this task is executed upon receipt of any type
     *  of lookup service discard event, there is one type of discard
     *  that this task is actually intended to address: the so-called
     *  "lost-interest" discard. A discard corresponding to a loss of
     *  interest is an indication that the discarded lookup service is
     *  still up and available, but the discovery manager employed by
     *  this join manager (not the join manager or the service itself)
     *  discards the lookup service because the service is no longer
     *  interested in being registered with that lookup service. This
     *  loss of interest is caused by a change in either the lookup
     *  service's member groups or the service's groups-to-discover.
     *  Such a change in groups would typically be made administratively,
     *  through either the lookup service's administrative interface or
     *  through the service's administrative interface (or both).
     *
     *  When the lookup service is discarded because of a loss of
     *  interest, there is a time period in which the service is still
     *  registered with the lookup service, even though the service no
     *  longer wishes to be registered with that lookup service. To
     *  address this, the lease is cancelled so that the service is
     *  removed from the lookup service in a much more timely fashion
     *  than simply allowing the lease to expire.
     *
     *  As stated above, this task is also executed upon receipt of
     *  the other types of discard event. But because those other
     *  discard types occur when the lookup service is no longer
     *  available, the lease cancellation that is attempted here has
     *  no significant effect.
     *
     *  @see DiscMgrListener#discarded
     */
    private class DiscardProxyTask extends JoinTask {
        /** Constructor that provides this task with access (through the given
         *  ProxyReg parameter) to the lease to be cancelled.
         *
         *  @param proxyReg  data structure that references the
         *                   ServiceRegistration and associated
         *                   lease whose cancellation is to be requested
         */
        DiscardProxyTask(ProxyReg proxyReg) {
            super(proxyReg);
	}//end constructor

        /** Requests the cancellation of the lease on the
         *  ServiceRegistration that is referenced in the
         *  proxyReg data structure.
         */
        public void run() {
            logger.finest("JoinManager --> DiscardProxyTask started");
            if( (proxyReg != null) && (proxyReg.serviceLease != null) ) {
                try {
                    proxyReg.serviceLease.cancel();
                } catch (Exception e) { /*ignore*/ }
            }//endif
            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 ProxyReg 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 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 ProxyReg 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 ProxyReg 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 (ProxyReg).
         *
         *  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
         *  UnknownLeaseException from the lookup service.
         *  As a result, the lease renewal manager removes the expired lease
         *  so that no further renewal attempts are made for that lease,
         *  and then sends to this listener a LeaseRenewalEvent,
         *  containing an UnknownLeaseException.
         *
         *  Alternatively, 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
         *  removes the expired lease (again, so that no further renewal
         *  attempts are made for that lease), and then sends a
         *  LeaseRenewalEvent 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
         *  LeaseRenewalEvent contains no exception (that is,
         *  LeaseRenewalEvent.getException returns
         *  null).
         *
         *  Both situations described above indicate that the lease
         *  referenced by the event received by this listener has expired.
         *  Thus, the normal course of action should be to attempt to
         *  re-register the service with the lookup service that originally
         *  granted the lease that has expired.
         *
         *  Prior to re-registering the service, the joinSet is examined to
         *  determine if it contains a ProxyReg object that is equivalent to
         *  the ProxyReg object referencing the current instance of this
         *  listener class. That is, using equals, it is
         *  determined whether the joinSet contains either ProxyReg.this,
         *  or a new instance of ProxyReg that is equal to ProxyReg.this.
         *  If the joinSet does not contain such a ProxyReg, then the lookup
         *  service must have been discarded and not yet re-discovered; in
         *  which case, there is no need to attempt to re-register with that
         *  lookup service, since it is unavailable.
         *
         *  If it is determined that the joinSet does contain either
         *  ProxyReg.this or a new ProxyReg equivalent to ProxyReg.this,
         *  then re-registration should be attempted, but only if the lease
         *  associated with the ProxyReg in the joinSet is equal to the
         *  expired lease referenced in the event received by this listener.
         *  Equality of those leases is an indication that the lease on the
         *  service's current registration has expired; thus, an attempt to
         *  re-register the service should be made.
         *
         *  If the lease associated with the ProxyReg from the joinSet
         *  does not equal the expired lease from the event, then
         *  re-registration should not be attempted. This is because
         *  the inequality of the leases is an indication that the lease
         *  renewal event received by this listener was the result of an
         *  UnknownLeaseException that occurs when the
         *  ProxyReg in the joinSet is a new ProxyReg, different from
         *  ProxyReg.this, and the lease renewal manager requests the
         *  renewal of the (now invalid) lease associated with that old
         *  ProxyReg.this; not the valid lease associated with the new
         *  ProxyReg.
         *
         *  A scenario such as that just described can occur when the
         *  lookup service is discarded, rediscovered, and the service is
         *  re-registered, resulting in a new ProxyReg (with new lease)
         *  being placed in the joinSet, replacing the previous ProxyReg
         *  (ProxyReg.this). But before the old, expired lease is removed
         *  from the lease renewal manager, an attempt to renew the old
         *  lease is made. Such an attempt can occur because the lease
         *  renewal manager may be in the process of requesting the renewal
         *  of that lease (or may have queued such a request) just prior to,
         *  or at the same time as, when the lease removal request is made
         *  during discard processing. When the request is made to renew
         *  the expired lease, an UnknownLeaseException occurs
         *  and a lease renewal event is sent to this listener.
         *
         *  If, upon receiving an event such as that just described, the
         *  service were to be re-registered, the current valid service
         *  registration would be replaced, a new lease would be granted,
         *  and the corresponding ProxyReg currently contained in the joinSet
         *  would be replaced with a new ProxyReg. Additionally, the now
         *  invalid lease corresponding to the ProxyReg that was just
         *  replaced would remain in the lease renewal manager. This means
         *  that an attempt to renew that lease will eventually be made and
         *  will fail, and the cycle just described will repeat indefinitely.
         *
         *  Thus, for the reasons stated above, re-registration is attempted
         *  only if the lease associated with the ProxyReg contained in the
         *  joinSet is equal to the expired lease referenced in the lease
         *  renewal event received by this listener.
         */
	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);
                        Lease expiredLease = e.getLease();
                        // Maybe re-register
                        int indx = joinSet.indexOf(ProxyReg.this);
                        if(indx >= 0) {//new proxyReg/old ProxyReg.this in set
                            ProxyReg curProxyReg = (ProxyReg)joinSet.get(indx);
                            if(expiredLease.equals(curProxyReg.serviceLease)) {
                                // Okay to re-register
                                addTask(new LeaseExpireNotifyTask
                                                (ProxyReg.this,
                                                 (Entry[])lookupAttr.clone()));
                            }//endif
                        }//endif
                    }//end sync(joinSet)
		} else {
		    fail(ex);
                }//endif
	    }//end notify
	}//end class DiscLeaseListener

        /** The ProxyRegTask that instantiated this
         *  ProxyReg.
         */
        public ProxyRegTask proxyRegTask;
        /** The prepared proxy to the lookup service referenced by
         *  this class, and with which this join manager's service will be
         *  registered.
         */
	public ServiceRegistrar proxy;
        /** The prepared registration proxy returned by this class'
         *  associated lookup service when this join manager registers its
         *  associated service.
         */
	public ServiceRegistration srvcRegistration = null;
        /* The prepared 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 DiscLeaseListener 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 ProxyReg parameter.
         *
	 *  @param proxy 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 ServiceIDListener
         *  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;
                synchronized(serviceItem) {//accessing serviceItem.serviceID
                    tmpSrvcItem = new ServiceItem(serviceItem.serviceID,
                                                  serviceItem.service,
                                                  srvcAttrs);
                }//end sync(serviceItem)
            }//end sync(joinSet)
            /* Retrieve and prepare the proxy to the service registration */
            ServiceRegistration tmpSrvcRegistration
                                = proxy.register(tmpSrvcItem, renewalDuration);
            try {
                tmpSrvcRegistration =
                   (ServiceRegistration)registrationPreparer.prepareProxy
                                                       ( tmpSrvcRegistration );
                logger.finest
                          ("JoinManager - ServiceRegistration proxy prepared");
            } catch(Exception e) {
		LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
		    	"register", "JoinManager - failure during " +
		    	"preparation of ServiceRegistration proxy: {0}",
		    	new Object[] { tmpSrvcRegistration }, 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) {
		LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
		    	"register", "JoinManager - failure during " +
		    	"preparation of service lease proxy: {0}",
		    	new Object[] { serviceLease }, e);
                throw e; //rethrow the exception since proxy may be unusable
            }
            leaseRenewalMgr.renewUntil(serviceLease, Lease.FOREVER,
                                       renewalDuration, dListener);
            ServiceID tmpID = null;
            synchronized(joinSet) {
                srvcRegistration = tmpSrvcRegistration;
                synchronized(serviceItem) {//accessing serviceItem.serviceID
                    if(serviceItem.serviceID == null) {
                        serviceItem.serviceID
                                            = srvcRegistration.getServiceID();
                        tmpID = serviceItem.serviceID;
                    }//endif
                }//end sync(serviceItem)
            }//end sync(joinSet)
            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
         *  Throwable 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 IllegalStateException.
         *  Since this method is called only within the tasks run by
         *  this join manager, and since propagating an
         *  IllegalStateException out into the
         *  ThreadGroup of those tasks is undesirable, this
         *  method will not propagate the IllegalStateException
         *  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 {
		    LogUtil.logThrow(logger, Level.INFO, ProxyReg.class, "fail",
			"JoinManager - failure for lookup service proxy: {0}",
			new Object[] { proxy }, 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) {
	    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) {
			LogUtil.logThrow(logger, Level.INFO,
			    DiscMgrListener.class, "discovered", "failure "
			    + "preparing discovered ServiceRegistrar proxy: "
			    + "{0}", new Object[] { proxys[i] }, 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) {
            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);
                        joinSet.remove(proxyReg);
                        try {
                            leaseRenewalMgr.remove( proxyReg.serviceLease );
                        } catch(UnknownLeaseException ex) { /*ignore*/ }
                        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 ProxyReg 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
     *  TaskManager so that the number of concurrent threads
     *  can be bounded. If one or more of those attempts fails, a
     *  WakeupManager is used (through the use of a
     *  RetryTask) to schedule - at a later time (employing a
     *  "backoff strategy") - the re-execution of each failed task in this
     *  TaskManager.
     */
    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 WakeupManager; 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 RetryTask) 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 discMgr.
     */
    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 ProxyReg 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;
    /** The value to use as the renewDuration parameter
     *  when invoking the lease renewal manager's renewUntil
     *  method to add a service lease to manage. This value represents,
     *  effectively, the time interval (in milliseconds) over which each
     *  managed lease must be renewed. As this value is made smaller,
     *  renewal requests will be made more frequently while the service
     *  is up, and lease expirations will occur sooner when the service
     *  goes down.
     */
    private long renewalDuration = Lease.FOREVER;
    /** 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 register the given
     * service reference with all discovered lookup services and, through
     * an event sent to the given ServiceIDListener object,
     * communicate the service ID assigned by the first lookup service
     * with which the service is registered. This constructor is typically
     * used by services which have not yet been assigned a service ID.
     *

     * The value input to the serviceProxy parameter represents
     * the service reference (proxy) to register with each discovered lookup
     * service. If the Object input to that parameter is not
     * Serializable, an IllegalArgumentException
     * is thrown. If null is input to that parameter, a
     * NullPointerException is thrown.
     *

     * The value input to the attrSets parameter is an array
     * of Entry objects, none of whose elements may be
     * null, that represents the new set of attributes to
     * associate with the new service reference to be registered. Passing
     * null as the value of the attrSets parameter
     * is equivalent to passing an empty array. If any of the elements
     * of the attrSets array are null, a
     * NullPointerException is thrown. The set of attributes
     * passed in this parameter will be associated with the service in all
     * future join processing until those attributes are changed through
     * an invocation of a method on this class such as,
     * addAttributes, setAttributes,
     * modifyAttributes, or replaceRegistration.
     *

     * When constructing this utility, the service supplies an object through
     * which notifications that indicate a lookup service has been discovered
     * or discarded will be received. At a minimum, the object supplied
     * (through the discoveryMgr parameter) must satisfy the
     * contract defined in the DiscoveryManagement interface.
     * That is, the object supplied must provide this utility with the ability
     * to set discovery listeners and to discard previously discovered
     * lookup services when they are found to be unavailable. A value of
     * null may be input to the discoveryMgr
     * parameter. When null is input to that parameter, an
     * instance of LookupDiscoveryManager is used to listen
     * for events announcing the discovery of only those lookup services
     * that are members of the public group.
     *

     * The object input to the leaseMgr parameter provides for
     * the coordination, systematic renewal, and overall management of all
     * leases on the given service reference's residency in the lookup
     * services that have been joined. As with the discoveryMgr
     * parameter, a value of null may be input to this
     * parameter. When null is input to this parameter,
     * an instance of LeaseRenewalManager, initially managing
     * no Lease objects will be used. This feature allows a
     * service to either use 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, that are accessible only within the current instance
     * of the JoinManager.
     *
     * @param serviceProxy the service reference (proxy) to register with all
     *                     discovered lookup services
     * @param attrSets     array of Entry consisting of the
     *                     attribute sets with which to register the service
     * @param callback     reference to the object that should receive the
     *                     event containing the service ID, assigned to the
     *                     service by the first lookup service with which the
     *                     service reference is registered
     * @param discoveryMgr reference to the DiscoveryManagement
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the LeaseRenewalManager
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if either null is
     *         input to the serviceProxy parameter, or at least
     *         one of the elements of the attrSets parameter is
     *         null
     * @throws java.io.IOException if initiation of discovery process results
     *         in IOException when socket allocation occurs
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.lookup.ServiceIDListener
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
			ServiceIDListener callback,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(null, serviceProxy, attrSets, callback, leaseMgr,
                             EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs an instance of this class, configured using the items
     * retrieved through the given Configuration object,
     * that will register the given service reference with all discovered
     * lookup services and, through an event sent to the given
     * ServiceIDListener object, communicate the service ID
     * assigned by the first lookup service with which the service is
     * registered. This constructor is typically used by services which
     * have not yet been assigned a service ID, and which wish to allow
     * for deployment-time configuration of the service's join processing.
     *

     * The items used to configure the current instance of this class
     * are obtained through the object input to the config
     * parameter. If null is input to that parameter, a
     * NullPointerException is thrown.
     *

     * The object this utility will use to manage lookup service discovery on
     * behalf of the given service can be supplied through either the
     * discoveryMgr parameter or through an entry contained
     * in the given Configuration. If null is input
     * to the discoveryMgr parameter, an attempt will first be
     * made to retrieve from the given Configuration, an entry
     * named "discoveryManager" (described above). If such an object is
     * successfully retrieved from the given Configuration, that
     * object will be used to perform the lookup service discovery management
     * required by this utility.
     *

     * If null is input to the discoveryMgr
     * parameter, and no entry named "discoveryManager" is specified in the
     * given Configuration, then an instance of the utility class
     * LookupDiscoveryManager will be used to listen for events
     * announcing the discovery of only those lookup services that are
     * members of the public group.
     *

     * As with the discoveryMgr parameter, the object this
     * utility will use to perform lease management on behalf of the given
     * service can be supplied through either the leaseMgr
     * parameter or through an entry contained in the given
     * Configuration. If null is input to the
     * leaseMgr parameter, an attempt will first be made to
     * retrieve from the given Configuration, an entry named
     * "leaseManager" (described above). If such an object is successfully
     * retrieved from the given Configuration, that object
     * will be used to perform the lease management required by this utility.
     *

     * If null is input to the leaseMgr
     * parameter, and no entry named "leaseManager" is specified in the
     * given Configuration, then an instance of the utility
     * class LeaseRenewalManager that takes the given
     * Configuration will be created (initially managing no
     * leases) and used to perform all required lease renewal management
     * on behalf of the given service.
     *

     * Except for the config parameter and the additional
     * semantics imposed by that parameter (as noted above), all other
     * parameters of this form of the constructor, along with their
     * associated semantics, are identical to that of the five-argument
     * constructor that takes a ServiceIDListener.
     *
     * @param serviceProxy the service reference (proxy) to register with all
     *                     discovered lookup services
     * @param attrSets     array of Entry consisting of the
     *                     attribute sets with which to register the service
     * @param callback     reference to the ServiceIDListener
     *                     object that should receive the event containing the
     *                     service ID assigned to the service by the first
     *                     lookup service with which the service reference
     *                     is registered
     * @param discoveryMgr reference to the DiscoveryManagement
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the LeaseRenewalManager
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     * @param config       instance of Configuration through
     *                     which the items used to configure the current
     *                     instance of this class are obtained
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if null is input
     *         to the serviceProxy parameter or the
     *         config parameter, or if at least one of the
     *         elements of the attrSets parameter is
     *         null
     * @throws java.io.IOException if initiation of discovery process results
     *         in IOException when socket allocation occurs
     * @throws net.jini.config.ConfigurationException if an exception
     *         occurs while retrieving an item from the given
     *         Configuration object
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     *
     * @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 serviceProxy,
                        Entry[] attrSets,
			ServiceIDListener callback,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(null, serviceProxy, 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
     * ServiceID. This constructor is typically used by
     * services which 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), and which do not wish to allow for
     * deployment-time configuration of the service's join processing.
     *

     * Except that the desired ServiceID is supplied through the
     * serviceID parameter rather than through a notification
     * sent to a ServiceIDListener, all other parameters
     * of this form of the constructor, along with their associated semantics,
     * are identical to that of the five-argument constructor that takes
     * a ServiceIDListener.
     *
     * @param serviceProxy a reference to the service requesting the services
     *                     of this class
     * @param attrSets     array of Entry consisting of the
     *                     attribute sets with which to register the service
     * @param serviceID    an instance of ServiceID with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr reference to the DiscoveryManagement
     *                     object this class should use to manage the given
     *                     service's lookup service discovery duties
     * @param leaseMgr     reference to the LeaseRenewalManager
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if either null is
     *         input to the serviceProxy parameter, or at least
     *         one of the elements of the attrSets parameter is
     *         null
     * @throws java.io.IOException if initiation of discovery process results
     *         in IOException when socket allocation occurs
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.core.lookup.ServiceID
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
			ServiceID serviceID,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(serviceID, serviceProxy, attrSets,
                             (ServiceIDListener)null, leaseMgr,
                             EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs an instance of this class, configured using the items
     * retrieved through the given Configuration, that will
     * register the service with all discovered lookup services, using the
     * supplied ServiceID. This constructor is typically used by
     * services which 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), and which wish to allow for deployment-time
     * configuration of the service's join processing.
     *

     * The items used to configure the current instance of this class
     * are obtained through the object input to the config
     * parameter. If null is input to that parameter, a
     * NullPointerException is thrown.
     *

     * Except that the desired ServiceID is supplied through the
     * serviceID parameter rather than through a notification
     * sent to a ServiceIDListener, all other parameters
     * of this form of the constructor, along with their associated semantics,
     * are identical to that of the six-argument constructor that takes
     * a ServiceIDListener.
     *
     * @param serviceProxy a reference to the service requesting the services
     *                     of this class
     * @param attrSets     array of Entry consisting of the
     *                     attribute sets with which to register the service.
     * @param serviceID    an instance of ServiceID with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr reference to the DiscoveryManagement
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the LeaseRenewalManager
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     * @param config       instance of Configuration through
     *                     which the items used to configure the current
     *                     instance of this class are obtained
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if null is input
     *         to the serviceProxy parameter or the
     *         config parameter, or if at least one of the
     *         elements of the attrSets parameter is
     *         null
     * @throws java.io.IOException if initiation of discovery process results
     *         in IOException when socket allocation occurs
     * @throws net.jini.config.ConfigurationException if an exception
     *         occurs while retrieving an item from the given
     *         Configuration object
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     *
     * @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 serviceProxy,
                        Entry[] attrSets,
			ServiceID serviceID,
			DiscoveryManagement discoveryMgr,
			LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(serviceID, serviceProxy, attrSets,
                          (ServiceIDListener)null, leaseMgr, config);
    }//end constructor

    /**
     * Returns the instance of DiscoveryManagement that was
     * either passed into the constructor, or that was created as a result
     * of null being input to that parameter.
     *

     * The object returned by this method encapsulates the mechanism by which
     * either the JoinManager 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 DiscoveryManagement interface
     *         that was either passed into the constructor, or that was
     *         created as a result of null 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 LeaseRenewalManager class
     * that was either passed into the constructor, or that was created
     * as a result of null being input to that parameter.
     *

     * The object returned by this method manages the leases requested and
     * held by the JoinManager. 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 JoinManager
     * 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 LeaseRenewalManager class
     *         that was either passed into the constructor, or that was
     *         created as a result of null 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 ServiceRegistrar 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 ServiceRegistrar, 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 Entry 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(joinSet)
    }//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.
     *

     * An invocation of this method with duplicate elements in the
     * attrSets parameter (where duplication means attribute
     * equality as defined by calling the MarshalledObject.equals
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     *

     * 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
     * attrSets 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
     * attrSets parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of Entry consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException if either null is
     *         input to the attrSets parameter, or one or more
     *         of the elements of the attrSets parameter is
     *         null
     *
     * @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.
     *

     * An invocation of this method with duplicate elements in the
     * attrSets parameter (where duplication means attribute
     * equality as defined by calling the MarshalledObject.equals
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     *

     * 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
     * attrSets 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
     * attrSets parameter, after this method is invoked, are
     * undefined.
     *

     * A service typically employs this version of addAttributes
     * 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
     * ServiceControlled marker interface.
     *

     * 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 modifyAttributes method in the appropriate
     * manner.
     *

     * 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
     * ServiceControlled 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 addAttributes (with the checkSC
     * parameter set to true), resulting in a
     * SecurityException 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 Entry consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     * @param checkSC  boolean 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 if either null is
     *         input to the attrSets parameter, or one or more
     *         of the elements of the attrSets parameter is
     *         null
     *
     * @throws java.lang.SecurityException if the checkSC
     *         parameter is true, and at least one of the
     *         attributes to be added is an instance of the
     *         ServiceControlled 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.
     *

     * An invocation of this method with duplicate elements in the
     * attrSets parameter (where duplication means attribute
     * equality as defined by calling the MarshalledObject.equals
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     *

     * 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
     * attrSets 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
     * attrSets parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of Entry consisting of the
     *                 attribute sets with which to replace the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException if either null is
     *         input to the attrSets parameter, or one or more
     *         of the elements of the attrSets parameter is
     *         null.
     *
     * @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 modifyAttributes method of the
     * ServiceRegistration class.
     *

     * 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.
     *

     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * IllegalArgumentException will be thrown and propagated
     * through this method.
     *

     * 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
     * attrSets 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
     * attrSets parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSetTemplates array of Entry used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of Entry containing the
     *                         actual modifications to make in the matching
     *                         sets found using the
     *                         attrSetTemplates parameter
     *
     * @throws java.lang.IllegalArgumentException if 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 modifyAttributes method of the
     * ServiceRegistration class.
     *

     * 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.
     *

     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * IllegalArgumentException will be thrown and propagated
     * through this method.
     *

     * 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
     * attrSets 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
     * attrSets parameter, after this method is invoked, are
     * undefined.
     *

     * A service typically employs this version of
     * modifyAttributes 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 ServiceControlled marker
     * interface.
     *

     * 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 modifyAttributes (with the checkSC
     * parameter set to true), resulting in a
     * SecurityException if any of the attributes being modified
     * happen to be service controlled attributes.
     *
     * @param attrSetTemplates array of Entry used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of Entry containing the
     *                         actual modifications to make in the matching
     *                         sets found using the
     *                         attrSetTemplates parameter
     * @param checkSC          boolean 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 if the array containing the
     *         templates does not equal the length of the array containing
     *         the modifications
     *
     * @throws java.lang.SecurityException if the checkSC
     *         parameter is true, and at least one of the
     *         attributes to be modified is an instance of the
     *         ServiceControlled 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 JoinManager. 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.
     *

     * 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.
     *

     * Whether an instance of the LeaseRenewalManager class was
     * supplied by the service or created by the JoinManager
     * 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.
     *

     * 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
     * LeaseRenewalManager object employed by that instance
     * of JoinManager 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

    /**
     * Registers a new reference to the service with all current and future
     * discovered lookup services. The new service reference will replace
     * the reference that was previously registered as a result of either
     * constructing this utility, or a prior invocation of one of the forms
     * of this method. The new service reference will be registered using
     * the same ServiceID with which previous registrations
     * were made through this utility.
     *

     * The value input to the serviceProxy parameter represents
     * the new service reference (proxy) to register with each discovered
     * lookup service. If the Object input to that parameter is
     * not Serializable, an IllegalArgumentException
     * is thrown. If null is input to that parameter, a
     * NullPointerException is thrown.
     *

     * The attribute sets that this method associates with the new service
     * reference are the same attribute sets as those associated with the
     * old registration.
     *
     * @param serviceProxy the new service reference (proxy) to register with
     *                     all current and future discovered lookup services
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if null is input
     *         to the serviceProxy parameter
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     */
    public void replaceRegistration(Object serviceProxy) {
        replaceRegistrationDo(serviceProxy, null, false);
    }//end replaceRegistration

    /**
     * Registers a new reference to the service with all current and future
     * discovered lookup services, applying semantics identical to the
     * one-argument form of this method, except with respect to the
     * registration of the given attribute sets.
     *

     * This form of replaceRegistration takes as its
     * second parameter, an array of Entry objects
     * (attrSets), none of whose elements may be
     * null, that represents the new set of attributes to
     * associate with the new service reference to be registered. As with
     * the constructor to this utility, passing null as the
     * value of the attrSets parameter is equivalent to passing
     * an empty array. If any of the elements of attrSets are
     * null, a NullPointerException is thrown.
     * This new set of attributes will be associated with the service in
     * all future join processing.
     *
     * @param serviceProxy the new service reference (proxy) to register with
     *                     all current and future discovered lookup services
     * @param attrSets     array of Entry consisting of the
     *                     attribute sets with which to register the new
     *                     service reference. Passing null as
     *                     the value of this parameter is equivalent to
     *                     passing an empty Entry array
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         serviceProxy parameter is not serializable
     * @throws java.lang.NullPointerException if either null is
     *         input to the serviceProxy parameter, or at least
     *         one of the elements of the attrSets parameter is
     *         null
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated JoinManager instance. Note that this
     *         exception is implementation-specific.
     */
    public void replaceRegistration(Object serviceProxy, Entry[] attrSets) {
        replaceRegistrationDo(serviceProxy, attrSets, true);
    }//end replaceRegistration

    /** Convenience method invoked by the constructors of this class that
     *  uses the given Configuration 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 serviceProxy,
                                   Entry[] attrSets,
                                   ServiceIDListener callback,
                                   LeaseRenewalManager leaseMgr,
                                   Configuration config)
                                    throws IOException, ConfigurationException
    {
	if(!(serviceProxy instanceof java.io.Serializable)) {
            throw new IllegalArgumentException
                                       ("serviceProxy 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 */
        try {
            taskMgr = (TaskManager)config.getEntry(COMPONENT_NAME,
                                                   "taskManager",
                                                   TaskManager.class);
        } catch(NoSuchEntryException e) { /* use default */
            taskMgr = new TaskManager(MAX_N_TASKS,(15*1000),1.0f);
        }
        /* Wakeup manager */
        try {
            wakeupMgr = (WakeupManager)config.getEntry(COMPONENT_NAME,
                                                       "wakeupManager",
                                                       WakeupManager.class);
        } catch(NoSuchEntryException e) { /* use default */
            wakeupMgr = new WakeupManager
                                    (new WakeupManager.ThreadDesc(null,true));
        }
        /* 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 {
            attrSets = (Entry[])attrSets.clone();
            LookupAttributes.check(attrSets,false);//null elements NOT ok
            lookupAttr = attrSets;
        }//endif
	serviceItem = new ServiceItem(serviceID, serviceProxy, lookupAttr);
        /* Lease renewal manager */
        leaseRenewalMgr = leaseMgr;
	if(leaseRenewalMgr == null) {
            try {
                leaseRenewalMgr = (LeaseRenewalManager)config.getEntry
                                                  (COMPONENT_NAME,
                                                   "leaseManager",
                                                   LeaseRenewalManager.class);
            } catch(NoSuchEntryException e) { /* use default */
                leaseRenewalMgr = new LeaseRenewalManager(config);
            }
        }//endif
        renewalDuration = ((Long)config.getEntry
                                      (COMPONENT_NAME,
                                       "maxLeaseDuration",
                                       long.class,
                                       new Long(renewalDuration))).longValue();
        if( (renewalDuration == 0) || (renewalDuration < Lease.ANY) ) {
            throw new ConfigurationException("invalid configuration entry: "
                                             +"renewalDuration ("
                                             +renewalDuration+") must be "
                                             +"positive or Lease.ANY");
        }//endif
	this.callback = callback;
        /* Discovery manager */
	if(discMgr == null) {
	    bCreateDiscMgr = true;
            try {
                discMgr = (DiscoveryManagement)config.getEntry
                                                 (COMPONENT_NAME,
                                                  "discoveryManager",
                                                  DiscoveryManagement.class);
            } catch(NoSuchEntryException e) { /* use default */
                discMgr = new LookupDiscoveryManager
                                     (new String[] {""}, null, null, config);
            }
	}//endif
	discMgr.addDiscoveryListener(discMgrListener);
    }//end createJoinManager

    /** For the given lookup service proxy, searches the joinSet
     *  for the corresponding ProxyReg element, and upon finding
     *  such an element, returns that element; otherwise returns
     *  null.
     */
    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 ProxyReg.
     */
    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 ProxyReg 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
     *  null element, throws a NullPointerException.
     */
    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

    /** Convenience method invoked by either form of the method
     *  replaceRegistration. This method registers the
     *  given serviceProxy with all discovered lookup
     *  services, replacing all current registrations. If the value
     *  of the doAttrs parameter is true,
     *  this method will associate the given attrSets
     *  with the new service registration; otherwise, it will use
     *  the attribute sets currently associated with the old registration.
     */
    private void replaceRegistrationDo(Object serviceProxy,
                                       Entry[] attrSets,
                                       boolean doAttrs)
    {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
	if(!(serviceProxy instanceof java.io.Serializable)) {
            throw new IllegalArgumentException
                                        ("serviceProxy must be Serializable");
	}//endif
	synchronized(joinSet) {
            if(doAttrs) {
                if(attrSets == null) {
                    lookupAttr = new Entry[0];
                } else {
                    attrSets = (Entry[])attrSets.clone();
                    LookupAttributes.check(attrSets,false);//no null elements
                    lookupAttr = attrSets;
                }//endif
            }//endif
            serviceItem.service = serviceProxy;
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)(joinSet.get(i));
                removeTasks(proxyReg);
                try {
                    leaseRenewalMgr.remove( proxyReg.serviceLease );
                } catch (Exception e) { }
                proxyReg.addTask(new RegisterTask(proxyReg,
                                                 (Entry[])lookupAttr.clone()));
            }//end loop
	}//end sync(joinSet)
    }//end replaceRegistrationDo

}//end class JoinManager