/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.discovery;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;

/** 
 * This class is a helper utility class that organizes and manages all
 * discovery-related activities on behalf of a client or service. Rather
 * than providing its own facility for coordinating and maintaining
 * all of the necessary state information related to group names,
 * {@link LookupLocator} objects, and {@link DiscoveryListener}
 * objects, clients and services can employ this class to provide those
 * facilities on their behalf.
 * <p>
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * The following implementation-specific items are discussed below:
 * <ul><li> <a href="#ldmConfigEntries">Configuring LookupDiscoveryManager</a>
 *     <li> <a href="#ldmLogging">Logging</a>
 * </ul>
 *
 * <a name="ldmConfigEntries">
 * <p>
 * <b><font size="+1">Configuring LookupDiscoveryManager</font></b>
 * <p>
 * </a>
 *
 * Currently, there are no configuration entries directly supported by this
 * implementation of <code>LookupDiscoveryManager</code>. All configuration
 * entries affecting the operation of this utility are retrieved by either
 * the {@link LookupDiscovery} utility, or the {@link LookupLocatorDiscovery}
 * utility. Please refer to the documentation provided with those utilities
 * when configuring the behavior of <code>LookupDiscoveryManager</code>.
 *
 * <a name="ldmLogging">
 * <p>
 * <b><font size="+1">Logging</font></b>
 * <p>
 * </a>
 *
 * Currently, there are no loggers directly supported by this implementation
 * of <code>LookupDiscoveryManager</code>.  All logging information produced
 * when using this utility is controlled by the loggers supported by the
 * following utilities:
 * <p>
 * <ul>
 *  <li> {@link LookupDiscovery} 
 *  <li> {@link LookupLocatorDiscovery} 
 * </ul>
 * <p>
 * For information about how to obtain logging information when using this
 * implementation of  <code>LookupDiscoveryManager</code>, please refer to
 * the documentation provided with the discovery utilities listed above.
 * <p>
 *
 * @author Sun Microsystems, Inc.
 *
 * @see net.jini.discovery.DiscoveryManagement
 * @see net.jini.discovery.DiscoveryGroupManagement
 * @see net.jini.discovery.DiscoveryLocatorManagement
 * 
 */
