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