Package net.jini.discovery

Source Code of net.jini.discovery.LookupDiscovery$AnnouncementInfo

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

import com.sun.jini.config.Config;
import com.sun.jini.discovery.Discovery;
import com.sun.jini.discovery.DiscoveryConstraints;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.EncodeIterator;
import com.sun.jini.discovery.MulticastAnnouncement;
import com.sun.jini.discovery.MulticastRequest;
import com.sun.jini.discovery.UnicastResponse;
import com.sun.jini.discovery.internal.MultiIPDiscovery;
import com.sun.jini.logging.Levels;
import com.sun.jini.logging.LogUtil;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.thread.WakeupManager;
import com.sun.jini.thread.WakeupManager.Ticket;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.MulticastSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.config.NoSuchEntryException;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.io.UnsupportedConstraintException;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.Security;
import net.jini.security.SecurityContext;

/**
* This class is a helper utility class that encapsulates the functionality
* required of an entity that wishes to employ multicast discovery to
* find lookup services located within the entity's "multicast radius"
* (roughly, the number of hops beyond which neither the multicast requests
* from the entity, nor the multicast announcements from the lookup service,
* will propagate). This class helps make the process of acquiring references
* to lookup services - based on no information other than lookup service
* group membership - much simpler for both services and clients.
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* The following implementation-specific items are discussed below:
* <ul><li> <a href="#ldConfigEntries">Configuring LookupDiscovery</a>
*     <li> <a href="#ldLogging">Logging</a>
* </ul>
*
* <a name="ldConfigEntries">
* <p>
* <b><font size="+1">Configuring LookupDiscovery</font></b>
* <p>
* </a>
*
* This implementation of <code>LookupDiscovery</code> supports the
* following configuration entries; where each configuration entry name
* is associated with the component name
* <code>net.jini.discovery.LookupDiscovery</code>. Note that the
* configuration entries specified here are specific to this implementation
* of <code>LookupDiscovery</code>. Unless otherwise stated, each entry
* is retrieved from the configuration only once per instance of this
* utility, where each such retrieval is performed in the constructor.
*
* <a name="discoveryConstraints">
* <table summary="Describes the discoveryConstraints
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>discoveryConstraints</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link net.jini.core.constraint.MethodConstraints}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>null</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> Constraints to apply to the multicast request, multicast
*           announcement and unicast discovery protocols.  Multicast
*           request constraints are derived by calling
*           {@link MethodConstraints#getConstraints getConstraints} on the
*           obtained <code>MethodConstraints</code> instance with a
*           <code>Method</code> object for the
*    {@link com.sun.jini.discovery.DiscoveryConstraints#multicastRequest
*              multicastRequest} method; multicast announcement and unicast
*              discovery constraints are similarly obtained by passing
*              <code>Method</code> objects for the
*    {@link com.sun.jini.discovery.DiscoveryConstraints#multicastAnnouncement
*              multicastAnnouncement} and
*    {@link com.sun.jini.discovery.DiscoveryConstraints#unicastDiscovery
*              unicastDiscovery} methods, respectively.  A <code>null</code>
*              value is interpreted as  mapping all methods to empty
*              constraints.
*           <p>
*           This class supports the use of the following constraint types
*              to control discovery behavior:
*           <ul>
*             <li> {@link com.sun.jini.discovery.DiscoveryProtocolVersion}:
*                  this constraint can be used to control which version(s)
*                  of the multicast request, multicast announcement and
*                  unicast discovery protocols are used.
*             <li> {@link com.sun.jini.discovery.MulticastMaxPacketSize}:
*                  this constraint can be used to control the maximum size
*                  of multicast request packets to send; it can also be
*                  used to specify the size of the buffer used to receive
*                  incoming multicast announcement packets.
*             <li> {@link com.sun.jini.discovery.MulticastTimeToLive}: this
*                  constraint can be used to control the time to live (TTL)
*                  value set on outgoing multicast request packets.
*             <li> {@link com.sun.jini.discovery.UnicastSocketTimeout}:
*                  this constraint can be used to control the read timeout
*                  set on sockets over which unicast discovery is
*                  performed.
*             <li> {@link net.jini.core.constraint.ConnectionRelativeTime}:
*                  this constraint can be used to control the relative
*                     connection timeout set on sockets over which unicast
*                     discovery is performed.
*              <li> {@link net.jini.core.constraint.ConnectionAbsoluteTime}:
*                  this constraint can be used to control the absolute
*                     connection timeout set on sockets over which unicast
*                     discovery is performed.
*           </ul>
*           Constraints other than those listed above are passed on to the
*           underlying implementations of versions 1 and 2 of the discovery
*              protocols.
* </table>
*
* <a name="finalMulticastRequestInterval">
* <table summary="Describes the finalMulticastRequestInterval
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>finalMulticastRequestInterval</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>2*60*1000 (2 minutes)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> With respect to when this utility is started, as well
*            as when the set of groups to discover is changed, this
*            entry represents the number of milliseconds to wait
*            after sending the n-th multicast request where n is
*            equal to the value of the <a href="#multicastRequestMax">
*            <code>multicastRequestMax</code></a> entry of this component.
* </table>
* <a name="initialMulticastRequestDelayRange">
* <table summary="Describes the initialMulticastRequestDelayRange
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>initialMulticastRequestDelayRange</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>0 milliseconds</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> With respect to when this utility is started, this entry controls
*       how long to wait before sending out the first multicast request.
*       If the value is positive, the first request will be delayed by a random
*       value between <code>0</code> and
*       <code>initialMulticastRequestDelayRange</code>
*       milliseconds. Subsequent request intervals are controlled by the
*       <a href="#multicastRequestInterval">
*       <code>multicastRequestInterval</code></a> entry. Note that this entry
*       only has effect when this utility is initialized. The first multicast
*       request is not delayed if the groups to discover are subsequently
*       changed.
* </table>
* <a name="multicastAnnouncementInterval">
* <table summary="Describes the multicastAnnouncementInterval
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastAnnouncementInterval</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>2*60*1000 (2 minutes)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> A lookup service will send out multicast packets
*            announcing its existence every N milliseconds; for some
*            value of N. The value of this entry controls how often
*            this utility examines the multicast announcements from
*            previously discovered lookup services for <i>liveness</i>.
* </table>
*
* <a name="multicastInterfaceRetryInterval">
* <table summary="Describes the multicastInterfaceRetryInterval
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastInterfaceRetryInterval</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>int</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>5*60*1000 (5 minutes)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> With respect to any network interface this utility is configured
*            to use to send and receive multicast packets (see entry 
*            <a href="#multicastInterfaces">
*            <code>multicastInterfaces</code></a>), if failure is encountered
*            upon the initial attempt to set the interface or join the
*            desired multicast group, this utility will retry the failed
*            interface every <a href="#multicastInterfaceRetryInterval">
*            <code>multicastInterfaceRetryInterval</code></a> milliseconds
*            until success is encountered.
* </table>
*
* <a name="multicastInterfaces">
* <table summary="Describes the multicastInterfaces
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastInterfaces</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link java.net.NetworkInterface NetworkInterface[]}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>new </code>
*              {@link java.net.NetworkInterface NetworkInterface[]}
*                     &nbsp <code>{all currently supported interfaces}</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> Each network interface that is represented by an element
*            in the array corresponding to this configuration item
*            will be used to send and receive multicast packets when
*            this utility is participating in the multicast discovery
*            process. When not set, this utility will use all of the
*            network interfaces in the system. When this entry is set
*            to a zero length array, multicast discovery is effectively
*            <b><i>disabled</i></b>. And when set to <code>null</code>,
*            the interface to which the operating system defaults will be
*            used.
* </table>
*
* <a name="multicastRequestHost">
* <table summary="Describes the multicastRequestHost
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastRequestHost</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>String</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td>
*    <code>{@link java.net.InetAddress}.getLocalHost().getHostAddress()</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> This entry specifies the host name to include in multicast
*            requests if participating in <b><i>version 2</i></b> of the
*            multicast request protocol. The name cannot be <code>null</code>.
* </table>
*
* <a name="multicastRequestInterval">
* <table summary="Describes the multicastRequestInterval
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastRequestInterval</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>5000</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*     <td> With respect to when this utility is started, as well as
*          when the set of groups to discover is changed, this entry
*          represents the number of milliseconds to wait after
*          sending the n-th multicast request, and before sending
*          the (n+1)-st request, where n is less than the value of the
*          <a href="#multicastRequestMax"><code>multicastRequestMax</code></a>
*          entry of this component.
* </table>
*
* <a name="multicastRequestMax">
* <table summary="Describes the multicastRequestMax
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>multicastRequestMax</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>int</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>7</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The maximum number multicast requests to send when this
*            utility is started for the first time, and whenever the
*            groups to discover is changed.
* </table>
*
* <a name="registrarPreparer">
* <table summary="Describes the registrarPreparer configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>registrarPreparer</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Type: <td> {@link net.jini.security.ProxyPreparer}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
*                     </code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*   Description:
*     <td> Preparer for the proxies to the lookup services that are
*          discovered and used by this utility.
*          <p>
*          This preparer should perform all operations required to use a
*          newly received proxy to a lookup service, which may including
*          verifying trust in the proxy, granting permissions, and setting
*          constraints.
*          <p>
*          The following methods of the
*          {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
*          returned by this preparer are invoked by this implementation of
*          <code>LookupDiscovery</code>:
*       <ul>
*         <li>{@link net.jini.core.lookup.ServiceRegistrar#getServiceID
*                                                           getServiceID}
*         <li>{@link net.jini.core.lookup.ServiceRegistrar#getGroups
*                                                           getGroups}
*         <li>{@link net.jini.core.lookup.ServiceRegistrar#getLocator
*                                                           getLocator}
*       </ul>
* </table>
*
* <a name="taskManager">
* <table summary="Describes the taskManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>taskManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link com.sun.jini.thread.TaskManager}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>new
*             {@link com.sun.jini.thread.TaskManager#TaskManager()
*                                   TaskManager}(15, (15*1000), 1.0f)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The object that pools and manages the various threads
*            executed by this utility. The default manager creates a
*            maximum of 15 threads, waits 15 seconds before removing
*            idle threads, and uses a load factor of 1.0 when
*            determining whether to create a new thread. This object
*            should not be shared with other components in the
*            application that employs this utility.
* </table>
* <a name="unicastDelayRange">
* <table summary="Describes the unicastDelayRange
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>unicastDelayRange</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>0 milliseconds</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> Controls how long this utility will wait before sending out
*       unicast discovery requests. If the value is positive, any
*       unicast discovery request that it initiates will be delayed by a
*       random value between <code>0</code> and
*       <code>unicastDelayRange</code> milliseconds. A typical use of this
*       entry would be to achieve a more uniform distribution of unicast
*       discovery requests to a lookup service, when a large number of
*       <code>LookupDiscovery</code> instances simultaneously receive multicast
*       announcements from the lookup service.
* </table>
* <a name="wakeupManager">
* <table summary="Describes the wakeupManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>wakeupManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link com.sun.jini.thread.WakeupManager}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>new
*     {@link com.sun.jini.thread.WakeupManager#WakeupManager(
*          com.sun.jini.thread.WakeupManager.ThreadDesc)
*     WakeupManager}(new
*     {@link com.sun.jini.thread.WakeupManager.ThreadDesc}(null,true))</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> Object used to schedule unicast discovery requests that are
*       delayed using the
*       <a href="#unicastDelayRange"><code>unicastDelayRange</code></a>
*       configuration entry of this utility. This entry is processed only
*       if <code>unicastDelayRange</code> has a positive value.
* </table>
* <a name="ldLogging">
* <p>
* <b><font size="+1">Logging</font></b>
* <p>
* </a>
*
* This implementation of <code>LookupDiscovery</code> uses the {@link Logger}
* named <code>net.jini.discovery.LookupDiscovery</code> to log information
* at the following logging levels: <p>
*
* <table border="1" cellpadding="5"
*       summary="Describes the information logged by LookupDiscovery, and
*                 the levels at which that information is logged">
*
* <caption halign="center" valign="top">
*   <b><code>net.jini.discovery.LookupDiscovery</code></b>
* </caption>
*
* <tr> <th scope="col"> Level</th>
*      <th scope="col"> Description</th>
* </tr>
*
* <tr>
*   <td>{@link java.util.logging.Level#SEVERE SEVERE}</td>
*   <td>
*     when this utility is configured to use either the default network
*     interface assigned by the system, or a specific list of network
*     interfaces, if one of those interfaces is bad or not configured for
*     multicast, or if a runtime exception occurs while either sending
*     multicast requests, or while configuring one of the interfaces to
*     receive multicast announcements, that fact will be logged at this
*     level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>
*     when any exception other than an <code>InterruptedIOException</code>,
*     <code>InterruptedException</code> or
*     <code>UnsupportedConstraintException</code> occurs in a thread or task
*     while attempting to marshal an outgoing multicast request
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>
*     when any exception other than an <code>InterruptedIOException</code> or
*     <code>SocketTimeoutException</code> occurs in a non-interrupted thread
*     while attempting to receive an incoming multicast packet
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>
*     when any exception other than an <code>InterruptedIOException</code>
*     occurs while attempting unicast discovery
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>
*     when this utility is configured to use either the default network
*     interface assigned by the system, or a specific list of network
*     interfaces, with respect to any such interface, if failure is
*     encountered upon the initial attempt to set the interface or join
*     the desired multicast group, the interface will be periodically
*     retried, and successful recovery will be logged at this level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>when any exception occurs while attempting to prepare a proxy</td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#CONFIG CONFIG}</td>
*   <td>
*     when the <code>multicastInterfaces</code> entry is configured to
*     be <code>null</code>, multicast packets will be sent and received
*     through the default network interface assigned by the system, and
*     that fact will be logged at this level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#CONFIG CONFIG}</td>
*   <td>
*     when the <code>multicastInterfaces</code> entry is configured to
*     be a zero length array, multicast discovery will be disabled, and
*     and that fact will be logged at this level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#CONFIG CONFIG}</td>
*   <td>
*     when the <code>multicastInterfaces</code> entry contains a specific
*     list of network interfaces, multicast packets will be sent and
*     received through only the network interfaces contained in that list,
*     and those interfaces will be logged at this level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#CONFIG CONFIG}</td>
*   <td>
*     when the <code>multicastInterfaces</code> entry is excluded from
*     the configuration, multicast packets will be sent and received
*     through all interfaces in the system, and those interfaces will
*     be logged at this level
*   </td>
* </tr>
* <tr>
*    <td>{@link com.sun.jini.logging.Levels#FAILED FAILED}</td>
*    <td>
*      when an <code>UnknownHostException</code> occurs while determining
*      the <code>multicastRequestHost</code>, but the caller does not have
*      permissions to retrieve the local host name. The original
*      <code>UnknownHostException</code> with the host name information
*      is logged
*    </td>
* <tr>
*   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
*   <td>
*     when this utility is configured to use all network interfaces enabled
*     in the system, if one of those interfaces is bad or not configured for
*     multicast, or if a runtime exception occurs while either sending
*     multicast requests, or while configuring one of the interfaces to
*     receive multicast announcements, that fact will be logged at this
*     level
*   </td>
* </tr>
* <tr>
*   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
*   <td>
*     when any exception occurs while attempting to unmarshal an incoming
*     multicast announcement
*   </td>
* </tr>
* <tr>
*   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
*   <td>
*     when an <code>UnsupportedConstraintException</code> occurs while
*     marshalling an outgoing multicast request, indicating that the provider
*     that threw the exception will not be used for encoding that request
*   </td>
* </tr>
* <tr>
*   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
*   <td>
*     when an <code>IOException</code> occurs upon attempting to close the
*     socket after the thread that listens for multicast responses is asked
*     to terminate
*   </td>
* </tr>
* <tr>
*   <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
*   <td>
*     when an exception is handled during unicast discovery
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINE FINE}</td>
*   <td>
*     when this utility is configured to use all network interfaces enabled
*     in the system, with respect to any such interface, if failure is
*     encountered upon the initial attempt to set the interface or join
*     the desired multicast group, the interface will be periodically
*     retried, and successful recovery will be logged at this level
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever any thread or task is started</td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>
*     whenever any thread (except the <code>Notifier</code> thread) or task
*     completes successfully
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever a discovered, discarded, or changed event is sent</td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever a proxy is prepared</td>
* </tr>
* </table>
* <p>
*
* @author Sun Microsystems, Inc.
*
* @see net.jini.core.lookup.ServiceRegistrar
* @see DiscoveryChangeListener
* @see DiscoveryManagement
* @see DiscoveryGroupManagement
* @see DiscoveryListener
* @see DiscoveryEvent
* @see DiscoveryPermission
*/
public class LookupDiscovery implements DiscoveryManagement,
                                        DiscoveryGroupManagement
{
    /* Name of this component; used in config entry retrieval and the logger.*/
    private static final String COMPONENT_NAME
                                        = "net.jini.discovery.LookupDiscovery";
    /* Logger used by this utility. */
    private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
    /** Convenience constant used to request that attempts be made to
     *  discover all lookup services that are within range, and which
     *  belong to any group. Must define this constant here as well as in
     *  <code>DiscoveryGroupManagement</code> for compatibility with
     *  earlier releases.
     */
    public static final String[] ALL_GROUPS
                                       = DiscoveryGroupManagement.ALL_GROUPS;
    /** Convenience constant used to request that discovery by group
     *  membership be halted (or not started, if the group discovery
     *  mechanism is simply being instantiated). Must define this constant
     *  here as well as in <code>DiscoveryGroupManagement</code> for
     *  compatibility with earlier releases.
     */
    public static final String[] NO_GROUPS
                                        = DiscoveryGroupManagement.NO_GROUPS;
    /** Maximum number of concurrent tasks that can be run in any task
     *  manager created by this class.
     */
    private static final int MAX_N_TASKS = 15;
    /** Default maximum size of multicast packets to send and receive. */
    private static final int DEFAULT_MAX_PACKET_SIZE = 512;
    /** Default time to live value to use for sending multicast packets. */
    private static final int DEFAULT_MULTICAST_TTL = 15;
    /** Default timeout to set on sockets used for unicast discovery. */
    private static final int DEFAULT_SOCKET_TIMEOUT = 1*60*1000;

    /** Flag indicating whether or not this class is still functional. */
    private boolean terminated = false;
    /** Set of listeners to be sent discovered/discarded/changed events. */
    private ArrayList listeners = new ArrayList(1);
    /** The groups to discover. Empty set -- NO_GROUPS, null -- ALL_GROUPS */
    private Set groups = null;
    /** Map from ServiceID to UnicastResponse. */
    private Map registrars = new HashMap(11);
    /**
     * Set that takes one of the following:
     * <p><ul>
     * <li> Socket (discovery from multicast request/response exchange)
     * <li> LookupLocator (discovery from multicast announcement)
     * <li> CheckGroupsMarker (discarded/changed from announcement)
     * <li> CheckReachabilityMarker (announcements stopped, tests reachability)
     * </ul><p>
     * Each element of this set represents a potential (or pending) discovered,
     * discarded, or changed event. Instances of UnicastDiscoveryTask retrieve
     * the next available element from this set and, based on the object type
     * of the element, determines the processing to perform and what event
     * type to send to the registered listeners.
     */
    private Set pendingDiscoveries = new HashSet(11);
    /** Thread that handles pending notifications. */
    private Notifier notifierThread;
    /** Notifications to be sent to listeners. */
    private LinkedList pendingNotifies = new LinkedList();
    /** Task manager for running UnicastDiscoveryTasks and
     *  DecodeAnnouncementTasks.
     */
    private TaskManager taskManager;
    /* WakeupManager to delay tasks. */
    private WakeupManager discoveryWakeupMgr = null;
    private boolean isDefaultWakeupMgr = false;
    /* Outstanding tickets - Access synchronized on pendingDiscoveries */
    private List tickets;
    /** Thread that handles incoming multicast announcements. */
    private AnnouncementListener announceeThread;
    /** Collection that contains instances of the Requestor Thread class,
     *  each of which participate in multicast discovery by periodically
     *  sending out multicast discovery requests for a finite period of time.
     */
    private Collection requestors = new LinkedList();
    /** Thread that manages incoming multicast responses. Runs only when
     *  there are Requestor threads running.
     */
    private ResponseListener respondeeThread = null;
    /** Security context that contains the access control context to restore
     * for callbacks, etc.
     */
    private final SecurityContext securityContext = Security.getContext();
    /** Map from ServiceID to multicast announcement time stamps; used by the
     *  process that monitors multicast announcements from already-discovered
     *  lookup services, and determines when those announcements have stopped.
     */
    private HashMap regInfo = new HashMap(11);
    /** Thread that monitors multicast announcements from already-discovered
     *  lookup services and, upon determining that those announcements have
     *  stopped, queues a reachability test with the UnicastDiscoveryTask
     *  which will ultimately result in the lookup service being discarded
     *  if the reachability test indicates that the lookup service is
     *  actually down.
     */
    private AnnouncementTimerThread announcementTimerThread;
    /* Preparer for the proxies to the lookup services that are discovered
     * and used by this utility.
     */
    private ProxyPreparer registrarPreparer;
    /* Utility for participating in version 2 of discovery protocols. */
    private Discovery protocol2 = Discovery.getProtocol2(null);
    /* Maximum number multicast requests to send when this utility is started
     * for the first time, and whenever the groups to discover are changed.
     */
    private int multicastRequestMax = 7;
    /* With respect to when this utility is started, as well as when the set
     * of groups to discover is changed, the value of this field represents
     * the number of milliseconds to wait after sending the n-th multicast
     * request, and before sending the (n+1)-st request, where n is less than
     * the value of <code)multicastRequestMax</code>.
     */
    private long multicastRequestInterval = 5000L;
    /* With respect to when this utility is started, as well as when the set
     * of groups to discover is changed, the value of this field represents
     * the number of milliseconds to wait after sending the n-th multicast
     * request, where n is equal to the value of
     * <code)multicastRequestMax</code>.
     */
    private long finalMulticastRequestInterval = 2*60*1000L;
    /* Name of requesting host to include in multicast request if
     * participating in version 2 of multicast request protocol.
     */
    private String multicastRequestHost;
    /* Constraints specified for outgoing multicast requests. */
    private DiscoveryConstraints multicastRequestConstraints;
    /* The network interfaces (NICs) through which multicast packets will
     * be sent.
     */
    private NetworkInterface[] nics;
    /* NICs that initially failed are retried after this number of millisecs.*/
    private int nicRetryInterval = 5*60*1000;
    /* Controls how often (in milliseconds) this utility examines the
     * multicast announcements from previously discovered lookup services
     * for "liveness".
     */
    private long multicastAnnouncementInterval = 2*60*1000L;
    /*
     * Controls how long to wait before responding to multicast
     * announcements
     */
    private long unicastDelayRange = 0;
    /* Controls how long to wait before sending out multicast requests */
    private long initialMulticastRequestDelayRange = 0;
    /*
     * Flag which indicates that initial multicast request thread has been
     * started.
     */
    private boolean initialRequestorStarted = false;
    /* Constraints specified for incoming multicast announcements. */
    private DiscoveryConstraints multicastAnnouncementConstraints;
    /* Unprocessed constraints specified for unicast discovery. */
    private InvocationConstraints rawUnicastDiscoveryConstraints;

    /** Constants used to tell the notifierThread the type of event to send */
    private static final int DISCOVERED = 0;
    private static final int DISCARDED  = 1;
    private static final int CHANGED    = 2;

    /** Constants used to indicate the set of network interfaces being used */
    private static final int NICS_USE_ALL  = 0;//use all NICs in the system
    private static final int NICS_USE_SYS  = 1;//use NIC assigned by the system
    private static final int NICS_USE_LIST = 2;//use list of NICs from config
    private static final int NICS_USE_NONE = 3;//multicast disabled
    /** Flag that indicates how the set of network interfaces was configured */
    private int nicsToUse = NICS_USE_ALL;

    /** Data structure containing task data processed by the Notifier Thread */
    private static class NotifyTask {
  /** The set of listeners to notify */
  public final ArrayList listeners;
  /** Map of discovered registrars-to-groups in which each is a member */
  public final Map groupsMap;
  /** The type of event to send: DISCOVERED, DISCARDED, CHANGED */
  public final int eventType;
  public NotifyTask(ArrayList listeners, Map groupsMap, int eventType) {
      this.listeners = listeners;
      this.groupsMap = groupsMap;
      this.eventType = eventType;
  }
    }//end class NotifyTask

    /** Thread that retrieves data structures of type NotifyTask from a
     *  queue and, based on the contents of the data structure, sends the
     *  appropriate event (discovered/discarded/changed) to each registered
     *  listener.
     *  <p>
     *  Only 1 instance of this thread is run.
     */
    private class Notifier extends Thread {
  /** Create a daemon thread */
  public Notifier() {
      super("event listener notification");
      setDaemon(true);
  }//end constructor

  public void run() {
            logger.finest("LookupDiscovery - Notifier thread started");
      while (true) {
    final NotifyTask task;
    synchronized (pendingNotifies) {
        if (pendingNotifies.isEmpty()) {
      notifierThread = null;
      return;
        }//endif
        task = (NotifyTask)pendingNotifies.removeFirst();
    }//end sync
                /* The call to notify() on the registered listeners is
                 * performed inside a doPrivileged block that restores the
                 * access control context that was in place when this utility
                 * was created.
                 *
                 * This is done because the notify() method called below
                 * executes in the client. But the listener object that
                 * defines that notify() method may have been obtained by
                 * the client from some (possibly untrusted) 3rd party. With
                 * respect to 3rd party code executing in the client, it is
                 * not desirable to allow such code to execute with more
                 * priviledges than the client that created this utility.
                 * Therefore, before executing notify() on any of the
                 * registered listeners, the client's Subject should be
                 * restored, and the listner code should be restricted to
                 * doing nothing more than the client itself is allowed to do.
                 */
    AccessController.doPrivileged
                (securityContext.wrap(new PrivilegedAction() {
        public Object run() {
                        boolean firstListener = true;
      for (Iterator iter = task.listeners.iterator();
           iter.hasNext(); )
      {
          DiscoveryListener l =
                                             (DiscoveryListener)iter.next();
                            /* Always send discovered and discarded events */
                            if(     (task.eventType == CHANGED)
                                && !(l instanceof DiscoveryChangeListener) )
                            {
                                continue;
                            }//endif
          DiscoveryEvent e =
        new DiscoveryEvent
                                        ( LookupDiscovery.this,
                                          deepCopy((HashMap)task.groupsMap) );
                            /* Log the event info about the lookup(s) */
                            if(     firstListener
                                && (logger.isLoggable(Level.FINEST)) )
                            {
                                String eType =
                                       new String[]{"discovered",
                                                    "discarded",
                                                    "changed"}[task.eventType];
                                ServiceRegistrar[] regs = e.getRegistrars();
                                logger.finest(eType+" event  -- "+regs.length
                                                                +" lookup(s)");
                                Map groupsMap = e.getGroups();
                                for(int i=0;i<regs.length;i++) {
                                    LookupLocator loc = null;
                                    try {
                                        loc = regs[i].getLocator();
                                    } catch (Throwable ex) { /* ignore */ }
                                    String[] groups =
                                             (String[])groupsMap.get(regs[i]);
                                    logger.finest("    "+eType+" locator  = "
                                                              +loc);
                                    if(groups.length == 0) {
                                        logger.finest("    "+eType+" group    "
                                                            +"= NO_GROUPS");
                                    } else {
                                        for(int j=0;j<groups.length;j++) {
                                            logger.finest("    "+eType
                                                          +" group["+j+"] = "
                                                          +groups[j]);
                                        }//end loop
                                    }//endif(groups.length)
                                }//end loop
                            }//endif(firstListener && isLoggable(Level.FINEST)
                            switch(task.eventType) {
                                case DISCOVERED:
                                    l.discovered(e);
                                    break;
                                case DISCARDED:
                                    l.discarded(e);
                                    break;
                                case CHANGED:
                                    ((DiscoveryChangeListener)l).changed(e);
                                    break;
                            }//end switch(eventType)
                            firstListener = false;
      }//end loop
      return null;
        }//end run
    }),//end PrivilegedAction and wrap
                securityContext.getAccessControlContext());//end doPrivileged
      }//end loop
  }//end run
    }//end class Notifier

    /** Thread that listens for multicast announcements from lookup services.
     *  <p>
     *  If the announcements are from a lookup service that has not already
     *  been discovered, and if it is determined that the lookup service
     *  belongs to at least one group of interest, a "pendingDiscovery" is
     *  queued for the UnicastDiscoveryTask to process asynchronously,
     *  completing the discovery process by performing unicast discovery.
     *  <p>
     *  If the announcements are from a lookup service that has already
     *  been discovered, the lookup service's member groups - as indicated
     *  in the announcements - are analyzed for changes that may result
     *  in either the lookup service being discarded, or in a changed event
     *  being sent.
     *  <p>
     *  Only 1 instance of this thread is run.
     */
    private class AnnouncementListener extends Thread {
  /** Multicast socket for receiving packets */
  private MulticastSocket sock;
        /* Set of interfaces whose elements also belong to the nics[] array,
         * which encountered failure when setting the interface or joining
         * the desired multicast group, and which will be retried periodically.
         */
        private ArrayList retryNics = null;

  /** Create a daemon thread */
  public AnnouncementListener() throws IOException {
      super("multicast discovery announcement listener");
      setDaemon(true);
      sock = new MulticastSocket(Constants.discoveryPort);
            switch(nicsToUse) {
                case NICS_USE_ALL:
                    /* Using all interfaces. Skip (but report) any interfaces
                     * that are "bad" or not configured for multicast.
                     */
                    for(int i=0;i<nics.length;i++) {
                        try {
                            sock.setNetworkInterface(nics[i]);
                            sock.joinGroup(Constants.getAnnouncementAddress());
                        } catch(IOException e) {
                            if(retryNics == null) {
                                retryNics = new ArrayList(nics.length);
                            }//endif
                            retryNics.add(nics[i]);
                            if( logger.isLoggable(Levels.HANDLED) ) {
                                LogRecord logRec =
                                  new LogRecord(Levels.HANDLED,
            "network interface "
                                                +"is bad or not configured "
                                                +"for multicast: {0}");
                                logRec.setParameters(new Object[]{nics[i]});
                                logRec.setThrown(e);
                                logger.log(logRec);
                            }//endif
                        }
                    }//end loop
                    break;
                case NICS_USE_LIST:
                    /* Using a configured list of specific interfaces. Skip
                     * (but report) any interfaces that are "bad" or not
                     * configured for multicast.
                     */
                    for(int i=0;i<nics.length;i++) {
                        try {
                            sock.setNetworkInterface(nics[i]);
                            sock.joinGroup(Constants.getAnnouncementAddress());
                        } catch(IOException e) {
                            if(retryNics == null) {
                                retryNics = new ArrayList(nics.length);
                            }//endif
                            retryNics.add(nics[i]);
                            if( logger.isLoggable(Level.SEVERE) ) {
                                LogRecord logRec =
                                  new LogRecord(Level.SEVERE,
                                                "network interface is bad or "
                                                +"not configured for "
                                                +"multicast: {0}");
                                logRec.setParameters(new Object[]{nics[i]});
                                logRec.setThrown(e);
                                logger.log(logRec);
                            }//endif
                        }
                    }//end loop
                    break;
                case NICS_USE_SYS:
                    /* Using the system-dependent default interface. Don't
                     * need to specifically set the interface. If that
                     * interface is "bad" or not configured for multicast,
                     * log it and try again later.
                     */
                    try {
                        sock.joinGroup(Constants.getAnnouncementAddress());
                    } catch(IOException e) {
                        retryNics = new ArrayList(0);
                        if( logger.isLoggable(Level.SEVERE) ) {
                            logger.log(Level.SEVERE, "system default network "
                                       +"interface is bad or not configured "
                                       +"for multicast", e);
                        }//endif
                    }
                    break;
                case NICS_USE_NONE:
                    break;//multicast disabled, do nothing
                default:
                    throw new AssertionError("nicsToUse flag out of range "
                                             +"(0-3): "+nicsToUse);
            }//end switch(nicsToUse)
  }//end constructor

  /** True if thread has been interrupted */
  private volatile boolean interrupted = false;

  /* This is a workaround for Thread.interrupt not working on
   * MulticastSocket.receive on all platforms.
   */
  public void interrupt() {
      interrupted = true;
      sock.close();
  }//end interrupt

        /** Accessor method that returns the <code>interrupted</code> flag. */
  public boolean isInterrupted() {
      return interrupted;
  }//end isInterrupted

        /** Convenience method that retries any previously failed interfaces.*/
  private void retryBadNics() {
            if(retryNics == null) return;//no failed NICs to retry
            if( !retryNics.isEmpty() ) {
                String recoveredStr = "network interface has recovered "
                                      +"from previous failure: {0}";
                ArrayList tmpList = (ArrayList)retryNics.clone();
                retryNics.clear();
                for(int i=0; i<tmpList.size(); i++) {
                    NetworkInterface nic =(NetworkInterface)tmpList.get(i);
                    try {
                        sock.setNetworkInterface(nic);
                        sock.joinGroup(Constants.getAnnouncementAddress());
                        if(nicsToUse == NICS_USE_LIST) {
                            logger.log(Level.INFO, recoveredStr, nic);
                        } else {
                            logger.log(Level.FINE, recoveredStr, nic);
                        }//endif
                    } catch(IOException e1) {
                        retryNics.add(nic);//put back for another retry later
                    }
                }//end loop
                if(retryNics.isEmpty()) retryNics = null;//future retries off
            } else {//(retryNics.size() == 0) ==> sys default interface
                try {
                    sock.joinGroup(Constants.getAnnouncementAddress());
                    retryNics = null;
                    logger.log(Level.INFO, "system default network "
                                           +"interface has recovered from "
                                           +"previous failure");
                } catch(IOException e1) { }
            }//endif(!retryNics.isEmpty())
  }//end retryBadNics

  public void run() {
            logger.finest("LookupDiscovery - AnnouncementListener thread "
                          +"started");
      byte[] buf = new byte[
    multicastAnnouncementConstraints.getMulticastMaxPacketSize(
        DEFAULT_MAX_PACKET_SIZE)];
      DatagramPacket pkt = new DatagramPacket(buf, buf.length);
            long endTime = System.currentTimeMillis() + nicRetryInterval;
      while (!isInterrupted()) {
    try {
                    int delta_t = 0;
                    if(retryNics != null) {//bad NICs, retry when time's up
                        delta_t = (int)(endTime - System.currentTimeMillis());
                        if( delta_t <= 0) {
                            retryBadNics();
                            if(retryNics != null) {//still bad, reset timer
                                delta_t = nicRetryInterval;
                                endTime = System.currentTimeMillis() + delta_t;
                            } else {//all NICs recovered, turn off timer
                                delta_t = 0;
                            }//endif
                        }//endif
                    }//endif
                    sock.setSoTimeout(delta_t);
        pkt.setLength(buf.length);
        try {
      sock.receive(pkt);
        } catch (NullPointerException e) {
      break; // workaround for bug 4190513
        }
        taskManager.add(new DecodeAnnouncementTask(pkt));

        buf = new byte[buf.length];
        pkt = new DatagramPacket(buf, buf.length);

    } catch (SocketTimeoutException e) {//continue/retry bad NICs
    } catch (InterruptedIOException e) {
        break;
    } catch (Exception e) {//ignore
                    if( isInterrupted() )  break;
                    logger.log(Level.INFO,
                               "exception while listening for multicast "
                               +"announcements",
                               e);
    }
      }//end loop(!interrupted)
      sock.close();
      sock = null;
            logger.finest("LookupDiscovery - AnnouncementListener thread "
                          +"completed");
  }//end run
    }//end class AnnouncementListener

    /** Thread that listens for multicast responses to the multicast requests
     *  sent out by the Requestor Thread class. Upon receiving a multicast
     *  response, the socket that accepted the connection request associated
     *  with the the multicast response is added to the set of
     *  pendingDiscoveries so that it (the socket) will be used by the
     *  UnicastDiscoveryTask to complete the discovery process asynchronously.
     *  <p>
     *  Only 1 instance of this thread is run.
     */
    private class ResponseListener extends Thread {
  /** Server socket for accepting connections */
  public ServerSocket serv;
 
  /** Create a daemon thread */
  public ResponseListener() throws IOException {
      super("multicast discovery response listener");
      setDaemon(true);
      serv = new ServerSocket(0);
  }//end constructor

  /** True if thread has been interrupted */
  private volatile boolean interrupted = false;

  /* This is a workaround for Thread.interrupt not working on
   * ServerSocket.accept on all platforms.  ServerSocket.close
   * can't be used as a workaround, because it also doesn't work
   * on all platforms.
   */
  public void interrupt() {
      interrupted = true;
      try {
    (new Socket(InetAddress.getLocalHost(), getPort())).close();
      } catch (IOException e) { /* ignore */ }
  }//end interrupt

        /** Accessor method that returns the <code>interrupted</code> flag. */
  public boolean isInterrupted() {
      return interrupted;
  }//end isInterrupt

  public void run() {
            logger.finest("LookupDiscovery - ResponseListener thread started");
      while (!isInterrupted()) {
    try {
        Socket sock = serv.accept();
        if (isInterrupted()) {
      try {
          sock.close();
      } catch (IOException e) { }
      break;
        }//end if
        synchronized (pendingDiscoveries) {
      pendingDiscoveries.add(sock);
      taskManager.add(new UnicastDiscoveryTask(sock));
        }//end sync
    } catch (InterruptedIOException e) {
        break;
    } catch (Exception e) {//ignore
                    logger.log(Level.INFO,
                               "exception while listening for multicast "
                               +"response",
                               e);
    }
      }//end loop(!isInterrupted)
      try {
    serv.close();
      } catch (IOException e) {//ignore
                logger.log(Levels.HANDLED,
                           "IOException while attempting a socket close",
                           e);
      }
            logger.finest("LookupDiscovery - ResponseListener thread "
                          +"completed");
  }//end run

  /** Return the local port of the socket */
  public int getPort() {
      return serv.getLocalPort();
  }//end getPort
    }//end class ResponseListener

    /** Thread that periodically sends out multicast requests for a limited
     *  period of time, and then exits.
     *  <p>
     *  An instance of this thread is run at startup, and each time the
     *  set of groups to discover is changed.
     */
    private class Requestor extends Thread {
  /** Multicast socket for sending packets */
  private MulticastSocket sock;
  /** Unicast response port */
  private int responsePort;
  /** Groups to request */
  private String[] groups;
  private boolean delayFlag;

  /** Create a daemon thread */
  public Requestor(String[] groups, int port, boolean delayFlag)
      throws IOException
  {
      super("multicast discovery request");
      setDaemon(true);
      sock = new MulticastSocket(Constants.discoveryPort);
      sock.setTimeToLive(
    multicastRequestConstraints.getMulticastTimeToLive(
        DEFAULT_MULTICAST_TTL));
      responsePort = port;
      this.groups = groups == null ? new String[0] : groups;
      this.delayFlag = delayFlag;
  }//end constructor

  /** This method sends out N (for small N) multicast requests. Until
         *  the last request is sent out, this method sleeps for 5 seconds
         *  after each request is sent. After the last request is sent,
         *  this method sleeps for 2 minutes to allow the ResponseListener
         *  time to receive and process any multicast responses sent in
         *  reply to the multicast requests. Before sending a request, a
         *  new multicast request is constructed so that updates
         *  can be made to the set of service IDs of the lookup services
         *  discovered due to previous requests.
         *  <p>
   *  After all requests have been sent, and the ResponseListener
         *  has been given the appropriate time to receive and process
         *  any multicast responses, if there are no more active instances
         *  of this thread, this method terminates (interrupts) the
         *  ResponseListener. Note that although it is more desirable to
         *  have the ResponseListener set a timeout on the server socket
         *  (using setSoTimeout) and then simply exit after a period of
         *  time in which both the ResponseListener has been idle, and
         *  there have been no active Requestor threads, using setSoTimeout
         *  in this way can cause random hangs on the Solaris(TM) operating
         *  system.
   */
  public void run() {
            logger.finest("LookupDiscovery - Requestor thread started");
      int count; // bug 4084783/4187594
      try {
    if (delayFlag
        && (initialMulticastRequestDelayRange > 0)
        && (multicastRequestMax >= 0))
    {
        Thread.sleep((long) (Math.random() *
           initialMulticastRequestDelayRange));
    }
    for (count = multicastRequestMax;
                                          --count >= 0 && !isInterrupted(); )
                {
                    DatagramPacket[] reqs = encodeMulticastRequest
      (new MulticastRequest(multicastRequestHost,
                responsePort,
                groups,
                getServiceIDs()));
                    sendPacketByNIC(sock, reqs);
        Thread.sleep(count > 0 ?
                      multicastRequestInterval:finalMulticastRequestInterval);
    }//end loop
      } catch (InterruptedException e) {//terminate gracefully
      } catch (InterruptedIOException e) {//terminate gracefully
            } catch (Exception e) {
                logger.log(Level.INFO,"exception while marshalling outgoing "
                                      +"multicast request", e);
      } finally {
    synchronized (requestors) {
        requestors.remove(Thread.currentThread());
        if (respondeeThread != null && requestors.isEmpty()) {
      respondeeThread.interrupt();
      respondeeThread = null;
        }
    }//end sync
    sock.close();
                logger.finest("LookupDiscovery - Requestor thread completed");
      }//end try/catch/finally
  }//end run
    }//end class Requestor

    /**
     * This thread monitors the multicast announcements sent from the
     * lookup service(s) that have already been discovered by this class,
     * looking for indications that those announcements have terminated.
     *
     * The data structure used to map the discovered lookup services to
     * the time of arrival of the most recent multicast announcement from
     * each such lookup service is examined at regular intervals; dependent
     * on the system property <code>net.jini.discovery.announce</code>.
     *
     * If the difference between the current time and the last time of
     * arrival for any announcement exceeds a predetermined threshold, the
     * corresponding lookup is polled for its current set of member groups.
     * If that lookup service is unreachable, or if it is reachable but its
     * member groups have been replaced, the lookup service is discarded.
     */
    private class AnnouncementTimerThread extends Thread {
        /* Number of interval to exceed for declaring announcements stopped */
        private static final long N_INTERVALS = 3;
        /** Create a daemon thread */
        public AnnouncementTimerThread() {
            super("multicast announcement timer");
            setDaemon(true);
        }
        public synchronized void run() {
            long timeThreshold = N_INTERVALS*multicastAnnouncementInterval;
            try {
                while(!isInterrupted()) {
                    wait(multicastAnnouncementInterval);
                    long curTime = System.currentTimeMillis();
                    synchronized (registrars) {
                        /* can't modify regInfo while iterating over it,
                         * so clone it
                         */
                        HashMap regInfoClone = (HashMap)(regInfo.clone());
                        Set eSet = regInfoClone.entrySet();
                        for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
                            Map.Entry pair   = (Map.Entry)itr.next();
                            ServiceID srvcID = (ServiceID)pair.getKey();
                            long tStamp =
        ((AnnouncementInfo)pair.getValue()).tStamp;
                            long deltaT = curTime - tStamp;
                            if(deltaT > timeThreshold) {
                                /* announcements stopped, queue reachability
                                 * test and potential discarded event
                                 */
                                UnicastResponse resp =
            (UnicastResponse)registrars.get(srvcID);
        Object req = new CheckReachabilityMarker(resp);
                                synchronized (pendingDiscoveries) {
                                    if(pendingDiscoveries.add(req)) {
                                        taskManager.add(
              new UnicastDiscoveryTask(req));
                                    }//endif
                                }//end sync
                            }//end if
                        }//end loop (itr)
                    }//end sync
                }//end loop (!isInterrupted)
            } catch (InterruptedException e) { }
        }//end run
    }//end class AnnouncementTimerThread

    /**
     * Marker object placed in pendingDiscoveries set to indicate to
     * UnicastDiscoveryTask that the groups of the lookup service which sent
     * the contained announcement need to be verified.
     */
    private static class CheckGroupsMarker {
  /** Announcement sent by lookup service to check groups of */
  final MulticastAnnouncement announcement;

  CheckGroupsMarker(MulticastAnnouncement announcement) {
      this.announcement = announcement;
  }

  public int hashCode() {
      return announcement.getServiceID().hashCode();
  }

  public boolean equals(Object obj) {
      return obj instanceof CheckGroupsMarker &&
       announcement.getServiceID().equals(
          ((CheckGroupsMarker) obj).announcement.getServiceID());
  }
    }

    /**
     * Marker object placed in pendingDiscoveries set to indicate to
     * UnicastDiscoveryTask that reachability of the lookup service which sent
     * the contained unicast response needs to be verified.
     */
    private static class CheckReachabilityMarker {
  /** Response sent by lookup service to check reachability of */
  final UnicastResponse response;

  CheckReachabilityMarker(UnicastResponse response) {
      this.response = response;
  }

  public int hashCode() {
      return response.getRegistrar().hashCode();
  }

  public boolean equals(Object obj) {
      return obj instanceof CheckReachabilityMarker &&
       response.getRegistrar().equals(
          ((CheckReachabilityMarker) obj).response.getRegistrar());
  }
    }

    /**
     * Task which decodes received multicast announcement packets.  This is
     * separated into a task to allow the AnnouncementListener thread to
     * quickly loop and receive new announcement packets; the act of decoding
     * packets may involve relatively slow cryptographic operations such as
     * signature verification, and would impede the packet receiving loop if it
     * were performed inline.
     */
    private class DecodeAnnouncementTask implements TaskManager.Task {

  private final DatagramPacket datagram;

  /**
   * Creates a task for decoding the given multicast announcement packet.
   */
  public DecodeAnnouncementTask(DatagramPacket datagram) {
      this.datagram = datagram;
  }

  /**
   * Decodes this task's multicast announcement packet.  If the
   * constraints for decoding multicast announcements are satisfied and
   * the announcement merits further processing, an appropriate object is
   * added to the pendingDiscoveries set, and control is transferred to a
   * UnicastDiscoveryTask.
   */
  public void run() {
      MulticastAnnouncement ann;
      try {
    ann = decodeMulticastAnnouncement(datagram);
      } catch (Exception e) {
    if (!(e instanceof InterruptedIOException)) {
        logger.log(Levels.HANDLED,
             "exception decoding multicast announcement", e);
    }
    return;
      }

      /* If the registrars map contains the service ID of the registrar
       * that sent the current announcement then that registrar has
       * already been discovered.
       *
       * Determine if the member groups of the already-discovered
       * registrar have been replaced by a set containing none of the
       * desired groups. If yes, then discard the registrar.
       *
       * If the registrar that sent the current announcement has not
       * already been discovered, then check to see if any of the
       * group(s) in which the registrar is a member are in the set of
       * desired groups to discover. If yes, then queue the registrar for
       * unicast discovery.
       */
      Object pending = null;
      ServiceID srvcID = ann.getServiceID();
      synchronized (registrars) {
    UnicastResponse resp =
        (UnicastResponse) registrars.get(srvcID);
    if (resp != null) {
        // already in discovered set, timestamp announcement
        AnnouncementInfo aInfo =
      (AnnouncementInfo) regInfo.get(srvcID);
        aInfo.tStamp = System.currentTimeMillis();
        long currNum = ann.getSequenceNumber();
        if ((newSeqNum(currNum, aInfo.seqNum)) &&
      (!groupSetsEqual(resp.getGroups(), ann.getGroups()))) {
      /* Check if the groups have changed. In the case of
       * split announcement messages, eventually, group difference
       * will be seen for the given sequence number. This
       * check ignores other differences, such as port numbers,
       * but for the purposes of LookupDiscovery, this is not
       * important.
       */     
      pending = new CheckGroupsMarker(ann);
        }
    } else if (groupsOverlap(ann.getGroups())) {
        // newly discovered
        pending = new LookupLocator(ann.getHost(), ann.getPort());
    }
      }
      if (pending != null) {
    try {
        ann.checkConstraints();
    } catch (Exception e) {
        if (!(e instanceof InterruptedIOException)) {
      logger.log(Levels.HANDLED,
             "exception decoding multicast announcement", e);
        }
        return;
    }
    if (pending instanceof CheckGroupsMarker) {
        synchronized(registrars) {
      // Since this is a valid announcement, update the
      // sequence number.
      AnnouncementInfo aInfo =
          (AnnouncementInfo) regInfo.get(srvcID);
      aInfo.seqNum = ann.getSequenceNumber();
        }
    }
    boolean added;
    // enqueue and handle pending action, if not already enqueued
    synchronized (pendingDiscoveries) {
        added = pendingDiscoveries.add(pending);
    }
    if (added) {
        if (unicastDelayRange <= 0) {
      new UnicastDiscoveryTask(pending).run();
        } else {
      final UnicastDiscoveryTask ud =
          new UnicastDiscoveryTask(pending, true);
      final Ticket t = discoveryWakeupMgr.schedule(
          System.currentTimeMillis() +
          (long) (Math.random() * unicastDelayRange),
          new Runnable() {
        public void run() {
            taskManager.add(ud);
        }
          }
      );
      synchronized (ud) {
          ud.ticket = t;
          ud.delayRun = false;
          synchronized (pendingDiscoveries) {
        tickets.add(t);
          }
          ud.notifyAll();
      }
        }
    }
      }
  }

  /** Returns <code>true</code> if currentNum is a new sequence number
   * that needs to be inspected. A -1 occurs if the announcement had no
   * sequence number (for e.g. DiscoveryV1) or the service had been
   * discovered through unicast discovery. REMIND: Ideally the
   * message should have a flag which indicates no sequence number instead
   * of overloading the -1 value
   */
  private boolean newSeqNum(long currentNum, long oldNum) {
      if (oldNum == -1) {
    // No sequence number information, so we guess that this is
    // a new announcement of interest.
    return true;
      } else if (currentNum > oldNum) {
    return true;
      } else {
    return false;
      }
  }
  /** No ordering */
  public boolean runAfter(List tasks, int size) {
      return false;
  }
    }

    /** Task which retrieves elements from the set of pendingDiscoveries and
     *  performs the appropriate processing based on the object type of
     *  the element.
     *  <p>
     *  Each element of the set of pendingDiscoveries is one of the following
     *  object types: Socket, LookupLocator, CheckGroupsMarker,
     *  or CheckReachabilityMarker.
     *  <p>
     *  When the element to process is a Socket, the element was a result
     *  of a multicast request/response exchange (see the Requestor and
     *  ResponseListener Thread classes). In this case, this task completes
     *  the discovery of the associated lookup service by performing the
     *  final stage of unicast discovery, ultimately resulting in a discovered
     *  event being sent to all registered listeners.
     *  <p>
     *  When the element to process is a LookupLocator, the element was a
     *  result of a multicast announcement received from a lookup service -
     *  belonging to at least one group of interest - which has not already
     *  been discovered. In this case, this task also completes the discovery
     *  of the lookup service referenced in the announcement by performing the
     *  final stage of unicast discovery, ultimately resulting in a discovered
     *  event being sent to all registered listeners.
     *  <p>
     *  When the element to process is a CheckGroupsMarker, the
     *  element was a result of a multicast announcement received from an
     *  already-discovered lookup service whose member groups have changed
     *  in some way. In this case, this task determines how those member
     *  groups have changed and, based on how they have changed, whether
     *  (or not) to send a discarded event or a changed event to the
     *  appropriate registered listeners.
     *  <p>
     *  When the element to process is a CheckReachabilityMarker, the
     *  element was a result of a determination that the multicast
     *  announcements from an already-discovered lookup service have
     *  stopped being received (see the AnnouncementTimerThread class).
     *  In this case, this task determines if the affected lookup service
     *  is still available ("reachable"). If this task cannot communicate
     *  with the lookup service, a discarded event is queued to be sent
     *  to all registered listeners.
     *  <p>
     *  Rather than performing unicast discovery synchronously, after multicast
     *  discovery has occurred in either the AnnouncementListener thread
     *  (the multicast announcement protocol) or the ResponseListener thread
     *  (the multicast request protocol), the unicast discovery processing
     *  that is required to complete the discovery process is queued for
     *  asynchronous execution in this task. Unicast discovery is performed
     *  asynchronously because unicast discovery can take quite a while to
     *  fail if a lookup service "disappears" (because the network or the
     *  lookup service itself has crashed) between the time a multicast
     *  announcement or response indicates the existence of a lookup service
     *  eligible for unicast discovery, and the time unicast discovery
     *  actually starts. If unicast discovery is performed synchronously
     *  in the threads that implement the multicast announcement and multicast
     *  request protocols, other multicast announcements (as well as other
     *  unicast discoveries) will be missed whenever a lookup service
     *  disappears prior to the commencement of the unicast discovery stage.
     */
    private class UnicastDiscoveryTask implements TaskManager.Task {
  private Object req;
  private Ticket ticket = null;
  private boolean delayRun = false;
  UnicastDiscoveryTask(Object req) {
      this(req, false);
  }
  UnicastDiscoveryTask(Object req, boolean delayRun) {
      this.req = req;
      this.delayRun = delayRun;
  }
  public void run() {
            logger.finest("LookupDiscovery - UnicastDiscoveryTask started");
      try {
    synchronized (this) {
        while (delayRun) {
      this.wait();
        }
        synchronized (pendingDiscoveries) {
      // If this was run by a WakeupManager, remove its
      // ticket from the list of outstanding tickets.
      if (ticket != null) {
          tickets.remove(ticket);
      }
        }
    }
    Socket sock = null;
    MulticastAnnouncement announcement = null;
    UnicastResponse response = null;
    if (req instanceof Socket) {
        // Perform unicast discovery on the connected socket.
        DiscoveryConstraints unicastDiscoveryConstraints =
      DiscoveryConstraints.process(
          rawUnicastDiscoveryConstraints);
        sock = (Socket)req;
        UnicastResponse resp;
        try {
      prepareSocket(sock, unicastDiscoveryConstraints);
      resp = doUnicastDiscovery(sock,
              unicastDiscoveryConstraints);
        } finally {
      try {
          sock.close();
      } catch (IOException e) { /* ignore */ }
        }
        maybeAddNewRegistrar(resp);
    } else if(req instanceof LookupLocator) {
        // Perform unicast discovery using the LookupLocator
        // host and port.
        LookupLocator loc = (LookupLocator)req;
        UnicastResponse resp = new MultiIPDiscovery() {
      protected UnicastResponse performDiscovery(
              Discovery disco,
              DiscoveryConstraints dc,
              Socket s)
          throws IOException, ClassNotFoundException
      {
          return doUnicastDiscovery(s, dc, disco);
      }
      protected void singleResponseException(Exception e,
                     InetAddress addr,
                     int port)
      {
          logger.log(
        Levels.HANDLED,
        "Exception occured during unicast discovery " +
        addr + ":" + port, e);
      }
        }.getResponse(loc.getHost(),
              loc.getPort(),
              rawUnicastDiscoveryConstraints);
        maybeAddNewRegistrar(resp);
    } else if(req instanceof CheckGroupsMarker) {
        // handle group changes
        announcement = ((CheckGroupsMarker)req).announcement;
        ServiceID srvcID = announcement.getServiceID();
        UnicastResponse resp = null;
        synchronized (registrars) {
      resp = (UnicastResponse)registrars.get(srvcID);
        }
        if(resp != null) {
      maybeSendEvent(resp, announcement.getGroups());
        }//endif
    } else if(req instanceof CheckReachabilityMarker) {
        // test reachability
        response = ((CheckReachabilityMarker)req).response;
        maybeSendEvent(response, null);
    }//endif
      } catch (InterruptedIOException e) {
    logger.log(Levels.HANDLED,
         "exception occurred during unicast discovery",
         e);
      } catch (Throwable e) {
    if (((req instanceof Socket) ||
        (req instanceof LookupLocator)) &&
        logger.isLoggable(Level.INFO)) {
        String logmsg =
      "exception occurred during unicast discovery to " +
      "{0}:{1,number,#} with constraints {2}";
        String methodName = "run";
        if (req instanceof Socket) {
      Socket sock = (Socket) req;
      LogUtil.logThrow(logger,
           Level.INFO,
           this.getClass(),
           methodName,
           logmsg,
           new Object[] {
              sock.getInetAddress().getHostName(),
              new Integer(sock.getPort()),
              rawUnicastDiscoveryConstraints
           },
           e);
        } else {
      LookupLocator loc = (LookupLocator) req;
      LogUtil.logThrow(logger,
           Level.INFO,
           this.getClass(),
           methodName,
           logmsg,
           new Object[] {
              loc.getHost(),
              new Integer(loc.getPort()),
              rawUnicastDiscoveryConstraints
           },
           e);
        }
    } else {
        logger.log(Level.INFO,
         "exception occurred during unicast discovery",
         e);
    }
      } finally {
    // Done with the request. Remove it regardless of
    // if we succeeded or failed.
    synchronized (pendingDiscoveries) {
        pendingDiscoveries.remove(req);
    }
      }//end try/catch
            logger.finest("LookupDiscovery - UnicastDiscoveryTask completed");
  }//end run

        /** Returns true if current instance must be run after task(s) in
         *  task manager queue.
         *  @param tasks the tasks to consider.
         *  @param size  elements with index less than size are considered.
         */
        public boolean runAfter(List tasks, int size) {
            return false;
        }//end runAfter
    }//end class UnicastDiscoveryTask

    /**
     * Construct a new lookup discovery object, set to discover the
     * given set of groups.  The set is represented as an array of
     * strings.  This array may be empty, which is taken as the empty
     * set, and discovery is not performed.  The reference passed in
     * may be null, which is taken as no set, and in which case
     * discovery of all reachable lookup services is performed.
     * Otherwise, the array contains the names of groups to discover.
     * The caller must have DiscoveryPermission for each group (or
     * for all groups, if the array is null).
     *
     * @param groups the set of group names to discover (null for no
     *               set, empty for no discovery)
     *
     * @throws java.lang.NullPointerException input array contains at least
     *         one null element
     *
     * @throws java.io.IOException an exception occurred in starting discovery
     *
     * @see #NO_GROUPS
     * @see #ALL_GROUPS
     * @see #setGroups
     * @see DiscoveryPermission
     */
    public LookupDiscovery(String[] groups) throws IOException {
        try {
            beginDiscovery(groups, EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs a new lookup discovery object, set to discover the
     * given set of groups, and having the given <code>Configuration</code>.
     * <p>
     * The set of groups to discover is represented as an array of
     * strings.  This array may be empty, which is taken as the empty
     * set, and discovery is not performed.  The reference passed in
     * may be <code>null</code>, which is taken as no set, and in which
     * case discovery of all reachable lookup services is performed.
     * Otherwise, the array contains the names of groups to discover.
     * The caller must have <code>DiscoveryPermission</code> for each
     * group (or for all groups, if the array is <code>null</code>).
     *
     * @param groups the set of group names to discover (null for no
     *               set, empty for no discovery)
     *
     * @param config an instance of <code>Configuration</code>, used to
     *               obtain the objects needed to configure the current
     *               instance of this class
     *
     * @throws java.lang.NullPointerException input array contains at least
     *         one <code>null</code> element or <code>null</code> is input
     *         for the configuration
     *
     * @throws java.io.IOException an exception occurred in starting discovery
     *
     * @throws net.jini.config.ConfigurationException indicates an exception
     *         occurred while retrieving an item from the given
     *         <code>Configuration</code>
     *
     * @see #NO_GROUPS
     * @see #ALL_GROUPS
     * @see #setGroups
     * @see DiscoveryPermission
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
    public LookupDiscovery(String[] groups, Configuration config)
                                   throws IOException, ConfigurationException
    {
        beginDiscovery(groups, config);
    }//end constructor

    /**
     * Register a listener as interested in receiving DiscoveryEvent
     * notifications.
     *
     * @param l the listener to register
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         <code>null</code> is input to the listener parameter
     *         <code>l</code>.
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see DiscoveryEvent
     * @see #removeDiscoveryListener
     */
    public void addDiscoveryListener(DiscoveryListener l) {
        if(l == null) {
            throw new NullPointerException("can't add null listener");
        }
  synchronized (registrars) {
            if (terminated) {
                throw new IllegalStateException("discovery terminated");
            }
      if (listeners.indexOf(l) >= 0) return; //already have this listener
      listeners.add(l);
      if (registrars.isEmpty()) return;//nothing to send the new listener
            HashMap groupsMap = new HashMap(registrars.size());
      Iterator iter = registrars.values().iterator();
      while (iter.hasNext()) {
                UnicastResponse resp = (UnicastResponse)iter.next();
                groupsMap.put(resp.getRegistrar(),resp.getGroups());
      }
      ArrayList list = new ArrayList(1);
      list.add(l);
      addNotify(list, groupsMap, DISCOVERED);
  }
    }//end addDiscoveryListener

    /**
     * Indicate that a listener is no longer interested in receiving
     * DiscoveryEvent notifications.
     *
     * @param l the listener to unregister
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see #addDiscoveryListener
     */
    public void removeDiscoveryListener(DiscoveryListener l) {
  synchronized (registrars) {
            if (terminated) {
                throw new IllegalStateException("discovery terminated");
            }
      listeners.remove(l);
  }
    }//end removeDiscoveryListener

    /**
     * Returns an array of instances of <code>ServiceRegistrar</code>, each
     * corresponding to a proxy to one of the currently discovered lookup
     * services. For each invocation of this method, a new array is returned.
     *
     * @return array of instances of <code>ServiceRegistrar</code>, each
     *         corresponding to a proxy to one of the currently discovered
     *         lookup services
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see net.jini.core.lookup.ServiceRegistrar
     * @see net.jini.discovery.DiscoveryManagement#removeDiscoveryListener
     */
    public ServiceRegistrar[] getRegistrars() {
        synchronized (registrars) {
            if (terminated) {
                throw new IllegalStateException("discovery terminated");
            }
            if (registrars.isEmpty()) {
                return new ServiceRegistrar[0];
            }
            Iterator iter = registrars.values().iterator();
            ServiceRegistrar[] regs = new ServiceRegistrar[registrars.size()];
            for (int i=0;iter.hasNext();i++) {
                regs[i] = ((UnicastResponse)iter.next()).getRegistrar();
            }
            return regs;
        }
    }//end getRegistrars

    /**
     * Discard a registrar from the set of registrars already
     * discovered.  This does not prevent that registrar from being
     * rediscovered; it is intended to be used to clear unreachable
     * entries from the set. <p>
     *
     * If the registrar has been discovered using this LookupDiscovery
     * object, each listener registered with this object will have its
     * discarded method called with the given registrar as parameter.
     *
     * @param reg the registrar to discard
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see DiscoveryListener#discarded
     */
    public void discard(ServiceRegistrar reg) {
        synchronized (registrars) {
            if (terminated) {
                throw new IllegalStateException("discovery terminated");
            }
            if(reg == null) return;
            sendDiscarded(reg,null);
        }//end sync
    }//end discard

    /** Terminate the discovery process. */
    public void terminate() {
  synchronized (registrars) {
      if (terminatedreturn;
      terminated = true;
  }
  nukeThreads();
    }//end terminate

    /**
     * Return the set of group names this LookupDiscovery instance is
     * trying to discover.  If this method returns the empty array,
     * that value is guaranteed to be referentially equal to
     * LookupDiscovery.NO_GROUPS.
     *
     * @return the set of groups to be discovered (null for all, empty
     *         for no discovery)
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see #NO_GROUPS
     * @see #ALL_GROUPS
     * @see #setGroups
     */
    public String[] getGroups() {
  synchronized (registrars) {
            if (terminated) {
                throw new IllegalStateException("discovery terminated");
            }
      if (groups == null)
    return ALL_GROUPS;
      if (groups.isEmpty())
    return NO_GROUPS;
      return collectionToStrings(groups);
  }
    }//end getGroups

    /**
     * Add a set of groups to the set to be discovered.
     * The caller must have DiscoveryPermission for each group.
     *
     * @param newGroups the groups to add
     *
     * @throws java.io.IOException the multicast request protocol failed
     *         to start
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @throws java.lang.UnsupportedOperationException there is no set of
     *         groups to add to
     *
     * @see DiscoveryPermission
     */
    public void addGroups(String[] newGroups) throws IOException {
        testArrayForNullElement(newGroups);
  checkGroups(newGroups);
  synchronized (registrars) {
      if (terminated)
    throw new IllegalStateException("discovery terminated");
      if (groups == null)
    throw new UnsupportedOperationException(
            "can't add to \"any groups\"");
      Collection req = new ArrayList(newGroups.length);
      for (int i = 0; i < newGroups.length; i++) {
    if (groups.add(newGroups[i]))
        req.add(newGroups[i]);
      }
      if (!req.isEmpty())
    requestGroups(req);
  }
    }//end addGroups

    /**
     * Change the set of groups to be discovered to correspond to the
     * given set.  The set is represented as an array of strings.
     * This array may be empty, which is taken as the empty set, and
     * discovery is not performed.  The reference passed in may be
     * null, which is taken as no set, and in which case discovery of
     * all reachable lookup services is performed.  Otherwise, the
     * array contains the names of groups to discover.
     * The caller must have DiscoveryPermission for each group (or
     * for all groups, if the array is null).
     *
     * @param newGroups the new set of groups to discover (null for
     *                  all, empty array for no discovery)
     *
     * @throws java.io.IOException an exception occurred when starting
     *         multicast discovery
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @see #LookupDiscovery
     * @see #ALL_GROUPS
     * @see #NO_GROUPS
     * @see DiscoveryPermission
     * @see #getGroups
     */
    public void setGroups(String[] newGroups) throws IOException {
        testArrayForNullElement(newGroups);
  checkGroups(newGroups);
  boolean maybeDiscard = false;
  Set newGrps = null;
  if (newGroups != null) { 
      newGrps = new HashSet(newGroups.length * 2);
      for (int i = 0; i < newGroups.length; i++) {
    newGrps.add(newGroups[i]);
      }
  }
  synchronized (registrars) {
      if (terminated)
    throw new IllegalStateException("discovery terminated");
      if (newGroups == null) {
    if (groups != null) {
        groups = null;
        requestGroups(null);
    }
    return;
      }
      if (groups == null) {
    groups = new HashSet(11);
    maybeDiscard = true;
      }
      Set toAdd = new HashSet(newGrps);
      toAdd.removeAll(groups);
      // Figure out which groups to get rid of.  We start off
      // with the full set for which we are already listening,
      // and eliminate any that are in both the new set and the
      // current set.
      Collection toRemove = new HashSet(groups);
      toRemove.removeAll(newGrps);
      // Add new groups before we remove any old groups, because
      // removeGroups will start a new round of multicast requests
      // if the set of groups becomes empty, and we don't want it
      // to do so without reason.
      groups.addAll(toAdd);
      if (!toRemove.isEmpty())
    maybeDiscard |= removeGroupsInt(collectionToStrings(toRemove));
      if (!toAdd.isEmpty())
    requestGroups(toAdd);
  }
  if (maybeDiscard)
      maybeDiscardRegistrars();
    }//end setGroups

    /**
     * Remove a set of groups from the set to be discovered.
     *
     * @param oldGroups groups to remove
     *
     * @throws java.lang.IllegalStateException this exception occurs when
     *         this method is called after the <code>terminate</code>
     *         method has been called.
     *
     * @throws java.lang.UnsupportedOperationException there is no set of
     *         groups from which to remove
     */
    public void removeGroups(String[] oldGroups) {
        testArrayForNullElement(oldGroups);
  boolean maybeDiscard;
  synchronized (registrars) {
      if (terminated)
    throw new IllegalStateException("discovery terminated");
      if (groups == null)
    throw new UnsupportedOperationException(
             "can't remove from \"any groups\"");
      maybeDiscard = removeGroupsInt(oldGroups);
  }
  if (maybeDiscard)
      maybeDiscardRegistrars();
    }//end removeGroups

    /**
     * Sends the given packet data on the given <code>MulticastSocket</code>
     * through each of the network interfaces corresponding to elements of
     * the array configured when this utility was constructed.
     *
     * @param mcSocket   the <code>MulticastSocket</code> on which the data
     *                   will be sent
     * @param packet     <code>DatagramPacket</code> array whose elements are
     *                   the data to send
     *
     * @throws java.io.InterruptedIOException
     */
    private void sendPacketByNIC(MulticastSocket mcSocket,
                                 DatagramPacket[] packet)
                                                 throws InterruptedIOException
    {
        switch(nicsToUse) {
            case NICS_USE_ALL:
                /* Using all interfaces. Skip (but report) any interfaces
                 * that are "bad" or not configured for multicast.
                 */
                for(int i=0;i<nics.length;i++) {
                    try {
                        mcSocket.setNetworkInterface(nics[i]);
                        sendPacket(mcSocket,packet);
                    } catch(InterruptedIOException e) {
                        throw e;//to signal a graceful exit
                    } catch(IOException e) {
                        if( logger.isLoggable(Levels.HANDLED) ) {
                            LogRecord logRec =
                              new LogRecord(Levels.HANDLED,
              "network interface is "
                                            +"bad or not configured for "
                                            +"multicast: {0}");
                            logRec.setParameters(new Object[]{nics[i]});
                            logRec.setThrown(e);
                            logger.log(logRec);
                        }//endif
                    } catch(Exception e) {
                        if( logger.isLoggable(Levels.HANDLED) ) {
                            LogRecord logRec =
                              new LogRecord(Levels.HANDLED, "exception while "
                                            +"sending packet through network "
                                            +"interface: {0}");
                            logRec.setParameters(new Object[]{nics[i]});
                            logRec.setThrown(e);
                            logger.log(logRec);
                        }//endif
                    }
                }//end loop
                break;
            case NICS_USE_LIST:
                /* Using a configured list of specific interfaces. Skip (but
                 * always report) any interfaces that are "bad" or not
                 * configured for multicast.
                 */
                for(int i=0;i<nics.length;i++) {
                    try {
                        mcSocket.setNetworkInterface(nics[i]);
                        sendPacket(mcSocket,packet);
                    } catch(InterruptedIOException e) {
                        throw e;//to signal a graceful exit
                    } catch(IOException e) {
                        if( logger.isLoggable(Level.SEVERE) ) {
                            LogRecord logRec =
                              new LogRecord(Level.SEVERE,"network interface "
                                            +"is bad or not configured for "
                                            +"multicast: {0}");
                            logRec.setParameters(new Object[]{nics[i]});
                            logRec.setThrown(e);
                            logger.log(logRec);
                        }//endif
                    } catch(Exception e) {
                        if( logger.isLoggable(Level.SEVERE) ) {
                            LogRecord logRec =
                              new LogRecord(Level.SEVERE,"exception while "
                                            +"sending packet through network "
                                            +"interface: {0}");
                            logRec.setParameters(new Object[]{nics[i]});
                            logRec.setThrown(e);
                            logger.log(logRec);
                        }//endif
                    }
                }//end loop
                break;
            case NICS_USE_SYS:
                /* Using the system-dependent default interface. Don't need
                 * to specifically set the interface. If that interface is
                 * "bad" or not configured for multicast, always report it.
                 */
                try {
                    sendPacket(mcSocket,packet);
                } catch(InterruptedIOException e) {
                    throw e;//to signal a graceful exit
                } catch(IOException e) {
                    if( logger.isLoggable(Level.SEVERE) ) {
                        logger.log(Level.SEVERE, "system default network "
                                   +"interface is bad or not configured "
                                   +"for multicast", e);
                    }//endif
                } catch(Exception e) {
                    if( logger.isLoggable(Level.SEVERE) ) {
                        logger.log(Level.SEVERE, "exception while sending "
                                   +"packet through system default network "
                                   +"interface", e);
                    }//endif
                }
                break;
            case NICS_USE_NONE:
                break;//multicast disabled, do nothing
            default:
                throw new AssertionError("nicsToUse flag out of range (0-3): "
                                         +nicsToUse);
        }//end switch(nicsToUse)
    }//end sendPacketByNIC

    /**
     * Sends the given packet data on the given <code>MulticastSocket</code>
     * through the network interface that is currently set.
     *
     * @param mcSocket the <code>MulticastSocket</code> on which the data
     *                 will be sent
     * @param packet   <code>DatagramPacket</code> array whose elements are
     *                 the data to send
     *
     * @throws java.io.IOException
     */
    private static void sendPacket(MulticastSocket mcSocket,
                                   DatagramPacket[] packet) throws IOException
    {
        for(int i=0;i<packet.length;i++) {
            mcSocket.send(packet[i]);
        }//end loop
    }//end sendPacket

    /** Returns the local host name. */
    private static String getLocalHost() throws UnknownHostException {
  try {
      return ((InetAddress) Security.doPrivileged(
    new PrivilegedExceptionAction() {
        public Object run() throws UnknownHostException {
      return InetAddress.getLocalHost();
        }
    })).getHostAddress();
  } catch (PrivilegedActionException e) {
      // Remove host information if caller does not have privileges
      // to see it.
      try {
    InetAddress.getLocalHost();
      } catch (UnknownHostException uhe) {
    throw uhe;
      }
      logger.log(Levels.FAILED, "Unknown host exception", e.getCause());
      throw new UnknownHostException("Host name cleared due to " +
             "insufficient caller permissions");
  }
    }

    /** Determines if the caller has discovery permission for each group. */
    private static void checkGroups(String[] groups) {
  SecurityManager sm = System.getSecurityManager();
  if (sm == nullreturn;
  if (groups != null) {
      for (int i = 0; i < groups.length; i++) {
    sm.checkPermission(new DiscoveryPermission(groups[i]));
      }//end loop
  } else {
      sm.checkPermission(new DiscoveryPermission("*"));
  }//endif
    }//end checkGroups

    /** Converts a collection to an array of strings. */
    private static final String[] collectionToStrings(Collection c) {
  return c == null ? null : (String[]) c.toArray(new String[c.size()]);
    }//end collectionToStrings

    /** Determines if two sets of registrar member groups have identical
     *  contents. Assumes there are no duplicates, and the sets can never
     *  be null.
     *
     *  @param groupSet0    <code>String</code> array containing the group
     *                      names from the first set used in the comparison
     *  @param groupSet1    <code>String</code> array containing the group
     *                      names from the second set used in the comparison
     *
     *  @return <code>true</code> if the contents of each set is identical;
     *          <code>false</code> otherwise
     */
    private static boolean groupSetsEqual(String[] groupSet0,
                                          String[] groupSet1)
    {
        if(groupSet0.length != groupSet1.length) return false;
        /* is every element of one set contained in the other set? */
        iLoop:
        for(int i=0;i<groupSet0.length;i++) {
            for(int j=0;j<groupSet1.length;j++) {
                if( groupSet0[i].equals(groupSet1[j]) ) {
                    continue iLoop;
                }
            }//end loop(j)
            return false;
        }//end loop(i)
        return true;
    }//end groupSetsEqual

    /** Returns true if the registrars contained in the given (possibly null)
     *  UnicastResponse instances are equals() to one another.
     */
    private static boolean registrarsEqual(UnicastResponse resp1,
             UnicastResponse resp2)
    {
  return resp1 != null && resp2 != null &&
         resp2.getRegistrar().equals(resp1.getRegistrar());
    }//end registrarsEqual

    /**
     * Remove the specified groups from the set of groups to discover, and
     * return true if any were actually removed.
     */
    private boolean removeGroupsInt(String[] oldGroups) {
  boolean removed = false;
  for (int i = 0; i < oldGroups.length; i++) {
      removed |= groups.remove(oldGroups[i]);
  }
  return removed;
    }//end removeGroupsInt

    /** Returns the service IDs of the lookup service(s) discovered to date. */
    private ServiceID[] getServiceIDs() {
  synchronized (registrars) {
      return (ServiceID[])
    registrars.keySet().toArray(new ServiceID[registrars.size()]);
  }//end sync
    }//end getServiceIDs

    /**
     * Indicate whether any of the group names in the given array match
     * any of the groups of interest.
     *
     * @param possibilities the set of group names to compare to the set
     *                      of groups to discover (must not be null)
     */
    private boolean groupsOverlap(String[] possibilities) {
  /* Match if we're interested in any group, or if we're
   * interested in none and there are no possibilities.
         */
  if (groups == nullreturn true;
  for (int i = 0; i < possibilities.length; i++) {
      if (groups.contains(possibilities[i]))  return true;
  }//end loop
  return false;
    }//end groupsOverlap

    /** Called at startup and whenever the set of groups to discover is
     *  changed. This method executes the multicast request protocol by
     *  starting the ResponseListener thread to listen for multicast
     *  responses; and starting a Requestor thread to send out multicast
     *  requests for the set of groups contained in the given Collection.
     */
    private void requestGroups(final Collection req) throws IOException {
  try {
            Security.doPrivileged(new PrivilegedExceptionAction() {
    public Object run() throws Exception {
        Thread t;
        synchronized (requestors) {
      if (respondeeThread == null) {
          respondeeThread = new ResponseListener();
          respondeeThread.start();
      }//endif
      boolean delayFlag = false;
      if (!initialRequestorStarted) {
          // only delay the first time
          delayFlag = true;
          initialRequestorStarted = true;
      }
      t = new Requestor(collectionToStrings(req),
            respondeeThread.getPort(),
            delayFlag);
      requestors.add(t);
        }//end sync
        t.start();
        return null;
    }//run
      });//end doPrivileged
  } catch (PrivilegedActionException e) {
      throw (IOException)e.getException();
  }//end try/catch
    }//end requestGroups
   
    private static void prepareSocket(Socket s, DiscoveryConstraints dc)
  throws SocketException
    {
  try {
      s.setTcpNoDelay(true);
  } catch (SocketException e) {
      // ignore possible failures and proceed anyway
  }
  try {
      s.setKeepAlive(true);
  } catch (SocketException e) {
      // ignore possible failures and proceed anyway
  }
  s.setSoTimeout(dc.getUnicastSocketTimeout(DEFAULT_SOCKET_TIMEOUT));
    }
   
    /**
     * If the lookup service associated with the given UnicastResponse
     * is not in the set of already-discovered lookup services, this method
     * adds it to that set, and each registered listener is notified.
     *
     * @param resp the UnicastResponse associated with the lookup
     *             service to add
     */
    private void maybeAddNewRegistrar(UnicastResponse resp) {
        /* If the group names contained in the given incoming unicast response
         * don't match any of the groups of interest, then don't waste time
         * performing an unnecessary proxy preparation; simply return.
         */
        synchronized(registrars) {
            if( !groupsOverlap(resp.getGroups()) )  return;
        }//end sync(registrars)

        /* Proxy preparation -
         *
         * The given incoming unicast response contains a proxy to a lookup
         * service that belongs to at least one of the groups of interest.
         * Before adding that proxy to the managed set of discovered lookup
         * services, and before notifying any of the registered listeners,
         * that proxy should be prepared. This is necessary in this utility
         * because that lookup service may be tested for reachability at
         * some point. Since that test involves a remote call (to getGroups())
         * through the proxy, the proxy should be prepared.
         *
         * The preparation of that proxy is performed inside a doPrivileged
         * block that restores the access control context that was in place
         * when this utility was created. In this way, any code that is
         * executed as a part of preparing the proxy will be executed with
         * no additional permissions beyond the permissions that were granted
         * to the client that created this utility. This is done because the
         * proxy preparer executed below is provided by the deployer and thus
         * can be viewed as an artifact of the client. Therefore, before
         * executing the preparer's code in this utility, the client's
         * Subject should be restored, and the preparer code should be
         * restricted to doing nothing more than the client itself is
         * allowed to do.
         *
         * Note that it's okay to modify the state of the given incoming
         * unicast response here because, prior to modification and storage
         * in the managed set of registrars in this method, it is assumed that
         * that object is not accessed by any other thread.
         */
        try {
      final ServiceRegistrar srcReg = resp.getRegistrar();
            ServiceRegistrar prepReg
    = (ServiceRegistrar)AccessController.doPrivileged
        ( securityContext.wrap( new PrivilegedExceptionAction() {
                        public Object run() throws RemoteException {
                            Object proxy = registrarPreparer.prepareProxy
                                                            (srcReg);
                            logger.log(Level.FINEST, "LookupDiscovery - "
                                       +"prepared lookup service proxy: {0}",
                                       proxy);
                            return proxy;
                        }//end run
                    }),//end PrivilegedExceptionAction and wrap
                  securityContext.getAccessControlContext());//end doPrivileged
      if (prepReg != srcReg) {
    resp = new UnicastResponse(resp.getHost(),
             resp.getPort(),
             resp.getGroups(),
             prepReg);
      }
  } catch (Exception e) {
            Exception e1 = ( (e instanceof PrivilegedActionException) ?
                            ((PrivilegedActionException)e).getException() : e);
            logger.log(Level.INFO,
                       "exception while preparing lookup service proxy",
                       e1);
      return;
  }
        /* Add any newly discovered registrars to the managed set and notify
         * all listeners.
         */
  synchronized (registrars) {
      if(groupsOverlap(resp.getGroups()) &&
         !registrarsEqual(resp,
        (UnicastResponse) registrars.put
           (resp.getRegistrar().getServiceID(), resp)))
      {
                /* Time stamp the service ID and store its current sequence
     * number. The first time stamp associated
                 * with the current service ID occurs here. All other time
                 * stamps for that service ID will occur when multicast
                 * announcements for that service ID arrive (in the
                 * AnnouncementListener thread).
                 *
                 * Note that if the time stamp for the service ID were
                 * initialized upon the arrival of the first announcement,
                 * rather than here when it is first discovered, the
                 * AnnouncementTimerThread would not be able to detect the
                 * termination of announcements for the case where the
                 * termination happens to occur between the time the lookup
                 * is first discovered here, and the time the first
                 * announcement was supposed to have arrived. This can
                 * happen because a multicast request from the client can
                 * cause the lookup to be discovered before the first
                 * announcement arrives.
                 */
                regInfo.put(resp.getRegistrar().getServiceID(),
       new AnnouncementInfo(System.currentTimeMillis(), -1));
                if(!listeners.isEmpty()) {
        addNotify((ArrayList)listeners.clone(),
                              mapRegToGroups(resp.getRegistrar(),
               resp.getGroups()),
                              DISCOVERED);
                }//endif
      }//endif
  }//end sync(registrars)
    }//end maybeAddNewRegistrar

    /** Determine if any of the already-discovered registrars are no longer
     *  members of any of the groups to discover, and discard those registrars
     *  that are no longer members of any of those groups.
     */
    private void maybeDiscardRegistrars() {
  synchronized (registrars) {
            HashMap groupsMap = new HashMap(registrars.size());
      for(Iterator iter=registrars.values().iterator();iter.hasNext(); ){
    UnicastResponse ent = (UnicastResponse)iter.next();
    if(!groupsOverlap(ent.getGroups())) { // not interested anymore
                    groupsMap.put(ent.getRegistrar(),ent.getGroups());
                    regInfo.remove(ent.getRegistrar().getServiceID());
        iter.remove(); // remove (srvcID,response) mapping
    }//endif
      }//end loop
            if( !groupsMap.isEmpty() && !listeners.isEmpty() ) {
    addNotify((ArrayList)listeners.clone(), groupsMap, DISCARDED);
      }//endif
  }//end sync
    }//end maybeDiscardRegistrars

    /**
     * Add a notification task to the pending queue, and start an instance of
     * the Notifier thread if one isn't already running.
     */
    private void addNotify(ArrayList notifies, Map groupsMap, int eventType) {
  synchronized (pendingNotifies) {
      pendingNotifies.addLast(new NotifyTask(notifies,
                                                   groupsMap,
                                                   eventType));
      if (notifierThread == null) {
                Security.doPrivileged(new PrivilegedAction() {
        public Object run() {
      notifierThread = new Notifier();
      notifierThread.start();
      return null;
        }//end run
    });//end doPrivileged
      }//endif
  }//end sync
    }//end addNotify

    /** Terminates (interrupts) all currently-running threads. */
    private void nukeThreads() {
        Security.doPrivileged(new PrivilegedAction() {
      public Object run() {
                if(announcementTimerThread != null) {
                    announcementTimerThread.interrupt();
                }//endif
    synchronized (requestors) {
        for (Iterator iter = requestors.iterator();
       iter.hasNext(); )
        {
      Thread t = (Thread) iter.next();
      t.interrupt();
        }
        if (respondeeThread != null)
      respondeeThread.interrupt();
    }
                if(announceeThread != null) {
                    announceeThread.interrupt();
                }//endif
    synchronized (pendingDiscoveries) {
                    terminateTaskMgr();
        Iterator i = tickets.iterator();
        while (i.hasNext()) {
      Ticket t = (Ticket) i.next();
      i.remove();
      discoveryWakeupMgr.cancel(t);
        }
        if (isDefaultWakeupMgr) {
      // cancelAll should be a no-op in this case,
      // but just be sure.
      discoveryWakeupMgr.cancelAll();
      discoveryWakeupMgr.stop();
        }
    }//end sync
    return null;
      }//end run
  });//end doPrivileged
    }//end nukeThreads

    /** This method removes all pending and active tasks from the TaskManager
     *  for this instance. It also clears the set of pendingDiscoveries, and
     *  closes all associated sockets.
     */
    private void terminateTaskMgr() {
        synchronized(taskManager) {
            /* Remove all pending tasks */
            ArrayList pendingTasks = taskManager.getPending();
            for(int i=0;i<pendingTasks.size();i++) {
                taskManager.remove((TaskManager.Task)pendingTasks.get(i));
            }//end loop
            /* Clear pendingDiscoveries and close all associated sockets */
            synchronized (pendingDiscoveries) {
                for(Iterator iter = pendingDiscoveries.iterator();
                    iter.hasNext();)
                {
                    Object req = iter.next();
                    iter.remove();
                    if (req instanceof Socket) {
                        try {
                            ((Socket)req).close();
                        } catch (IOException e) { /* ignore */ }
                    }//endif
                }//end loop
            }//end sync
            /* Interrupt active TaskThreads, prepare the taskManager for GC. */
            taskManager.terminate();
        }//end sync(taskManager)
        synchronized(pendingNotifies) {
            pendingNotifies.clear();
        }//end sync
    }//end terminateTaskMgr

    /** After a possible change in the member groups of the
     *  <code>ServiceRegistrar</code> corresponding to the given
     *  <code>UnicastResponse</code> parameter, this method
     *  determines whether or not the registrar's member groups have
     *  changed in such a way that either a changed event or a discarded
     *  event is warranted.
     *  <p>
     *  Note that even if the contents of the new set of groups initially
     *  indicate that the corresponding registrar is a candidate for
     *  a discarded or a changed event, further analysis must be performed.
     *  This is because there is no guarantee that the new set of member
     *  groups have not been "split" across the multicast announcements
     *  sent by the lookup service; and so there is no guarantee that the
     *  contents of the new group set actually reflect a change that warrants
     *  an event. To guarantee that the new group set accurately reflects
     *  the registrar's member groups, this method makes a remote call to
     *  the registrar to retrieve its actual member groups.
     *  <p>
     *  There is one situation where it is not necessary to query the
     *  registrar for its current member groups. That situation is
     *  when the set of groups input to the <code>newGroups</code> parameter
     *  is equivalent to NO_GROUPS. If that new group set is equivalent
     *  to NO_GROUPS, it is guaranteed that the registrar's member groups
     *  have not been split across the multicast announcements.
     *
     * @param response  instance of <code>UnicastResponse</code>
     *                  corresponding to the registrar whose current and
     *                  previous member groups are to be compared
     * @param newGroups <code>String</code> array containing the new
     *                  member groups of the registrar corresponding to the
     *                  <code>response</code> parameter (just after a
     *                  possible change)
     */
    private void maybeSendEvent(UnicastResponse response, String[] newGroups) {
        ServiceRegistrar reg = response.getRegistrar();
        boolean getActual    = true;
        if(newGroups == null) { // newGroups null means get actual groups now
            newGroups = getActualGroups(reg);
            if(newGroups == null) return; // if null, then it was discarded
            getActual = false;
        }//endif

        if(groupSetsEqual(response.getGroups(),newGroups)) return;

        String[] actualGroups = newGroups;
        if( getActual && (newGroups.length > 0) ) {
            actualGroups = getActualGroups(reg);
            if(actualGroups == null) return; // null ==> was already discarded
        }//endif
 
  synchronized (registrars) {
      // Other events may have occured to registrars while we were
      // making our remote call.
      UnicastResponse resp =
    (UnicastResponse) registrars.get(reg.getServiceID());
      if (resp == null) {
    // The registrar was discarded in the meantime. Oh well.
    return;
      }
      notifyOnGroupChange(reg, resp.getGroups(), actualGroups);
  }
    }//end maybeSendEvent

    /** After a possible change in the member groups of the given
     *  <code>ServiceRegistrar</code> parameter, this method compares
     *  the registrar's original set of member groups to its new set
     *  of member groups.
     *  <p>
     *  If the criteria shown below is satisfied, either a discarded event
     *  or a changed event will be sent to any registered listeners. The
     *  criteria is based on whether the old and new groups are equal,
     *  and whether one or more elements of the new group set also belong
     *  to the set of groups to discover (the new groups are "still of
     *  interest"). The criteria is as follows:
     *  <p>
     *  if (old groups and new groups)
     *  <p><ul>
     *       <li> (not equal but stillInterested) --> send a changed event
     *       <li> (!stillInterested)              --> send a discarded event
     *    </ul>
     <p>
     *
     * @param reg       instance of <code>ServiceRegistrar</code>
     *                  corresponding to the registrar whose current and
     *                  previous member groups are to be compared; and
     *                  whose corresponding service ID is used as the key
     *                  into the various data structures that contain
     *                  pertinent information about that registrar
     * @param oldGroups <code>String</code> array containing the member
     *                  groups of the <code>reg</code> parameter prior to
     *                  being changed
     * @param oldGroups <code>String</code> array containing the current
     *                  member groups of the <code>reg</code> parameter
     *                  (just after a possible change)
     */
    private void notifyOnGroupChange(ServiceRegistrar reg,
                                     String[]         oldGroups,
                                     String[]         newGroups)
    {
        boolean equal           = groupSetsEqual(oldGroups,newGroups);
        boolean stillInterested = groupsOverlap(newGroups);
        if(!equal && stillInterested) {
            sendChanged(reg,newGroups);
        } else if(!stillInterested) {
            sendDiscarded(reg,newGroups);
        }//endif
    }//end notifyOnGroupChange

    /** Convenience method that sends a discarded event containing only
     *  one registrar to all registered listeners. This method must be
     *  called from within a block that is synchronized on the registrars
     *  map.
     *
     *  @param reg       instance of <code>ServiceRegistrar</code>
     *                   corresponding to the registrar to include in the
     *                   event
     *  @param curGroups <code>String</code> array containing the current
     *                   member groups of the registrar referenced by the
     *                   <code>reg</code> parameter
     */
    private void sendDiscarded(ServiceRegistrar reg, String[] curGroups) {
        ServiceID srvcID = reg.getServiceID();
        if(curGroups == null) { // discard request is from external source
            UnicastResponse resp = (UnicastResponse)registrars.get(srvcID);
            if(resp == null) return;
            curGroups = resp.getGroups();
        }//endif
        if( registrars.remove(srvcID) != null ) {
            regInfo.remove(srvcID);
            if( !listeners.isEmpty() ) {
                addNotify((ArrayList)listeners.clone(),
                           mapRegToGroups(reg,curGroups), DISCARDED);
            }//endif
        }//endif
    }//end sendDiscarded

    /** Convenience method that sends a changed event containing only
     *  one registrar to all registered listeners that are interested in
     *  such events. This method must be called from within a block that
     *  is synchronized on the registrars map.
     *
     *  @param reg       instance of <code>ServiceRegistrar</code>
     *                   corresponding to the registrar to include in the
     *                   event
     *  @param curGroups <code>String</code> array containing the current
     *                   member groups of the registrar referenced by the
     *                   <code>reg</code> parameter
     */
    private void sendChanged(ServiceRegistrar reg, String[] curGroups) {
        /* replace old groups with new; prevents repeated changed events */
        UnicastResponse resp =
                   (UnicastResponse)registrars.get(reg.getServiceID());
  registrars.put(reg.getServiceID(),
           new UnicastResponse(resp.getHost(),
             resp.getPort(),
             curGroups,
             resp.getRegistrar()));
        if( !listeners.isEmpty() ) {
            addNotify((ArrayList)listeners.clone(),
                       mapRegToGroups(reg,curGroups), CHANGED);
        }//endif
    }//end sendChanged

    /** Creates and returns a deep copy of the input parameter. This method
     *  assumes the input map is a HashMap of the registrar-to-groups mapping;
     *  and returns a clone not only of the map, but of each key-value pair
     *  contained in the mapping.
     *
     * @param groupsMap mapping from a set of registrars to the member groups
     *                  of each registrar
     *
     *  @return clone of the input map, and of each key-value pair contained
     *          in the input map
     */
    private Map deepCopy(HashMap groupsMap) {
        /* clone the input HashMap */
        HashMap newMap = (HashMap)(groupsMap.clone());
        /* clone the values of each mapping in place */
        Set eSet = newMap.entrySet();
        for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
            Map.Entry pair = (Map.Entry)itr.next();
            /* only need to clone the value of the order pair */
            pair.setValue( ((String[])pair.getValue()).clone() );
        }
        return newMap;
    }//end deepCopy

    /** This method retrieves from the given <code>ServiceRegistrar</code>,
     *  the current groups in which that registrar is a member. If the
     *  registrar is un-reachable, then this method will discard the
     *  registrar.
     *
     * @param reg instance of <code>ServiceRegistrar</code> referencing the
     *            registrar whose member groups are to be retrieved and returned
     *
     *  @return <code>String</code> array containing the current member groups
     *          of the registrar referenced by the <code>reg</code> parameter
     */
    private String[] getActualGroups(final ServiceRegistrar reg) {
        /* The retrieval of the member groups of the given ServiceRegistrar
         * is performed inside a doPrivileged block that restores the access
         * control context that was in place when this utility was created.
         *
         * This is done because the call to getGroups() below is executed
         * on a proxy to the given ServiceRegistrar; which may be downloaded
         * code supplied by a 3rd party. With respect to downloaded, 3rd party
         * code, it is not desirable to allow such code to execute with more
         * priviledges than the client that created this utility. Therefore,
         * before executing getGroups() on the given proxy, the client's
         * Subject should be restored, and the proxy code should be
         * restricted to doing nothing more than the client itself is
         * allowed to do.
         */
        try {
            return (String[])AccessController.doPrivileged
                     ( securityContext.wrap(new PrivilegedExceptionAction()
                           { public Object run() throws RemoteException {
                                   return reg.getGroups();
                               }//end run
                           }),//end PrivilegedExceptionAction and wrap
                       securityContext.getAccessControlContext());//end doPriv
        } catch(Throwable e) {
            /* A RemoteException, wrapped in a PriviligedActionException,
             * occurred. This means that the reg is unreachable; discard it.
             */
            discard(reg);
            return null;
        }//end try

    }//end getActualGroups

    /** Convenience method that creates and returns a mapping of a single
     *  <code>ServiceRegistrar</code> instance to a set of groups.
     *
     *  @param reg       instance of <code>ServiceRegistrar</code>
     *                   corresponding to the registrar to use as the key
     *                   to the mapping
     *  @param curGroups <code>String</code> array containing the current
     *                   member groups of the registrar referenced by the
     *                   <code>reg</code> parameter; and which is used
     *                   as the value of the mapping
     *
     *   @return <code>Map</code> instance containing a single mapping from
     *           a given registrar to its current member groups
     */
    private Map mapRegToGroups(ServiceRegistrar reg, String[] curGroups) {
        HashMap groupsMap = new HashMap(1);
        groupsMap.put(reg,curGroups);
        return groupsMap;
    }//end mapRegToGroups

    /**
     * This method is used by the public methods of this class that are
     * specified to throw a <code>NullPointerException</code> when the given
     * array of group names contains one or more <code>null</code> elements;
     * in which case, this method throws a <code>NullPointerException</code>
     * which should be allowed to propagate outward.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         one or more of the elements of the <code>groupArray</code>
     *         parameter is <code>null</code>.
     */
    private void testArrayForNullElement(String[] groupArray) {
        if(groupArray == null) return;
        for(int i=0;i<groupArray.length;i++) {
            if(groupArray[i] == null) {
                throw new NullPointerException("null element in group array");
            }//endif
        }//end loop
    }//end testArrayForNullElement

    /**
     * Using the given <code>Configuration</code>, initializes the current
     * instance of this utility, and initiates the discovery process for
     * the given set of groups.
     *
     * @param groups the set of group names to discover
     *
     * @param config an instance of <code>Configuration</code>, used to
     *               obtain the objects needed to configure this utility
     *
     * @throws java.lang.NullPointerException input array contains at least
     *         one <code>null</code> element or <code>null</code> is input
     *         for the configuration
     *
     * @throws java.io.IOException an exception occurred when initiating
     *         discovery processing
     *
     * @throws net.jini.config.ConfigurationException indicates an exception
     *         occurred while retrieving an item from the given
     *         <code>Configuration</code>
     */
    private void beginDiscovery(String[] groups, Configuration config)
                                    throws IOException, ConfigurationException
    {
        testArrayForNullElement(groups);
  checkGroups(groups);
  if (groups != null) {
      this.groups = new HashSet(groups.length * 2);
      for (int i = 0; i < groups.length; i++) {
    this.groups.add(groups[i]);
      }//end loop
  }//endif
        init(config);
        if(nicsToUse ==  NICS_USE_NONEreturn;//disable discovery
  try {
            Security.doPrivileged(new PrivilegedExceptionAction() {
    public Object run() throws Exception {
        announceeThread = new AnnouncementListener();
                    announcementTimerThread = new AnnouncementTimerThread();
        return null;
    }//end run
      });//end doPrivileged
  } catch (PrivilegedActionException e) {
      throw (IOException)e.getException();
  }
  if (this.groups == null || !this.groups.isEmpty()) {
            requestGroups(this.groups);
        }//endif
  announceeThread.start();
  announcementTimerThread.start();
    }//end beginDiscovery

    /* Convenience method that encapsulates the retrieval of the configurable
     * items from the given <code>Configuration</code> object.
     */
    private void init(Configuration config) throws IOException,
                                                   ConfigurationException
    {
        if(config == nullthrow new NullPointerException("config is null");
        /* Lookup service proxy preparer */
        registrarPreparer = (ProxyPreparer)config.getEntry
                                                    (COMPONENT_NAME,
                                                     "registrarPreparer",
                                                     ProxyPreparer.class,
                                                     new BasicProxyPreparer());
  /* constraints */
  MethodConstraints constraints = (MethodConstraints)config.getEntry
                (COMPONENT_NAME,
                 "discoveryConstraints",
                 MethodConstraints.class,
                 null);
  if (constraints == null) {
      constraints =
    new BasicMethodConstraints(InvocationConstraints.EMPTY);
  }
  multicastRequestConstraints = DiscoveryConstraints.process(
      constraints.getConstraints(
    DiscoveryConstraints.multicastRequestMethod));
  multicastAnnouncementConstraints = DiscoveryConstraints.process(
      constraints.getConstraints(
    DiscoveryConstraints.multicastAnnouncementMethod));
  rawUnicastDiscoveryConstraints =
      constraints.getConstraints(
    DiscoveryConstraints.unicastDiscoveryMethod);

        /* Task manager */
        try {
            taskManager = (TaskManager)config.getEntry(COMPONENT_NAME,
                   "taskManager",
                   TaskManager.class);
        } catch(NoSuchEntryException e) { /* use default */
            taskManager = new TaskManager(MAX_N_TASKS,(15*1000),1.0f);
        }

        /* Multicast request-related configuration items */
        multicastRequestMax
         = ( (Integer)config.getEntry
                             (COMPONENT_NAME,
                              "multicastRequestMax",
                              int.class,
                              new Integer(multicastRequestMax) ) ).intValue();
        multicastRequestInterval
         = ( (Long)config.getEntry
                            (COMPONENT_NAME,
                            "multicastRequestInterval",
                            long.class,
                            new Long(multicastRequestInterval) ) ).longValue();
        finalMulticastRequestInterval
         = ( (Long)config.getEntry
                      (COMPONENT_NAME,
                       "finalMulticastRequestInterval",
                       long.class,
                       new Long(finalMulticastRequestInterval) ) ).longValue();
  try {
      multicastRequestHost
       = (String) Config.getNonNullEntry(config,
                 COMPONENT_NAME,
                 "multicastRequestHost",
                 String.class);
  } catch (NoSuchEntryException nse) {
      multicastRequestHost = getLocalHost();
  }
        /* Configuration items related to the network interface(s) */
        try {
            nics = (NetworkInterface[])config.getEntry
                                                   (COMPONENT_NAME,
                                                    "multicastInterfaces",
                                                    NetworkInterface[].class);
            if(nics == null) {
                nicsToUse = NICS_USE_SYS;
                logger.config("LookupDiscovery - using system default network "
                              +"interface for multicast");
            } else {//(nics != null)
                if( nics.length == 0 ) {
                    nicsToUse = NICS_USE_NONE;
                    logger.config("LookupDiscovery - MULTICAST DISABLED");
                } else {//(nics.length > 0), use the given specific list
                    nicsToUse = NICS_USE_LIST;
                    if( logger.isLoggable(Level.CONFIG) ) {
                        logger.log(Level.CONFIG,
                               "LookupDiscovery - multicast network "
                               +"interface(s): {0}", Arrays.asList(nics) );
                    }//endif
                }//endif
            }//endif
        } catch(NoSuchEntryException e) {// no config item, use default - all
      Enumeration en = NetworkInterface.getNetworkInterfaces();
      List nicList = (en != null) ?
    Collections.list(en) : Collections.EMPTY_LIST;
            nics = (NetworkInterface[])(nicList.toArray
                                     (new NetworkInterface[nicList.size()]) );
            nicsToUse = NICS_USE_ALL;
            if( logger.isLoggable(Level.CONFIG) ) {
                logger.log(Level.CONFIG,"LookupDiscovery - multicast network "
                                        +"interface(s): {0}", nicList);
            }//endif
        }
        nicRetryInterval
         = ( (Integer)config.getEntry
                                (COMPONENT_NAME,
                                 "multicastInterfaceRetryInterval",
                                 int.class,
                                 new Integer(nicRetryInterval) ) ).intValue();
        /* Multicast announcement-related configuration items */
        multicastAnnouncementInterval
         = ( (Long)config.getEntry
          (COMPONENT_NAME,
           "multicastAnnouncementInterval",
           long.class,
           new Long(multicastAnnouncementInterval) ) ).longValue();

  unicastDelayRange = Config.getLongEntry(config,
            COMPONENT_NAME,
            "unicastDelayRange",
            0,
            0,
            Long.MAX_VALUE);
  tickets = new ArrayList();
  if (unicastDelayRange > 0) {
      /* Wakeup manager */
      try {
    discoveryWakeupMgr =
      (WakeupManager)config.getEntry(COMPONENT_NAME,
                   "wakeupManager",
                   WakeupManager.class);
      } catch(NoSuchEntryException e) { /* use default */
    discoveryWakeupMgr = new WakeupManager(
             new WakeupManager.ThreadDesc(null, true));
    isDefaultWakeupMgr = true;
      }
  }

  initialMulticastRequestDelayRange = Config.getLongEntry(config,
          COMPONENT_NAME,
          "initialMulticastRequestDelayRange",
          0,
          0,
          Long.MAX_VALUE);
    }//end init

    /**
     * Decodes received multicast announcement packet. Constraint checking is
     * delayed.
     */
    private MulticastAnnouncement decodeMulticastAnnouncement(
                final DatagramPacket pkt)
  throws IOException
    {
  // REMIND: cache recently received announcements to skip re-decoding?
  int pv;
  try {
      pv = ByteBuffer.wrap(
    pkt.getData(), pkt.getOffset(), pkt.getLength()).getInt();
  } catch (BufferUnderflowException e) {
      throw new DiscoveryProtocolException(null, e);
  }
  multicastAnnouncementConstraints.checkProtocolVersion(pv);
  final Discovery disco = getDiscovery(pv);

  try {
      return (MulticastAnnouncement) AccessController.doPrivileged(
    securityContext.wrap(new PrivilegedExceptionAction() {
        public Object run() throws IOException {
      return disco.decodeMulticastAnnouncement(
          pkt,
          multicastAnnouncementConstraints.
        getUnfulfilledConstraints(),
        true);
        }
    }), securityContext.getAccessControlContext());
  } catch (PrivilegedActionException e) {
      throw (IOException) e.getCause();
  }
    }

    /**
     * Encodes outgoing multicast requests based on protocol in use, applying
     * configured security constraints (if any).
     */
    private DatagramPacket[] encodeMulticastRequest(final MulticastRequest req)
  throws IOException
    {
  // REMIND: cache latest request to skip re-encoding
  final Discovery disco = getDiscovery(
      multicastRequestConstraints.chooseProtocolVersion());
  final List packets = new ArrayList();
  AccessController.doPrivileged(
      securityContext.wrap(new PrivilegedAction() {
    public Object run() {
        EncodeIterator ei = disco.encodeMulticastRequest(
      req,
      multicastRequestConstraints.getMulticastMaxPacketSize(
          DEFAULT_MAX_PACKET_SIZE),
      multicastRequestConstraints.getUnfulfilledConstraints()
        );
        while (ei.hasNext()) {
      try {
          packets.addAll(Arrays.asList(ei.next()));
      } catch (Exception e) {
          logger.log(
        (e instanceof UnsupportedConstraintException) ?
            Levels.HANDLED : Level.INFO,
        "exception encoding multicast request", e);
      }
        }
        return null;
    }
      }), securityContext.getAccessControlContext());

  if (packets.isEmpty()) {
      throw new DiscoveryProtocolException("no encoded requests");
  }
  return (DatagramPacket[]) packets.toArray(
      new DatagramPacket[packets.size()]);
    }

    /**
     * Performs unicast discovery over given socket based on protocol in use,
     * applying configured security constraints (if any).
     */
    private UnicastResponse
  doUnicastDiscovery(
      final Socket socket,
      final DiscoveryConstraints unicastDiscoveryConstraints,
      final Discovery disco)
  throws IOException, ClassNotFoundException
    {
  try {
      return (UnicastResponse) AccessController.doPrivileged(
    securityContext.wrap(new PrivilegedExceptionAction() {
        public Object run() throws Exception {
      return disco.doUnicastDiscovery(
          socket,
          unicastDiscoveryConstraints.
        getUnfulfilledConstraints(),
          null,
          null,
          null);
        }
    }), securityContext.getAccessControlContext());
  } catch (PrivilegedActionException e) {
      Throwable t = e.getCause();
      if (t instanceof IOException) {
    throw (IOException) t;
      } else if (t instanceof ClassNotFoundException) {
    throw (ClassNotFoundException) t;
      } else {
    throw new AssertionError(t);
      }
  }
    }
   
    private UnicastResponse doUnicastDiscovery(
      final Socket socket,
      final DiscoveryConstraints unicastDiscoveryConstraints)
  throws IOException, ClassNotFoundException
    {
  Discovery disco =
      getDiscovery(unicastDiscoveryConstraints.chooseProtocolVersion());
  return doUnicastDiscovery(socket, unicastDiscoveryConstraints, disco);
    }

    /**
     * Returns Discovery instance for the given version, or throws
     * DiscoveryProtocolException if the version is unsupported.
     */
    private Discovery getDiscovery(int version)
  throws DiscoveryProtocolException
    {
  switch (version) {
      case Discovery.PROTOCOL_VERSION_1:
    return Discovery.getProtocol1();
      case Discovery.PROTOCOL_VERSION_2:
    return protocol2;
      default:
    throw new DiscoveryProtocolException(
        "unsupported protocol version: " + version);
  }
    }
   
    /**
     * Holder class for the time and sequence number of the last
     * received announcement. The regInfo map contains instances of this
     * class as values.
     */
    private static class AnnouncementInfo {
  private long tStamp;
  private long seqNum;
  private AnnouncementInfo(long tStamp, long seqNum) {
      this.tStamp = tStamp;
      this.seqNum = seqNum;
  }
    }
}//end class LookupDiscovery
TOP

Related Classes of net.jini.discovery.LookupDiscovery$AnnouncementInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.