public class LookupDiscoveryManager implements DiscoveryManagement,
                                               DiscoveryGroupManagement,
                                               DiscoveryLocatorManagement
{
    /** Constant that indicates the discovery mechanism is group discovery */
    public static final int FROM_GROUP = 1;
    /** Constant that indicates the discovery mechanism is locator discovery */
    public static final int FROM_LOCATOR = 2;

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

    /** Contains instances of the <code>ProxyReg</code> wrapper class. This
     *  set acts as what is referred to as the "managed set of registrars"
     *  or simply, the "managed set". When a registrar is discovered, that
     *  registrar and its associated "discovered state" is wrapped in an
     *  instance of <code>ProxyReg</code>, which is then placed in this set,
     *  which is managed by this <code>LookupDiscoveryManager</code>. As
     *  registrars are discovered and discarded, and as the discovered state
     *  of those registrars is modified, the contents of this set are modified
     *  appropriately.
     *
     *  Note that this set is shared across threads; therefore, when
     *  accessing or modifying the contents of this set, the appropriate
     *  synchronization must be applied.
     */
    private final ArrayList discoveredSet = new ArrayList(1);
    /** Contains the instances of <code>DiscoveryListener</code> that clients
     *  register with the <code>LookupDiscoveryManager</code>. The elements
     *  of this set receive discovered events, discarded events and, when
     *  appropriate, changed events.
     */
    private final ArrayList listeners = new ArrayList(1);
    /** The <code>LookupDiscovery</code> utility used to manage the group
     *  discovery mechanism. Note that this object cannot be accessed outside
     *  of this <code>LookupDiscoveryManager</code>.
     */
    private LookupDiscovery lookupDisc;
    /** The listener that receives discovered, discarded, and changed events
     *  from the <code>LookupDiscovery</code> utility that manages group
     *  discovery on behalf of this <code>LookupDiscoveryManager</code>.
     */
    private DiscoveryListener groupListener = new GroupDiscoveryListener();
    /** The <code>LookupLocatorDiscovery</code> utility used to manage the
     *  locator discovery mechanism. Note that this object cannot be accessed
     *  outside of this <code>LookupDiscoveryManager</code>.
     */
    private LookupLocatorDiscovery locatorDisc;
    /** The listener that receives discovered and discarded events from the
     *  <code>LookupLocatorDiscovery</code> utility that manages locator
     *  discovery on behalf of this <code>LookupDiscoveryManager</code>.
     */
    private DiscoveryListener locatorListener = new LocatorDiscoveryListener();

    /** Wrapper class in which each instance corresponds to a lookup service
     *  that has been discovered via either group discovery, locator discovery,
     *  or both.
     *  
     *  Note that each element of the set of discovered registrars managed by
     *  this <code>LookupDiscoveryManager</code> (referred to as the "managed
     *  set of registrars" or simply, the "managed set"), is actually an 
     *  instance of this class.
     */
    private final class ProxyReg  {
        /** The discovered registrar to be managed */
	public ServiceRegistrar proxy;
        /** The groups to which the discovered registrar belongs */
        public String[] memberGroups;
        /** Special-purpose flag used in the discard process. This flag is
         *  only relevant to <code>LocatorDiscoveryListener.discarded</code>.
         *  This flag is set to <code>true</code> only when the method
         *  <code>LookupDiscoveryManager.discard</code> is called by the
         *  client. 
         *  
         *  A <code>true</code> value for this flag indicates to 
         *  <code>LocatorDiscoveryListener.discarded</code> that the registrar
         *  referenced by a given discarded event was discarded because the
         *  registrar was determined to be unreachable (a "communication
         *  discard"). A <code>false</code> value for this flag indicates to 
         *  <code>LocatorDiscoveryListener.discarded</code> that the registrar
         *  was discarded due to lost interest in the registrar's locator
         *  (a "no-interest discard"). 
         * 
         *  This flag is necessary because the locators of the discovered
         *  registrars are not retrieved and stored with the registrars and
         *  their member groups. The locators are not retrieved because to
         *  do so would require a remote call which is viewed as undesirable
         *  here since such a call cannot be guaranteed to complete in a timely
         *  fashion. Without the locator of the discarded registrar, the
         *  <code>LocatorDiscoveryListener.discarded</code> method cannot
         *  determine if the discarded event received from the 
         *  <code>LookupLocatorDiscovery</code> is a communication discard 
         *  or a no-interest discard. It is important for that method to
         *  be able to make such a determination because the action taken
         *  by that <code>discarded</code> method is dependent on that
         *  determination. For more information, refer to the API
         *  documentation (generated by the <code>javadoc</code> tool) of
         *  the <code>LocatorDiscoveryListener.discarded</code> method.
         *  
         *  Note that the mechanism that uses this flag assumes that
         *  neither the <code>LookupDiscovery</code> utility nor the 
         *  <code>LookupLocatorDiscovery</code> utility employed by this
         *  <code>LookupDiscoveryManager</code> are accessible outside of
         *  this <code>LookupDiscoveryManager</code>. If either were 
         *  accessible, then there would be no way to guarantee that a 
         *  value of <code>true</code> in this flag is equivalent to a
         *  communication discard.
         */
        public boolean commDiscard = false;
        /** Integer restricted to the values 0, 1, 2, and 3. Each value 
         *  represents a bit (or set of bits) that, when set, indicates the
         *  mechanism (group discovery, locator discovery, or both) through
         *  which the registrar referenced by the current instance of
         *  this class (proxy) has currently been (or not been) discovered.
         *  That is, if
         *  from = 0 (no bits set)  ==> discovered by neither group nor locator
         *       = 1 (bit 0 set)    ==> discovered by only group discovery
         *       = 2 (bit 1 set)    ==> discovered by only locator discovery
         *       = 3 (bits 1&2 set) ==> discovered by both group and locator
         */
	private int from;
        /** Indicates whether the registrar referenced by this class is
         *  currently in the process of being discarded. This flag is used to
         *  prevent access, or inadvertent modifications to, the discovered
         *  state of registrars that are in the process of being discarded,
         *  but which have not yet been removed from the managed set.
         */
	private boolean bDiscarded = false;
        /** Constructs an instance of this wrapper class.
         *
         *  @param proxy        reference to a registrar that has been newly
         *                      discovered or re-discovered
         *  @param memberGroups the groups to which the discovered registrar
         *                      belongs
         *  @param from         indicates the mechanism by which the registrar
         *                      was discovered (group or locator discovery).
         *                      The only values which are valid for this
         *                      parameter are FROM_GROUP or FROM_LOCATOR.
         */
        public ProxyReg(ServiceRegistrar proxy,
                        String[] memberGroups,
                        int from)
        {
            if(proxy == null) {
	        throw new IllegalArgumentException("proxy cannot be null");
            }//endif
            if( (from != FROM_GROUP) && (from != FROM_LOCATOR) ) {
	        throw new IllegalArgumentException
                                ("invalid discovery mechanism: must be either "
                                 +"FROM_GROUP or FROM_LOCATOR");
            }//endif
	    this.proxy        = proxy;
	    this.memberGroups = memberGroups;
	    this.from         = from;
	}//end constructor	    

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

	public int hashCode() {
	    return proxy.hashCode();
	}//end hashCode
	
        /** Sets the appropriate bit in the 'from' variable to indicate the
         *  mechanism or mechanisms through which the registrar referenced by
         *  this class was discovered (group discovery, locator discovery, or
         *  both). 
         *  
         *  This method is typically called during the discovery process; and
         *  the value input should always be either FROM_GROUP or FROM_LOCATOR.
         */
	public synchronized void addFrom(int from) {
	    this.from |= from;
	}//end addFrom

        /** Un-sets the appropriate bit in the 'from' variable to remove
         *  the indication that the registrar referenced by this class 
         *  was discovered via the mechanism identified by the input
         *  parameter. If, after un-setting the appropriate bit, the
         *  'from' variable is equal to 0 (indicating the registrar was
         *  previously discovered by neither group nor locator discovery),
         *  this method returns <code>true</code>; otherwise it returns
         *  <code>false</code>, which indicates that the registrar was 
         *  still discovered by the "opposite" mechanism from that identified
         *  by the input parameter.
         *  
         *  This method is typically called during the discard process; and
         *  the value input should always be either FROM_GROUP or FROM_LOCATOR.
         */
	public synchronized boolean removeFrom(int from) {
	    if( from == 0 ) {
	        throw new RuntimeException
                                   ("call to removeFrom with bad argument");
            }//endif
	    this.from &= ~from;
	    return (this.from == 0);
	}//end removeFrom

        /** Accessor method that returns the value of the 'from' variable.
         *  The value returned indicates the mechanism or mechanisms through
         *  which the registrar referenced by this class was previously
         *  discovered (group discovery, locator discovery, or both).
         *  
         *  @return <code>int</code> representing either group discovery (1),
         *          or locator discovery (2), or both (3).
         */
	public synchronized int getFrom() {
	    return this.from;
	}//end getFrom

        /** Discards the registrar referenced in this class from either the
         *  <code>LookupDiscovery</code> or <code>LookupLocatorDiscovery</code>
         *  utility employed by this <code>LookupDiscoveryManager</code>.
         * 
         *  The utility from which that registrar is discarded is dependent on
         *  whether the registrar was previously discovered via group or both
         *  group and locator discovery, or via locator discovery alone.
         *
         *  This method enables a mechanism for sequentially "chaining" the
         *  discard process of the <code>LookupDiscovery</code> utility with
         *  the discard process of the <code>LookupLocatorDiscovery</code>.
         *  That is, rather than discarding the registrar from both utilities
         *  at the same time, by invoking this method, the registrar will be
         *  discarded from only one of those utilities. Then when the discarded
         *  event is received by the listener registered with the utility from
         *  which the registrar was discarded, the listener - based on the
         *  current discovered state of the registrar - determines whether to: 
         *  discard the registrar from the other utility, send a discarded
         *  event out to the client's listener, or simply update state and
         *  do nothing more.
         *
         *  This chaining mechanism helps to present a single event
         *  source to the client listeners. That is, even though the
         *  two discovery utilities used internally by this 
         *  <code>LookupDiscoveryManager</code> operate independently,
         *  sending discarded events to the group discovery listener and/or
         *  the locator discovery listener, this chaining mechanism enables
         *  the coordination of those separate events so that only one 
         *  event is sent to the client listeners, even though multiple 
         *  events may have been received here.
         *
         *  Although this chaining mechanism is helpful in coordinating the
         *  discarded events received from the <code>LookupDiscovery</code>
         *  and <code>LookupLocatorDiscovery</code> utilities, the
         *  actual reason it must be used is due to the fact that 
         *  <code>LookupDiscovery</code> can send a certain type of
         *  discarded event - referred to as "passive communication
         *  discard"; whereas <code>LookupLocatorDiscovery</code> cannot.
         *  Recall that <code>LookupDiscovery</code> monitors the
         *  registrars it has discovered for reachability, whereas
         *  <code>LookupLocatorDiscovery</code> does not. When the
         *  <code>LookupDiscovery</code> sends a passive communication
         *  discard because it has determined that one of its registrars
         *  has become unreachable, if that registrar was also discovered
         *  via locator discovery, it is necessary to discard the registrar
         *  from the <code>LookupLocatorDiscovery</code> as well. In
         *  that case, this method is called to create a discard chain
         *  which ultimately will result in the registrar being discarded
         *  from the <code>LookupLocatorDiscovery</code>.
         */
	public void discard() {
	    synchronized(this) {
		bDiscarded = true;
	    }//end sync
            /* Give group discovery priority to avoid race condition. When
             * appropriate, locator discard will eventually occur as a result
             * of the first group discard.
             */
	    if((from & FROM_GROUP) != 0) {
                lookupDisc.discard(proxy);
	    } else if((from & FROM_LOCATOR) != 0) {
                locatorDisc.discard(proxy);
            }//endif
	}//end discard

        /** Accessor method that returns the value of the <code>boolean</code>
         *  variable <code>bDiscarded</code>.
         */
	public synchronized boolean isDiscarded() {
	    return bDiscarded;
	}

        /** Accessor method that returns the <code>String</code> array
         *  containing the names of the groups to which the registrar
         *  referenced in this class belongs.
         */
        public synchronized  String[] getMemberGroups() {
            return memberGroups;
        }//end getMemberGroups

        /** Modifier method that changes the set of member groups - stored in
         *  this class and associated with the registrar referenced in this
         *  class - to the given set of member groups.
         */
        public synchronized  void setMemberGroups(String[] newMemberGroups) {
            memberGroups = newMemberGroups;
        }//end setMemberGroups
    }//end class ProxyReg

    /** Class that defines the listener that is registered with the
     *  <code>LookupLocatorDiscovery</code> that performs locator discovery
     *  on behalf of this <code>LookupDiscoveryManager</code>.
     */
    class LocatorDiscoveryListener implements DiscoveryListener {
        /** Called by the <code>LookupLocatorDiscovery</code> to
         *  send a discovered event to this listener when that
         *  <code>LookupLocatorDiscovery</code> has discovered at least
         *  one registrar (reg) having a locator equal one of the locators
         *  desired by this <code>LookupDiscoveryManager</code>.
         *  The reg(s) referenced in the given discovered event may,
         *  or may not, have also been previously discovered through group
         *  discovery.
         *  
         *  If this <code>LookupDiscoveryManager</code> has no prior knowledge
         *  of a particular reg referenced in the given discovered event when
         *  the <code>LookupLocatorDiscovery</code> invokes this method, then
         *  this method will send to the listeners registered with this
         *  <code>LookupDiscoveryManager</code>, a discovered event
         *  referencing that reg; otherwise, it will simply update state
         *  and send no event at all.
         */
	public void discovered(DiscoveryEvent e) {
            ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
            Map groupsMap = e.getGroups();
            HashMap discoveredGroupsMap = new HashMap(proxys.length);
            HashMap changedGroupsMap    = new HashMap(proxys.length);
	    for(int i=0; i<proxys.length; i++) {
                synchronized(discoveredSet) {
                    ProxyReg reg = findReg(proxys[i]);
                    if(reg == null) {//newly discovered, send event
                        reg = new ProxyReg(proxys[i],
                                          (String[])(groupsMap.get(proxys[i])),
                                           FROM_LOCATOR);
                        addDiscoveredSet(reg);
                        discoveredGroupsMap.put(proxys[i],
                                                groupsMap.get(proxys[i]));
                    } else {//previously discovered, update bit, send no event
                        reg.addFrom(FROM_LOCATOR);
                    }//endif
                }//end sync(discoveredSet)
	    }//end loop
            /* Will send notification only if map is non-empty from above */
            notifyListener(discoveredGroupsMap, DISCOVERED);
	}//end discovered

        /** Called by <code>LookupLocatorDiscovery</code> to send a discarded
         *  event to this listener when one of the following events occurs,
         *  affecting at least one registrar (reg) that was previously
         *  discovered through either locator discovery alone, or through
         *  both group and locator discovery:
         *  <p><ul>  
         *   <li> The method <code>LookupLocatorDiscovery.discard</code>
         *        was called because the reg was determined to be unreachable
         *        (this is referred to as a "communication discard").
         *   <li> Either the method <code>setLocators</code> or the method
         *        <code>removeLocators</code> was called on the
         *        <code>LookupLocatorDiscovery</code>, and the resulting new
         *        set of desired locators no longer contains an element
         *        equal to the locator of the discarded reg (this is referred
         *        to as a "no-interest discard").
         *  </ul><p>
         *  Depending on the type of discarded event received from the
         *  <code>LookupLocatorDiscovery</code> as described above, and
         *  depending on whether the discarded reg was previously discovered
         *  by locator discovery alone, or by both group and locator discovery,
         *  this method will determine whether or not to pass a discarded
         *  event on to the client's listener. (Note that because the discarded
         *  event was sent by the <code>LookupLocatorDiscovery</code>,
         *  the discarded reg could not have been previously discovered by
         *  group discovery alone.)
         *
         *  The following describes the logic used by this method to determine
         *  whether or not to send a discarded event to the client listeners
         *  registered with this <code>LookupDiscoveryManager</code>. Note
         *  that since the <code>LookupLocatorDiscovery</code> is accessible
         *  only from within this <code>LookupDiscoveryManager</code>, the
         *  method <code>LookupLocatorDiscovery.discard</code> is never called
         *  from "outside" of this <code>LookupDiscoveryManager</code>.
         *  This fact is exploited to communicate to this method that a
         *  communication discard has occurred. That is, when
         *  <code>LookupDiscoveryManager.discard</code> is called, a boolean
         *  associated with the discarded reg is set to <code>true</code> to
         *  indicate that a communication discard has occurred.
         *
         *  Thus, for a particular reg referenced in the given discarded event,
         *  <p><ul>  
         *   <li> if the discarded event is a result of a call to 
         *        <code>LookupDiscoveryManager.discard</code>, then this
         *        method will conclude that the discarded event must be a 
         *        communication discard (indicated by the value of the
         *        boolean set by <code>LookupDiscoveryManager.discard</code>
         *        and associated with the discarded reg). When the discard is
         *        a communication discard, if the reg was previously
         *        discovered by only locator discovery, this method sends a
         *        discarded event. But if the reg was previously discovered
         *        by group discovery as well as locator discovery,
         *        then rather than sending a discarded event here,
         *        <code>LookupDiscovery.discard</code> is called (through
         *        a call to <code>reg.discard</code>), which will send a
         *        discarded event to <code>GroupDiscoveryListener</code>, which
         *        then ultimately sends a discarded event to the registered
         *        listeners of this <code>LookupDiscoveryManager</code>.
         *   <li> if the discarded event is NOT a result of a call to
         *        <code>LookupDiscoveryManager.discard</code> (is not a
         *        communication discard), then this method will conclude
         *        that the discarded event must be a no-interest discard.
         *        When the discard is a no-interest discard, if the discarded
         *        reg was previously discovered by ONLY locator discovery,
         *        then a discarded event is sent; otherwise, no event is sent
         *        (see section DU.2.5.1 of the "Jini(TM) Discovery Utilities
         *        Specification").
         *  </ul><p>
         *  The logic described above can be collapsed into the following
         *  decision process:
         *  <p><pre>
         *      if(discovered by locator discovery alone) {
         *          send discarded event
         *      } else {//discovered by both group and locator discovery
         *          if(communication discard) {
         *              call discard on LookupDiscovery
         *          }
         *      }
         *  </pre>
         */
	public void discarded(DiscoveryEvent e) {
	    ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
            Map groupsMap = e.getGroups();
            HashMap discardedGroupsMap = new HashMap(proxys.length);
	    for(int i=0; i<proxys.length; i++) {
                synchronized(discoveredSet) {
                    ProxyReg reg = findReg(proxys[i]);
                    if(reg != null) {
                        String[] newGroups = (String[])groupsMap.get
                                                                   (reg.proxy);
                        if( removeDiscoveredSet(reg,FROM_LOCATOR) ) {
                            /* Locator discovery only, always send discarded */
                            discardedGroupsMap.put(reg.proxy,newGroups);
                        } else {//group and loc discovery
                            if( reg.commDiscard ) {//unreachable, send later
                                reg.discard();//discard from LookupDiscovery
                            }//endif
                        }//endif
                    }//endif(reg != null)
                }//end sync(discoveredSet)
            }//end loop
            /* Will send notification only if map is non-empty from above */
            notifyListener(discardedGroupsMap, DISCARDED);
	}//end discarded

        /* Convenience method that adds the given <code>ProxyReg</code> 
         * instance to the managed set of discovered registrars.
         */
	void addDiscoveredSet(ProxyReg reg) {
	    synchronized(discoveredSet) {
		discoveredSet.add(reg);
	    }
	}//end addDiscoveredSet

        /* Convenience method that first attempts to unset the bit in the
         * discovery mechanism flag of the given ProxyReg based on the value
         * of the given discovery mechanism parameter (from). Depending on
         * the value of that parameter, the flag value that results will show
         * either no group discovery or no locator discovery (or neither
         * mechanism).
         *
         * After unsetting the appropriate bit, if the value of the flag
         * shows neither group discovery nor locator discovery, then the 
         * given ProxyReg is removed from the managed set of registrars, and
         * <code>true</code> is returned; otherwise <code>false</code> is
         * returned.
         */
	boolean removeDiscoveredSet(ProxyReg reg, int from) {
	    boolean bret = reg.removeFrom(from);
	    if(bret) {
		synchronized(discoveredSet) {
		    discoveredSet.remove(reg);
		}//end sync
	    }//endif
	    return bret;
	}//end removeDiscoveredSet
    }//end class LocatorDiscoveryListener

    /** Class that defines the listener that is registered with the
     *  <code>LookupDiscovery</code> that performs group discovery
     *  on behalf of this <code>LookupDiscoveryManager</code>.
     */
    private final class GroupDiscoveryListener
                                           extends LocatorDiscoveryListener
                                           implements DiscoveryChangeListener
    {
        /** Called by <code>LookupDiscovery</code> to send a discovered event
         *  to this listener when that <code>LookupDiscovery</code> has
         *  discovered at least one registrar (reg) belonging to at least one
         *  of the groups desired by this <code>LookupDiscoveryManager</code>.
         *  The reg(s) referenced in the given discovered event may,
         *  or may not, have also been previously discovered through locator
         *  discovery.
         *  
         *  If this <code>LookupDiscoveryManager</code> has no prior knowledge
         *  of a particular reg referenced in the given discovered event when
         *  the <code>LookupDiscovery</code> invokes this method, then this
         *  method will send to the listeners registered with this
         *  <code>LookupDiscoveryManager</code>, a discovered event
         *  referencing that reg; otherwise, depending on whether or not
         *  the set of member groups referenced in the given discovered
         *  event equal the member groups currently associated with the
         *  reg from that event, this method may send a changed event, or
         *  may simply update state and send no event at all.
         */
	public void discovered(DiscoveryEvent e) {
            ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
            Map groupsMap = e.getGroups();
            HashMap discoveredGroupsMap = new HashMap(proxys.length);
            HashMap changedGroupsMap    = new HashMap(proxys.length);
	    for(int i=0; i<proxys.length; i++) {
                synchronized(discoveredSet) {
                    ProxyReg reg = findReg(proxys[i]);
                    if(reg == null) {//newly discovered, send discovered event
                        reg = new ProxyReg(proxys[i],
                                          (String[])(groupsMap.get(proxys[i])),
                                           FROM_GROUP);
                        addDiscoveredSet(reg);
                        discoveredGroupsMap.put(proxys[i],
                                                groupsMap.get(proxys[i]));
                    } else {//previously discovered by group, by loc or by both
                        String[] oldGroups = reg.getMemberGroups();
                        String[] newGroups = (String[])groupsMap.get
                                                                  (reg.proxy);
                        if( groupSetsEqual(oldGroups,newGroups) ) {//by loc
                            reg.addFrom(FROM_GROUP);//send no event
                        } else {//by group or by both, send changed event
                            reg.setMemberGroups(newGroups);//update groups
                            changedGroupsMap.put(reg.proxy,newGroups);
                        }//endif
                    }//endif
                }//end sync(discoveredSet)
	    }//end loop
            /* Will send notification only if map is non-empty from above */
            notifyListener(discoveredGroupsMap, DISCOVERED);
            notifyListener(changedGroupsMap, CHANGED);
	}//end discovered

        /** Called by <code>LookupDiscovery</code> to send a discarded event
         *  to this listener when one of the following events occurs  
         *  affecting at least one registrar (reg) that was previously
         *  discovered through either group discovery alone, or through
         *  both group and locator discovery:
         *  <p><ul>  
         *   <li> The method <code>LookupDiscovery.discard</code> was
         *        called because the reg was determined to be unreachable.
         *        In this case, the discarded event received from the
         *        <code>LookupDiscovery</code> is referred to as an
         *        "active communication discard".
         *   <li> The <code>LookupDiscovery</code> has determined that the
         *        reg has stopped sending multicast announcements.
         *        In this case, the discarded event received from the
         *        <code>LookupDiscovery</code> is referred to as a
         *        "passive communication discard".
         *   <li> Either the method <code>setGroups</code> or the method
         *        <code>removeGroups</code> has been called on the
         *        <code>LookupDiscovery</code>, and the resulting new set
         *        of desired groups no longer intersects the set of member
         *        groups of the discarded reg. In this case, the discarded
         *        event received from the <code>LookupDiscovery</code> is
         *        referred to as an "active no-interest discard".
         *   <li> The member groups of the discarded reg have been changed
         *        in such a way that the set of member groups associated
         *        with the discarded reg no longer intersects the set of
         *        desired groups to discover. In this case, the discarded
         *        event received from <code>LookupDiscovery</code> is
         *        referred to as a "passive no-interest discard".
         *  </ul><p>
         *  Depending on the type of discarded event received from the
         *  <code>LookupDiscovery</code> as described above, and depending
         *  on whether the discarded reg was previously discovered by group
         *  discovery alone, or by both group and locator discovery, this
         *  method will determine whether to send a discarded event, a changed
         *  event, or no event at all. Note that because the discarded event
         *  was sent by the <code>LookupDiscovery</code>, the discarded reg
         *  could not have been previously discovered by locator discovery
         *  alone. Note also that even though a discarded event is received
         *  by this method, there are conditions in which a changed event
         *  is ultimately sent to the listeners registered with this
         *  <code>LookupDiscoveryManager</code> (see below).
         *
         *  The following describes the logic used by this method to determine
         *  whether or not to send an event to the registered listeners of
         *  this <code>LookupDiscoveryManager</code>, as well as what type
         *  of event to send. Note that this method concludes that the
         *  current discarded event must be a communication discard when the
         *  the set of desired groups still intersects the discarded reg's
         *  current set of member groups. This is because when interest in
         *  the reg still exists, <code>LookupDiscovery</code> will discard 
         *  the reg only when it has determined that the reg is unreachable.
         *  
         *  Thus, for a particular reg referenced in the given discarded event,
         *  <p><ul>  
         *   <li> if the member groups contained in the discarded event from
         *        the <code>LookupDiscovery</code> are identical to the
         *        previously discovered member groups of that reg, and if
         *        if those member groups still intersect the current set of
         *        desired groups (still interested in the reg), then this
         *        method will conclude that the discarded event must be either
         *        an active or a passive communication discard. When the 
         *        discard is a communication discard (either active or
         *        passive), if the reg was previously discovered by only group
         *        discovery, this method sends a discarded event. But if the
         *        reg was previously discovered by locator discovery as well
         *        as group discovery, then rather than sending a discarded
         *        event here, <code>LookupLocatorDiscovery.discard</code>
         *        is called (through a call to <code>reg.discard</code>),
         *        which will send a discarded event to
         *        <code>LocatorDiscoveryListener</code>, which then
         *        ultimately sends a discarded event to the registered
         *        listeners of this <code>LookupDiscoveryManager</code>.
         *   <li> if the member groups contained in the discarded event from
         *        the <code>LookupDiscovery</code> are identical to the
         *        previously discovered member groups of that reg, but those
         *        member groups NO LONGER intersect the current set of desired
         *        groups, then this method will conclude that the discarded
         *        event must be an active no-interest discard. When the 
         *        discard is an active no-interest discard, if the discarded
         *        reg was previously discovered by ONLY group discovery, then
         *        a discarded event is sent; otherwise, no event is sent
         *        (see section DU.2.4.1 of the "Jini(TM) Discovery Utilities
         *        Specification").
         *   <li> if the member groups contained in the discarded event from
         *        the <code>LookupDiscovery</code> neither intersect the
         *        current set of desired groups (indicating no interest), nor
         *        are they identical to the previously discovered member
         *        groups of that reg (the member groups have changed), this
         *        method will conclude that the discarded event must be a
         *        passive no-interest discard. When the discard is a passive
         *        no-interest discard, if the discarded reg was previously
         *        discovered by locator discovery as well as group discovery,
         *        a changed event is is sent to the appropriate listeners;
         *        otherwise a discarded event is sent. A changed event is
         *        sent when the reg was previously discovered by both group
         *        and locator discovery because the fact that the reg was
         *        discovered via locator discovery indicates continued
         *        interest in the reg, even though the reg was discarded with
         *        respect to group discovery. In this case, since the member
         *        groups have actually changed (changed in such a way that the
         *        <code>LookupDiscovery</code> sent a discarded event), and 
         *        since there is still interest in that reg, it is more
         *        appropriate to send a changed event here rather than a
         *        discarded event.
         *  </ul><p>
         *  The logic described above can be collapsed into the following
         *  decision process:
         *  <p><pre>
         *      if(discovered by group discovery alone) {
         *          send discarded event
         *      } else {//discovered by both group and locator discovery
         *          if(NO LONGER interested in member groups) {
         *              if(member group have changed) {
         *                  send changed event
         *              }
         *          } else {//still interested in member groups
         *              call discard on LookupLocatorDiscovery
         *          }
         *      }
         *  </pre>
         */
	public void discarded(DiscoveryEvent e) {
	    ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
            Map groupsMap = e.getGroups();
            HashMap discardedGroupsMap = new HashMap(proxys.length);
            HashMap changedGroupsMap   = new HashMap(proxys.length);
	    for(int i=0; i<proxys.length; i++) {
                synchronized(discoveredSet) {
                    ProxyReg reg = findReg(proxys[i]);
                    if(reg != null) {
                        String[] newGroups = (String[])groupsMap.get
                                                                   (reg.proxy);
                        if( removeDiscoveredSet(reg,FROM_GROUP) ) {
                            /* group discovery only, always send discarded */
                            discardedGroupsMap.put(reg.proxy,newGroups);
                        } else {//discovered by group and locator
                            String[] desiredGroups = lookupDisc.getGroups();
                            if( !stillInterested(newGroups,desiredGroups) ) {
                                String[] oldGroups = reg.getMemberGroups();
                                if( !groupSetsEqual(oldGroups,newGroups) ) {
                                    reg.setMemberGroups(newGroups);
                                    changedGroupsMap.put(reg.proxy,newGroups);
                                }//endif(!groupSetsEqual ==> groups changed)
                            } else {//stillInterested ==> communication discard
                                reg.discard();//from LookupLocatorDiscovery
                            }//endif(stillInterested or not)
                        }//endif(removeDiscoveredSet ==> group or group & loc)
                    }//endif(reg != null)
                }//end sync(discoveredSet)
            }//end loop
            /* Will send notification only if map is non-empty from above */
            notifyListener(discardedGroupsMap, DISCARDED);
            notifyListener(changedGroupsMap, CHANGED);
	}//end discarded

        /** Called by <code>LookupDiscovery</code> to send a changed event
         *  to this listener when that <code>LookupDiscovery</code> has 
         *  determined that the member groups of a previously discovered
         *  registrar (reg) have changed. This method extracts and records
         *  the appropriate information from the given changed event, and
         *  then sends a changed event to the appropriate listeners registered
         *  with this <code>LookupDiscoveryManager</code>.
         */
        public void changed(DiscoveryEvent e) {
            /* update the groups of each changed registrar */
            ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
            Map groupsMap = e.getGroups();
            HashMap changedGroupsMap = new HashMap(proxys.length);
	    for(int i=0; i<proxys.length; i++) {
                synchronized(discoveredSet) {
                    ProxyReg reg = findReg(proxys[i]);
                    if(reg != null) {//previously discovered
                        String[] newGroups = (String[])groupsMap.get
                                                                   (reg.proxy);
                        reg.setMemberGroups(newGroups);
                        changedGroupsMap.put(reg.proxy,newGroups);
                    }//endif
                }//end sync(discoveredSet)
            }//end loop
            /* Will send notification only if map is non-empty from above */
            notifyListener(changedGroupsMap, CHANGED);
        }//end changed
    }//end class GroupDiscoveryListener

    /** 
     * Constructs an instance of this class that will organize and manage 
     * all discovery-related activities on behalf of the client or service
     * that instantiates this class.
     * <p>
     * If <code>null</code> (<code>DiscoveryGroupManagement.ALL_GROUPS</code>)
     * is input to the <code>groups</code> parameter, then attempts will be
     * made via group discovery to discover all lookup services located within
     * range of the entity that constructs this class. If the empty array
     * (<code>DiscoveryGroupManagement.NO_GROUPS</code>) is input to that
     * parameter, no group discovery will be performed until the set of
     * groups to discover is populated.
     * <p>
     * If an empty array or a <code>null</code> reference is input to the
     * <code>locators</code> parameter, no locator discovery will be performed
     * until the set of locators to discover is populated.
     *
     * @param groups   <code>String</code> array, none of whose elements may
     *                 be <code>null</code>, consisting of the names of the
     *                 groups whose members are lookup services the client
     *                 or service wishes to discover.
     * @param locators array of instances of <code>LookupLocator</code>, none
     *                 of whose elements may be <code>null</code>, and in
     *                 which each element corresponds to a specific lookup
     *                 service the client or service wishes to discover via
     *                 locator discovery.
     * @param listener a reference to <code>DiscoveryListener</code> object
     *                 that will be notified when a targeted lookup service
     *                 is discovered or discarded.
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either one or more of the elements of the <code>groups</code>
     *         parameter is <code>null</code>, or one or more elements of
     *         the <code>locators</code> parameter is null.
     *
     * @see net.jini.core.discovery.LookupLocator
     * @see net.jini.discovery.DiscoveryListener
     */
    public LookupDiscoveryManager(String[] groups,  
				  LookupLocator[] locators,
				  DiscoveryListener listener)
                                                           throws IOException
    {
        try {
            beginDiscovery(groups, locators, listener, 
                           EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /** 
     * Constructs an instance of this class, using the given 
     * <code>Configuration</code>, that will organize and manage all
     * discovery-related activities on behalf of the client or service
     * that instantiates this class.
     * <p>
     * If <code>null</code> (<code>DiscoveryGroupManagement.ALL_GROUPS</code>)
     * is input to the <code>groups</code> parameter, then attempts will be
     * made via group discovery to discover all lookup services located within
     * range of the entity that constructs this class. If the empty array
     * (<code>DiscoveryGroupManagement.NO_GROUPS</code>) is input to that
     * parameter, no group discovery will be performed until the set of
     * groups to discover is populated.
     * <p>
     * If an empty array or a <code>null</code> reference is input to the
     * <code>locators</code> parameter, no locator discovery will be performed
     * until the set of locators to discover is populated.
     *
     * @param groups   <code>String</code> array, none of whose elements may
     *                 be <code>null</code>, consisting of the names of the
     *                 groups whose members are lookup services the client
     *                 or service wishes to discover.
     * @param locators array of instances of <code>LookupLocator</code>, none
     *                 of whose elements may be <code>null</code>, and in
     *                 which each element corresponds to a specific lookup
     *                 service the client or service wishes to discover via
     *                 locator discovery.
     * @param listener a reference to <code>DiscoveryListener</code> object
     *                 that will be notified when a targeted lookup service
     *                 is discovered or discarded.
     *
     * @param config   an instance of <code>Configuration</code>, used to
     *                 obtain the objects needed to configure the current
     *                 instance of this class
     *
     * @throws java.io.IOException because construction of this class may
     *         initiate the discovery process, which can throw an
     *         <code>IOException</code> when socket allocation occurs.
     *
     * @throws net.jini.config.ConfigurationException indicates an exception
     *         occurred while retrieving an item from the given
     *         <code>Configuration</code>
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either one or more of the elements of the <code>groups</code>
     *         parameter is <code>null</code>, or one or more elements of
     *         the <code>locators</code> parameter is <code>null</code>, or
     *         when <code>null</code> is input for the configuration.
     *
     * @see net.jini.core.discovery.LookupLocator
     * @see net.jini.discovery.DiscoveryListener
     * @see net.jini.config.Configuration
     */
    public LookupDiscoveryManager(String[] groups,  
				  LookupLocator[] locators,
				  DiscoveryListener listener,
                                  Configuration config)
                                    throws IOException, ConfigurationException
    {
        beginDiscovery(groups, locators, listener, config);
    }//end constructor

    /**
     * Returns an array consisting of the elements of the managed set
     * of locators; that is, instances of <code>LookupLocator</code> in
     * which each instance corresponds to a specific lookup service to
     * discover. The returned set will include both the set of 
     * <code>LookupLocator</code>s corresponding to lookup services 
     * that have already been discovered as well as the set of those
     * that have not yet been discovered. If the managed set of locators
     * is empty, this method will return the empty array. This method
     * returns a new array upon each invocation.
     *
     * @return <code>LookupLocator</code> array consisting of the elements
     *         of the managed set of locators
     *
     * @see net.jini.discovery.DiscoveryLocatorManagement#getLocators
     * @see #setLocators
     */
    public LookupLocator[] getLocators(){
	return locatorDisc.getLocators();
    }

    /**
     * Adds a set of locators to the managed set of locators. Elements in the
     * input set that duplicate (using the <code>LookupLocator.equals</code>
     * method) elements already in the managed set will be ignored. If the
     * empty array is input, the managed set of locators will not change.
     *
     * @param locators <code>LookupLocator</code> array consisting of the
     *                 locators to add to the managed set.
     * 
     * @throws java.lang.UnsupportedOperationException this exception 
     *         occurs when there is no managed set of locators to augment.
     *         That is, the current managed set of locators is
     *         <code>null</code>.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either <code>null</code> is input to the <code>locators</code>
     *         parameter, or one or more of the elements of the
     *         <code>locators</code> parameter is <code>null</code>.
     *
     * @see net.jini.discovery.DiscoveryLocatorManagement#addLocators
     * @see #removeLocators
     */
    public void addLocators(LookupLocator[] locators){
	locatorDisc.addLocators(locators);
    }

    /**
     * Deletes a set of locators from the managed set of locators, and discards
     * any already-discovered lookup service that corresponds to a deleted
     * locator. For any lookup service that is discarded as a result of an
     * invocation of this method, a discard notification is sent; and that
     * lookup service will not be eligible for re-discovery (assuming it is
     * not currently eligible for discovery through other means, such as
     * group discovery).
     * <p>
     * If the empty array is input, this method takes no action.
     *
     * @param locators <code>LookupLocator</code> array consisting of the
     *                 locators that will be removed from the managed set.
     * 
     * @throws java.lang.UnsupportedOperationException this exception 
     *         occurs when there is no managed set of locators from which
     *         remove elements.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         <code>null</code> is input to the <code>locators</code>
     *         parameter.
     *
     * @see net.jini.discovery.DiscoveryLocatorManagement#removeLocators
     * @see #addLocators
     */
    public void removeLocators(LookupLocator[] locators) {
	locatorDisc.removeLocators(locators);
    }

    /**
     * Replaces all of the locators in the managed set with locators from
     * a new set, and discards any already-discovered lookup service that
     * corresponds to a locator that is removed from the managed set
     * as a result of an invocation of this method. For any such lookup
     * service that is discarded, a discard notification is sent; and that
     * lookup service will not be eligible for re-discovery (assuming it is
     * not currently eligible for discovery through other means, such as
     * group discovery).
     * <p>
     * If the empty array is input, locator discovery will cease until this
     * method is invoked with an input parameter that is non-<code>null</code>
     * and non-empty.
     *
     * @param locators <code>LookupLocator</code> array consisting of the 
     *                 locators that will replace the current locators in the
     *                 managed set.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         <code>null</code> is input to the <code>locators</code>
     *         parameter.
     *
     * @see net.jini.discovery.DiscoveryLocatorManagement#setLocators
     * @see #getLocators
     */
    public void setLocators(LookupLocator[] locators) {
	locatorDisc.setLocators(locators);
    }
    
    /** 
     * Returns an array consisting of the elements of the managed set
     * of groups; that is, the names of the groups whose members are the
     * lookup services to discover. If the managed set of groups is empty,
     * this method will return the empty array. If there is no managed set
     * of groups, then null is returned; indicating that all groups are to
     * be discovered. This method returns a new array upon each invocation.
     *
     * @return <code>String</code> array consisting of the elements of the
     *         managed set of groups
     *
     * @see net.jini.discovery.DiscoveryGroupManagement#getGroups
     * @see #setGroups
     */
    public String[] getGroups() {
	return lookupDisc.getGroups();
    }

    /**   
     * Adds a set of group names to the managed set of groups. Elements in
     * the input set that duplicate elements already in the managed set
     * will be ignored. Once a new name is added to the managed set,
     * attempts will be made to discover all (as yet) undiscovered lookup
     * services that are members of the group having that name. If the empty
     * array (<code>DiscoveryGroupManagement.NO_GROUPS</code>) is input, the
     * managed set of groups will not change.
     *
     * Note that any entity that invokes this method must have
     * <code>DiscoveryPermission</code> on each of the groups in the
     * new set, otherwise a <code>SecurityException</code> will be
     * propagated through this method.
     *
     * @param groups <code>String</code> array consisting of the group names
     *               to add to the managed set.
     *
     * @throws java.io.IOException because an invocation of this method may
     *         result in the re-initiation of the discovery process, which can
     *         throw an <code>IOException</code> when socket allocation occurs.
     * 
     * @throws java.lang.UnsupportedOperationException this exception 
     *         occurs when there is no managed set of groups to augment.
     *         That is, the current managed set of groups is <code>null</code>.
     *         If the managed set of groups is <code>null</code>, all groups
     *         are being discovered; thus, requesting that a set of groups be
     *         added to the set of all groups makes no sense.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         either <code>null</code> is input to the <code>groups</code>
     *         parameter, or one or more of the elements of the
     *         <code>groups</code> parameter is <code>null</code>. If a
     *         <code>null</code> is input, then the entity is effectively
     *         requesting that "all groups" be added to the current managed
     *         set of groups; which is not allowed. (Note that if the entity
     *         wishes to change the managed set of groups from a finite set
     *         of names to "all groups", the <code>setGroups</code> method
     *         should be invoked with <code>null</code> input.)
     *
     * @see net.jini.discovery.DiscoveryGroupManagement#addGroups
     * @see #removeGroups
     */
    public void addGroups(String[] groups) throws IOException {
	lookupDisc.addGroups(groups);
    }

    /**   
     * Deletes a set of group names from the managed set of groups. If the
     * empty array (<code>DiscoveryGroupManagement.NO_GROUPS</code>) is input,
     * this method takes no action.
     *
     * @param groups <code>String</code> array consisting of the group names
     *               that will be removed from the managed set.
     *
     * @throws java.lang.UnsupportedOperationException this exception 
     *         occurs when there is no managed set of groups from which to
     *         remove elements.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         <code>null</code> is input to the <code>groups</code>
     *         parameter.
     *
     * @see net.jini.discovery.DiscoveryGroupManagement#removeGroups
     * @see #addGroups
     */
    public void removeGroups(String[] groups) {
        if (groups == null) {
            throw new NullPointerException
                                ("can't remove null from groups to discover");
        }
        String[] curGroupsToDiscover = lookupDisc.getGroups();
        if (curGroupsToDiscover == null) {
            throw new UnsupportedOperationException
                                        ("can't remove from \"any groups\"");
        }
        if (curGroupsToDiscover.length == 0) return;

        HashSet curGroups = new HashSet(curGroupsToDiscover.length);
        for(int i=0;i<curGroupsToDiscover.length;i++) {
            curGroups.add(curGroupsToDiscover[i]);
        }
        boolean removed = false;
        for(int i=0;i<groups.length;i++) {
            removed |= curGroups.remove(groups[i]);
        }
        if(!removed) return; // nothing removed

        String[] newGroupsToDiscover = (String[])(curGroups).toArray
                                                (new String[curGroups.size()]);
	lookupDisc.removeGroups(groups); //generates discards that get ignored
    }

    /**   
     * Replaces all of the group names in the managed set with names from
     * a new set. Once a new group name has been placed in the managed
     * set, if there are lookup services belonging to that group that have
     * already been discovered, no event will be sent to the entity's
     * listener for those particular lookup services. Attempts to discover
     * all (as yet) undiscovered lookup services belonging to that group
     * will continue to be made.
     * <p>
     * If null (<code>DiscoveryGroupManagement.ALL_GROUPS</code>) is input
     * to this method, then attempts will be made to discover all (as yet)
     * undiscovered lookup services that are within range, and which
     * are members of any group. If the empty array
     * (<code>DiscoveryGroupManagement.NO_GROUPS</code>) is input, then
     * group discovery will cease until this method is invoked with an
     * input parameter that is non-<code>null</code> and non-empty.
     *
     * Note that any entity that invokes this method must have
     * <code>DiscoveryPermission</code> on each of the groups in the
     * new set, otherwise a <code>SecurityException</code> will be
     * propagated through this method.
     *
     * @param groups <code>String</code> array consisting of the group
     *               names that will replace the current names in the
     *               managed set.
     *
     * @throws java.io.IOException because an invocation of this method may
     *         result in the re-initiation of the discovery process, which can
     *         throw an <code>IOException</code> when socket allocation occurs.
     *
     * @see net.jini.discovery.DiscoveryGroupManagement#setGroups
     * @see #getGroups
     */
    public void setGroups(String[] groups) throws IOException {
        lookupDisc.setGroups(groups); //generates discards that get ignored
    }

    /**
     * Adds an instance of <code>DiscoveryListener</code> to the set of
     * objects listening for discovery events. Once the listener is
     * registered, it will be notified of all lookup services discovered
     * to date, and will then be notified as new lookup services are
     * discovered or existing lookup services are discarded.
     * <p>
     * If <code>null</code> is input, this method takes no action. If the
     * listener input to this method duplicates (using the <code>equals</code>
     * method) another element in the current set of listeners, no action
     * is taken.
     *
     * @param listener an instance of <code>DiscoveryListener</code>
     *                 corresponding to the listener to add to the set of
     *                 listeners.
     *
     * @throws java.lang.NullPointerException this exception occurs when
     *         <code>null</code> is input to the  <code>listener</code>
     *          parameter.
     *
     * @see net.jini.discovery.DiscoveryListener
     * @see net.jini.discovery.DiscoveryManagement#addDiscoveryListener
     * @see #removeDiscoveryListener
     */
    public void addDiscoveryListener(DiscoveryListener listener) {
        if(listener == null) {
            throw new NullPointerException("can't add null listener");
        }
        HashMap groupsMap = new HashMap(discoveredSet.size());
	synchronized(listeners) {
            if(!listeners.contains(listener)) {
                listeners.add(listener);
            }
            if(discoveredSet.isEmpty()) return;
	    for(int i=0; i< discoveredSet.size(); i++) {
                ProxyReg reg = (ProxyReg)discoveredSet.get(i);
                groupsMap.put(reg.proxy,reg.getMemberGroups());
	    }
	}
        notifyListener(listener, groupsMap, DISCOVERED);
    }

    /**
     * Removes a listener from the set of objects listening for discovery
     * events. If the listener object input to this method does not exist
     * in the set of listeners, then this method will take no action.
     *
     * @param listener an instance of <code>DiscoveryListener</code>
     *                 corresponding to the listener to remove from the set
     *                 of listeners.
     *
     * @see net.jini.discovery.DiscoveryListener
     * @see net.jini.discovery.DiscoveryManagement#removeDiscoveryListener
     * @see #addDiscoveryListener
     */
    public void removeDiscoveryListener(DiscoveryListener listener) {
	synchronized(listeners) {
	    listeners.remove(listener);
	}
    }

    /**
     * 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
     *
     * @see net.jini.core.lookup.ServiceRegistrar
     * @see net.jini.discovery.DiscoveryManagement#removeDiscoveryListener
     */
    public ServiceRegistrar[] getRegistrars() {
	ArrayList proxySet = new ArrayList(1);;
	synchronized(discoveredSet) {
	    int k = 0;
	    Iterator iter = discoveredSet.iterator();
	    while(iter.hasNext()) {
		ProxyReg reg = (ProxyReg)iter.next();
		if(!reg.isDiscarded())
		    proxySet.add(reg.proxy);
	    }
	}
	ServiceRegistrar[] ret = new ServiceRegistrar[proxySet.size()];
	proxySet.toArray(ret);
	return ret;
    }

    /**
     * Removes an instance of <code>ServiceRegistrar</code> from the
     * managed set of lookup services, making the corresponding lookup
     * service eligible for re-discovery. This method takes no action if
     * the parameter input to this method is <code>null</code>, or if it
     * does not match (using <code>equals</code>) any of the elements in
     * the managed set.
     *
     * @param proxy the instance of <code>ServiceRegistrar</code> to discard
     *              from the managed set of lookup services
     *
     * @see net.jini.core.lookup.ServiceRegistrar
     * @see net.jini.discovery.DiscoveryManagement#discard
     */
    public void discard(ServiceRegistrar proxy) {
        if(proxy == null) return;
	ProxyReg reg = findReg(proxy);
	if(reg != null) {
            synchronized(discoveredSet) {
	        reg.discard();
                reg.commDiscard = true;
            }//end sync(discoveredSet)
        }//endif
    }//end discard

    /**
     * Terminates all threads, ending all discovery processing being
     * performed by the current instance of this class.
     * <p>
     * After this method has been invoked, no new lookup services will
     * be discovered, and the effect of any new operations performed
     * on the current instance of this class are undefined.
     *
     * @see net.jini.discovery.DiscoveryManagement#terminate
     */
    public void terminate() {
	synchronized(listeners) {
	    listeners.clear();
	}
	lookupDisc.terminate();
	locatorDisc.terminate();
    }

    /** Return where the proxy come from. 
     * @param proxy  a ServiceRegistrar object
     * @return an <code>int</code> indicating whether the proxy   
     *         was obtained through group or locator discovery. 
     */
    public int getFrom(ServiceRegistrar proxy) {
	ProxyReg reg = findReg(proxy);
	if(reg != null)
	    return reg.getFrom();
	return 0;
    }

    private ProxyReg findReg(ServiceRegistrar proxy) {
	synchronized(discoveredSet) {
	    Iterator iter = discoveredSet.iterator();
	    while(iter.hasNext()) {
		ProxyReg reg =(ProxyReg)iter.next();
		if(reg.proxy.equals(proxy))
		    return reg;
	    }
	}
	return null;
    }

    private void notifyListener(Map groupsMap, int eventType) {
	if(groupsMap.isEmpty()) return;
	ArrayList notifies;
	synchronized(listeners) {
	    notifies = (ArrayList)listeners.clone();
	}
	Iterator iter = notifies.iterator();
	while(iter.hasNext()) {
	    DiscoveryListener l = (DiscoveryListener)iter.next();
            notifyListener(l, groupsMap, eventType);
	}
    }//end notifyListener
    
    private void notifyListener(DiscoveryListener l,
                                Map groupsMap,
                                int eventType)
    {
        /* always send discovered and discarded events, not always changed */
        if((eventType == CHANGED) && !(l instanceof DiscoveryChangeListener)){
            return;
        }
        DiscoveryEvent evt = new DiscoveryEvent(LookupDiscoveryManager.this,
                                                deepCopy((HashMap)groupsMap) );
        switch(eventType) {
            case DISCOVERED:
                l.discovered(evt);
                break;
            case DISCARDED:
                l.discarded(evt);
                break;
            case CHANGED:
                ((DiscoveryChangeListener)l).changed(evt);
                break;
        }//end switch
    }//end notifyListener

    /** 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;
        /* 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;
                }
            }
            return false;
        }
        return true;
    }//end groupSetsEqual

    /** Determines if at least one member group of a given registrar is
     *  contained in the given set of desired groups.
     *
     * @param regGroups     <code>String</code> array containing the member
     *                      groups of a given registrar (will never be null)
     * @param desiredGroups <code>String</code> array containing the groups
     *                      to discover (can be null - ALL_GROUPS)
     * 
     *  @return <code>true</code> if at least one element of regGroups is
     *          contained in desiredGroups; <code>false</code> otherwise
     */
    private boolean stillInterested(String[] regGroups,String[] desiredGroups){
        if(desiredGroups == DiscoveryGroupManagement.ALL_GROUPS) return true;
        if(desiredGroups.length == 0) return false;
	for(int i=0;i<regGroups.length;i++) {
	    for (int j=0;j<desiredGroups.length;j++) {
                if(regGroups[i].equals(desiredGroups[j])) return true;
            }
        }
	return false;
    }//end stillInterested

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

    /**
     * Using the given <code>Configuration</code>, initializes the current
     * instance of this utility, and initiates the discovery process for
     * the given set of groups and the given set of locators. Whenever a
     * lookup service is discovered, discarded, or changed, the appropriate
     * notification will be sent to the given listener.
     *
     * @param groups the set of group names to discover
     *
     * @param locators the set of locators to discover
     *
     * @param listener the listener that will be notified when a targeted
     *                 lookup service is discovered, discarded, or changed.
     *
     * @param config   an instance of <code>Configuration</code>, used to
     *                 obtain the objects needed to configure this utility
     *
     * @throws java.lang.NullPointerException either input array contains at
     *         least one <code>null</code> element
     *
     * @throws java.io.IOException an exception occurred initiating discovery
     *
     * @throws net.jini.config.ConfigurationException indicates an exception
     *         occurred while retrieving an item from the given
     *         <code>Configuration</code>
     */
    private void beginDiscovery(String[] groups,  
                                LookupLocator[] locators,
                                DiscoveryListener listener,
                                Configuration config)
                                    throws IOException, ConfigurationException
    {
        /* Initiate the discovery process */
        if(listener != null) listeners.add(listener);
        /* Configure for group discovery */
 	lookupDisc = new LookupDiscovery(groups, config);
	lookupDisc.addDiscoveryListener(groupListener);
        /* Configure for locator discovery */
	locatorDisc = new LookupLocatorDiscovery(locators, config);
	locatorDisc.addDiscoveryListener(locatorListener);
    }//end beginDiscovery

}//end class LookupDiscoveryManager
