Package com.sun.jini.reggie

Source Code of com.sun.jini.reggie.RegistrarImpl$SnapshotThread

/*
* 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 com.sun.jini.reggie;

import com.sun.jini.config.Config;
import com.sun.jini.constants.ThrowableConstants;
import com.sun.jini.constants.TimeConstants;
import com.sun.jini.constants.VersionConstants;
import com.sun.jini.discovery.ClientSubjectChecker;
import com.sun.jini.discovery.Discovery;
import com.sun.jini.discovery.DiscoveryConstraints;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.EncodeIterator;
import com.sun.jini.discovery.MulticastAnnouncement;
import com.sun.jini.discovery.MulticastRequest;
import com.sun.jini.discovery.UnicastResponse;
import com.sun.jini.logging.Levels;
import com.sun.jini.lookup.entry.BasicServiceType;
import com.sun.jini.proxy.MarshalledWrapper;
import com.sun.jini.reliableLog.LogHandler;
import com.sun.jini.reliableLog.ReliableLog;
import com.sun.jini.start.LifeCycle;
import com.sun.jini.thread.InterruptedStatusThread;
import com.sun.jini.thread.ReadersWriter;
import com.sun.jini.thread.ReadyState;
import com.sun.jini.thread.ReadersWriter.ConcurrentLockException;
import com.sun.jini.thread.TaskManager;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.rmi.MarshalledObject;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationSystem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import net.jini.activation.ActivationExporter;
import net.jini.activation.ActivationGroup;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.config.NoSuchEntryException;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.core.lease.UnknownLeaseException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.discovery.Constants;
import net.jini.discovery.ConstrainableLookupLocator;
import net.jini.discovery.DiscoveryGroupManagement;
import net.jini.discovery.DiscoveryLocatorManagement;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.id.ReferentUuid;
import net.jini.id.Uuid;
import net.jini.id.UuidFactory;
import net.jini.io.MarshalledInstance;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lookup.JoinManager;
import net.jini.lookup.entry.ServiceInfo;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;

/**
* Base server-side implementation of a lookup service, subclassed by
* TransientRegistrarImpl and PersistentRegistrarImpl.  Multiple client-side
* proxy classes are used, for the ServiceRegistrar interface as well as for
* leases and administration; their methods transform the parameters and then
* make corresponding calls on the Registrar interface implemented on the
* server side.
*
* @author Sun Microsystems, Inc.
*
*/
class RegistrarImpl implements Registrar, ProxyAccessor, ServerProxyTrust {

    /** Maximum minMax lease duration for both services and events */
    private static final long MAX_LEASE = 1000L * 60 * 60 * 24 * 365 * 1000;
    /** Maximum minimum renewal interval */
    private static final long MAX_RENEW = 1000L * 60 * 60 * 24 * 365;
    /** Default maximum size of multicast packets to send and receive */
    private static final int DEFAULT_MAX_PACKET_SIZE = 512;
    /** Default time to live value to use for sending multicast packets */
    private static final int DEFAULT_MULTICAST_TTL = 15;
    /** Default timeout to set on sockets used for unicast discovery */
    private static final int DEFAULT_SOCKET_TIMEOUT = 1*60*1000;
    /** Log format version */
    private static final int LOG_VERSION = 3;
    /** Logger and configuration component name */
    private static final String COMPONENT = "com.sun.jini.reggie";
    /** Lease ID always assigned to self */
    private static final Uuid myLeaseID = UuidFactory.create(0L, 0L);
    /** Logger used by this service */
    private static final Logger logger = Logger.getLogger(COMPONENT);

    /** Base set of initial attributes for self */
    private static final Entry[] baseAttrs = {
  new ServiceInfo(
      "Lookup", "Sun Microsystems, Inc.", "Sun Microsystems, Inc.",
      VersionConstants.SERVER_VERSION, "", ""),
  new BasicServiceType("Lookup")
    };
    /** Empty attribute set */
    private static final EntryRep[] emptyAttrs = {};

    /** Proxy for myself */
    private RegistrarProxy proxy;
    /** Exporter for myself */
    private Exporter serverExporter;
    /** Remote reference for myself */
    private Registrar myRef;
    /** Our service ID */
    private ServiceID myServiceID;
    /** Our activation id, or null if not activatable */
    private ActivationID activationID;
    /** Associated activation system, or null if not activatable */
    private ActivationSystem activationSystem;
    /** Our LookupLocator */
    private volatile LookupLocator myLocator;
    /** Our login context, for logging out */
    private LoginContext loginContext;
    /** Shutdown callback object, or null if no callback needed */
    private LifeCycle lifeCycle;

    /**
     * Map from ServiceID to SvcReg.  Every service is in this map under
     * its serviceID.
     */
    private final HashMap serviceByID = new HashMap();
    /**
     * Identity map from SvcReg to SvcReg, ordered by lease expiration.
     * Every service is in this map.
     */
    private final TreeMap serviceByTime = new TreeMap();
    /**
     * Map from String to HashMap mapping ServiceID to SvcReg.  Every service
     * is in this map under its types.
     */
    private final HashMap serviceByTypeName = new HashMap();
    /**
     * Map from EntryClass to HashMap[] where each HashMap is a map from
     * Object (field value) to ArrayList(SvcReg).  The HashMap array has as
     * many elements as the EntryClass has fields (including fields defined
     * by superclasses).  Services are in this map multiple times, once
     * for each field of each entry it has.  The outer map is indexed by the
     * first (highest) superclass that defines the field.  This means that a
     * HashMap[] has null elements for fields defined by superclasses, but
     * this is a small memory hit and is simpler than subtracting off base
     * index values when accessing the arrays.
     */
    private final HashMap serviceByAttr = new HashMap(23);
    /**
     * Map from EntryClass to ArrayList(SvcReg).  Services are in this map
     * multiple times, once for each no-fields entry it has (no fields meaning
     * none of the superclasses have fields either).  The map is indexed by
     * the exact type of the entry.
     */
    private final HashMap serviceByEmptyAttr = new HashMap(11);
    /** All EntryClasses with non-zero numInstances */
    private final ArrayList entryClasses = new ArrayList();
    /**
     * Map from Long(eventID) to EventReg.  Every event registration is in
     * this map under its eventID.
     */
    private final HashMap eventByID = new HashMap(11);
    /**
     * Identity map from EventReg to EventReg, ordered by lease expiration.
     * Every event registration is in this map.
     */
    private final TreeMap eventByTime = new TreeMap();
    /**
     * Map from ServiceID to EventReg or EventReg[].  An event
     * registration is in this map if its template matches on (at least)
     * a specific serviceID.
     */
    private final HashMap subEventByService = new HashMap(11);
    /**
     * Map from Long(eventID) to EventReg.  An event registration is in
     * this map if its template matches on ANY_SERVICE_ID.
     */
    private final HashMap subEventByID = new HashMap(11);

    /** Generator for resource (e.g., registration, lease) Uuids */
    private UuidGenerator resourceIdGenerator = new UuidGenerator();
    /** Generator for service IDs */
    private UuidGenerator serviceIdGenerator = resourceIdGenerator;
    /** Event ID */
    private long eventID = 0;
    /** Random number generator for use in lookup */
    private final Random random = new Random();

    /** Preparer for received remote event listeners */
    private ProxyPreparer listenerPreparer = new BasicProxyPreparer();
    /** Preparer for remote event listeners recovered from state log */
    private ProxyPreparer recoveredListenerPreparer = listenerPreparer;
    /** Preparer for received lookup locators */
    private ProxyPreparer locatorPreparer = listenerPreparer;
    /** Preparer for lookup locators recovered from state log */
    private ProxyPreparer recoveredLocatorPreparer = listenerPreparer;

    /** ArrayList of pending EventTasks */
    private final ArrayList newNotifies = new ArrayList();

    /** Current maximum service lease duration granted, in milliseconds. */
    private long maxServiceLease;
    /** Current maximum event lease duration granted, in milliseconds. */
    private long maxEventLease;
    /** Earliest expiration time of a SvcReg */
    private long minSvcExpiration = Long.MAX_VALUE;
    /** Earliest expiration time of an EventReg */
    private long minEventExpiration = Long.MAX_VALUE;

    /** Manager for discovering other lookup services */
    private DiscoveryManagement discoer;
    /** Manager for joining other lookup services */
    private JoinManager joiner;
    /** Task manager for sending events and discovery responses */
    private TaskManager tasker;
    /** Service lease expiration thread */
    private Thread serviceExpirer;
    /** Event lease expiration thread */
    private Thread eventExpirer;
    /** Unicast discovery request packet receiving thread */
    private UnicastThread unicaster;
    /** Multicast discovery request packet receiving thread */
    private Thread multicaster;
    /** Multicast discovery announcement sending thread */
    private Thread announcer;
    /** Snapshot-taking thread */
    private Thread snapshotter;

    /** Concurrent object to control read and write access */
    private final ReadersWriter concurrentObj = new ReadersWriter();
    /** Object for synchronizing with the service expire thread */
    private final Object serviceNotifier = new Object();
    /** Object for synchronizing with the event expire thread */
    private final Object eventNotifier = new Object();
    /** Object on which the snapshot-taking thread will synchronize */
    private final Object snapshotNotifier = new Object();

    /** Canonical ServiceType for java.lang.Object */
    private ServiceType objectServiceType;

    /** Log for recovering/storing state, or null if running as transient */
    private ReliableLog log;
    /** Flag indicating whether system is in a state of recovery */
    private boolean inRecovery;
    /** Flag indicating whether system state was recovered from a snapshot */
    private boolean recoveredSnapshot = false;
    /** Current number of records in the Log File since the last snapshot */
    private int logFileSize = 0;

    /** Log file must contain this many records before snapshot allowed */
    private int persistenceSnapshotThreshold = 200;
    /** Weight factor applied to snapshotSize when deciding to take snapshot */
    private float persistenceSnapshotWeight = 10;
    /** Minimum value for maxServiceLease. */
    private long minMaxServiceLease = 1000 * 60 * 5;
    /** Minimum value for maxEventLease. */
    private long minMaxEventLease = 1000 * 60 * 30;
    /** Minimum average time between lease renewals, in milliseconds. */
    private long minRenewalInterval = 100;
    /** Port for unicast discovery */
    private int unicastPort = 0;
    /** The groups we are a member of */
    private volatile String[] memberGroups = { "" };
    /** The groups we should join */
    private String[] lookupGroups = DiscoveryGroupManagement.NO_GROUPS;
    /** The locators of other lookups we should join */
    private LookupLocator[] lookupLocators = {};
    /** The attributes to use when joining (including with myself) */
    private Entry[] lookupAttrs;
    /** Interval to wait in between sending multicast announcements */
    private long multicastAnnouncementInterval = 1000 * 60 * 2;
    /** Multicast announcement sequence number */
    private volatile long announcementSeqNo = 0L;

    /** Network interfaces to use for multicast discovery */
    private NetworkInterface[] multicastInterfaces;
    /** Flag indicating whether network interfaces were explicitly specified */
    private boolean multicastInterfacesSpecified;
    /** Interval to wait in between retrying failed interfaces */
    private int multicastInterfaceRetryInterval = 1000 * 60 * 5;
    /** Utility for participating in version 2 of discovery protocols */
    private Discovery protocol2;
    /** Cached raw constraints associated with unicastDiscovery method*/
    private InvocationConstraints rawUnicastDiscoveryConstraints;
    /** Constraints specified for incoming multicast requests */
    private DiscoveryConstraints multicastRequestConstraints;
    /** Constraints specified for outgoing multicast announcements */
    private DiscoveryConstraints multicastAnnouncementConstraints;
    /** Constraints specified for handling unicast discovery */
    private DiscoveryConstraints unicastDiscoveryConstraints;
    /** Client subject checker to apply to incoming multicast requests */
    private ClientSubjectChecker multicastRequestSubjectChecker;
    /** Maximum time to wait for calls to finish before forcing unexport */
    private volatile long unexportTimeout = 1000 * 60 * 2;
    /** Time to wait between unexport attempts */
    private volatile long unexportWait = 1000;
    /** Client subject checker to apply to unicast discovery attempts */
    private ClientSubjectChecker unicastDiscoverySubjectChecker;

    /** Lock protecting startup and shutdown */
    private final ReadyState ready = new ReadyState();

    /**
     * Constructs RegistrarImpl based on a configuration obtained using the
     * provided string arguments.  If activationID is non-null, the created
     * RegistrarImpl runs as activatable; if persistent is true, it
     * persists/recovers its state to/from disk.  A RegistrarImpl instance
     * cannot be constructed as both activatable and non-persistent.  If
     * lifeCycle is non-null, its unregister method is invoked during shutdown.
     */
    RegistrarImpl(String[] configArgs,
      final ActivationID activationID,
      final boolean persistent,
      final LifeCycle lifeCycle)
  throws Exception
    {
  if (activationID != null && !persistent) {
      throw new IllegalArgumentException();
  }
  try {
      final Configuration config = ConfigurationProvider.getInstance(
    configArgs, getClass().getClassLoader());
      loginContext = (LoginContext) config.getEntry(
         COMPONENT, "loginContext", LoginContext.class, null);

      PrivilegedExceptionAction init = new PrivilegedExceptionAction() {
    public Object run() throws Exception {
        init(config, activationID, persistent, lifeCycle);
        return null;
    }
      };
      if (loginContext != null) {
    loginContext.login();
    try {
        Subject.doAsPrivileged(
      loginContext.getSubject(), init, null);
    } catch (PrivilegedActionException e) {
        throw e.getCause();
    }
      } else {
    init.run();
      }
  } catch (Throwable t) {
      logger.log(Level.SEVERE, "Reggie initialization failed", t);
      if (t instanceof Exception) {
    throw (Exception) t;
      } else {
    throw (Error) t;
      }
  }
    }

    /** A service item registration record. */
    private final static class SvcReg implements Comparable, Serializable {

  private static final long serialVersionUID = 2L;

  /**
   * The service item.
   *
   * @serial
   */
  public final Item item;
  /**
   * The lease id.
   *
   * @serial
   */
  public final Uuid leaseID;
  /**
   * The lease expiration time.
   *
   * @serial
   */
  public long leaseExpiration;

  /** Simple constructor */
  public SvcReg(Item item, Uuid leaseID, long leaseExpiration) {
      this.item = item;
      this.leaseID = leaseID;
      this.leaseExpiration = leaseExpiration;
  }

  /**
   * Primary sort by leaseExpiration, secondary by leaseID.  The
   * secondary sort is immaterial, except to ensure a total order
   * (required by TreeMap).
   */
  public int compareTo(Object obj) {
      SvcReg reg = (SvcReg)obj;
      if (this == reg)
    return 0;
      int i = compare(leaseExpiration, reg.leaseExpiration);
      if (i != 0) {
    return i;
      }
      i = compare(leaseID.getMostSignificantBits(),
      reg.leaseID.getMostSignificantBits());
      if (i != 0) {
    return i;
      }
      return compare(leaseID.getLeastSignificantBits(),
         reg.leaseID.getLeastSignificantBits());
  }

  /**
   * Compares long values, returning -1, 0, or 1 if l1 is less than,
   * equal to or greater than l2, respectively.
   */
  private static int compare(long l1, long l2) {
      return (l1 < l2) ? -1 : ((l1 > l2) ? 1 : 0);
  }
    }

    /** An event registration record. */
    private final static class EventReg implements Comparable, Serializable {

  private static final long serialVersionUID = 2L;

  /**
   * The event id.
   * @serial
   */
  public final long eventID;
  /**
   * The lease id.
   * @serial
   */
  public final Uuid leaseID;
  /**
   * The template to match.
   * @serial
   */
  public final Template tmpl;
  /**
   * The transitions.
   *
   * @serial
   */
  public final int transitions;
  /**
   * The current sequence number.
   *
   * @serial
   */
  public long seqNo;
  /**
   * The event listener.
   */
  public transient RemoteEventListener listener;
  /**
   * The handback object.
   *
   * @serial
   */
  public final MarshalledObject handback;
  /**
   * The lease expiration time.
   *
   * @serial
   */
  public long leaseExpiration;

  /** Simple constructor */
  public EventReg(long eventID, Uuid leaseID, Template tmpl,
      int transitions, RemoteEventListener listener,
      MarshalledObject handback, long leaseExpiration) {
      this.eventID = eventID;
      this.leaseID = leaseID;
      this.tmpl = tmpl;
      this.transitions = transitions;
      this.seqNo = 0;
      this.listener = listener;
      this.handback = handback;
      this.leaseExpiration = leaseExpiration;
  }

  /**
   * Primary sort by leaseExpiration, secondary by eventID.  The
   * secondary sort is immaterial, except to ensure a total order
   * (required by TreeMap).
   */
  public int compareTo(Object obj) {
      EventReg reg = (EventReg)obj;
      if (this == reg)
    return 0;
      if (leaseExpiration < reg.leaseExpiration ||
    (leaseExpiration == reg.leaseExpiration &&
     eventID < reg.eventID))
    return -1;
      return 1;
  }

  /**
   * Prepares listener (if non-null) using the given proxy preparer.  If
   * preparation fails, the listener field is set to null.
   */
  public void prepareListener(ProxyPreparer preparer) {
      if (listener != null) {
    try {
        listener =
      (RemoteEventListener) preparer.prepareProxy(listener);
    } catch (Exception e) {
        if (logger.isLoggable(Level.WARNING)) {
      logThrow(
          Level.WARNING,
          getClass().getName(),
          "prepareListener",
          "failed to prepare event listener {0}",
          new Object[]{ listener },
          e);
        }
        listener = null;
    }
      }
  }

  /**
   * @serialData RemoteEventListener as a MarshalledInstance
   */
  private void writeObject(ObjectOutputStream stream)
      throws IOException
  {
      stream.defaultWriteObject();
      stream.writeObject(new MarshalledInstance(listener));
  }

  /**
   * Unmarshals the event listener.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException
  {
      stream.defaultReadObject();
      MarshalledInstance mi = (MarshalledInstance) stream.readObject();
      try {
    listener = (RemoteEventListener) mi.get(false);
      } catch (Throwable e) {
    if (e instanceof Error &&
        ThrowableConstants.retryable(e) ==
      ThrowableConstants.BAD_OBJECT)
    {
        throw (Error) e;
    }
    logger.log(Level.WARNING,
         "failed to recover event listener", e);
      }
  }
    }

    /**
     * Interface defining the method(s) that must be implemented by each of
     * the concrete LogObj classes. This allows for the definition of
     * object-dependent invocations of the appropriate implementation of
     * the method(s) declared in this interface.
     */
    private static interface LogRecord extends Serializable {
  void apply(RegistrarImpl regImpl);
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a new service is registered.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class SvcRegisteredLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service registration.
   *
   * @serial
   */
  private SvcReg reg;

  /** Simple constructor */
  public SvcRegisteredLogObj(SvcReg reg) {
      this.reg = reg;
  }
 
  /**
   * Modifies the state of the Registrar by registering the service
         * stored in the reg object. Also needs to delete any existing
   * service with the same serviceID; this can happen if a service
   * re-registers while an existing registration is in effect, because
   * we don't log a separate lease cancellation record for the existing
   * registration in that case.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      SvcReg oldReg =
    (SvcReg)regImpl.serviceByID.get(reg.item.serviceID);
      if (oldReg != null)
    regImpl.deleteService(oldReg, 0);
      regImpl.addService(reg);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * new attributes are added to an existing service in the Registrar.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class AttrsAddedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service id.
   *
   * @serial
   */
  private ServiceID serviceID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;
  /**
   * The attributes added.
   *
   * @serial
   */
  private EntryRep[] attrSets;

  /** Simple constructor */
  public AttrsAddedLogObj(ServiceID serviceID,
        Uuid leaseID,
        EntryRep[] attrSets)
  {
      this.serviceID = serviceID;
      this.leaseID = leaseID;
      this.attrSets = attrSets;
  }
 
  /**
   * Modifies the state of the Registrar by adding to all of the
         * services matching the template, the attributes stored in
         * attributeSets.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.addAttributesDo(serviceID, leaseID, attrSets);
      } catch (UnknownLeaseException e) {
    /* this exception should never occur when recovering  */
    throw new AssertionError("an UnknownLeaseException should"
           + " never occur during recovery");
      }
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * existing attributes of an existing service in the Registrar are
     * modified.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class AttrsModifiedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service id.
   *
   * @serial
   */
  private ServiceID serviceID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;
  /**
   * The templates to match.
   * @serial
   */
  private EntryRep[] attrSetTmpls;
  /**
   * The new attributes.
   *
   * @serial
   */
  private EntryRep[] attrSets;

  /** Simple constructor */
  public AttrsModifiedLogObj(ServiceID serviceID,
           Uuid leaseID,
           EntryRep[] attrSetTmpls,
           EntryRep[] attrSets)
  {
      this.serviceID = serviceID;
      this.leaseID = leaseID;
      this.attrSetTmpls = attrSetTmpls;
      this.attrSets = attrSets;
  }

  /**
   * Modifies the state of the Registrar by modifying the attributes
         * of the services that match the template with the attributes
         * stored in attributeSets.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.modifyAttributesDo(serviceID, leaseID,
             attrSetTmpls, attrSets);
      } catch (UnknownLeaseException e) {
    /* this exception should never occur when recovering  */
    throw new AssertionError("an UnknownLeaseException should"
           + " never occur during recovery");
      }
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * new attributes are set on an existing service in the Registrar.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class AttrsSetLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service id.
   *
   * @serial
   */
  private ServiceID serviceID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;
  /**
   * The new attributes.
   *
   * @serial
   */
  private EntryRep[] attrSets;

  /** Simple constructor */
  public AttrsSetLogObj(ServiceID serviceID,
            Uuid leaseID,
            EntryRep[] attrSets)
  {
      this.serviceID = serviceID;
      this.leaseID = leaseID;
      this.attrSets = attrSets;
  }
 
  /**
   * Modifies the state of the Registrar by replacing the attributes
         * of the services matching the template with the attributes stored
         * in attributeSets.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.setAttributesDo(serviceID, leaseID, attrSets);
      } catch (UnknownLeaseException e) {
    /* this exception should never occur when recovering  */
      }
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a new event is registered.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class EventRegisteredLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The event registration.
   *
   * @serial
   */
  private EventReg eventReg;

  /** Simple constructor */
  public EventRegisteredLogObj(EventReg eventReg) {
      this.eventReg = eventReg;
  }
 
  /**
   * Modifies the state of the Registrar by registering the event
         * stored in the eventReg object; and by updating both the event
         * sequence number and the event ID.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      eventReg.prepareListener(regImpl.recoveredListenerPreparer);
      eventReg.seqNo += Integer.MAX_VALUE;
      regImpl.addEvent(eventReg);
      regImpl.eventID++;
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a lease on an existing service in the Registrar is cancelled.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class ServiceLeaseCancelledLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service id.
   *
   * @serial
   */
  private ServiceID serviceID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;

  /** Simple constructor */
  public ServiceLeaseCancelledLogObj(ServiceID serviceID, Uuid leaseID) {
      this.serviceID = serviceID;
      this.leaseID = leaseID;
  }
 
  /**
   * Modifies the state of the Registrar by cancelling the lease
         * having ID equal to the contents of the leaseID field; and
         * corresponding to the service with ID equal to the contents of
         * the serviceID field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.cancelServiceLeaseDo(serviceID, leaseID);
      } catch (UnknownLeaseException e) {
    /* this exception should never occur when recovering  */
      }
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a lease on an existing service in the Registrar is renewed.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class ServiceLeaseRenewedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service id.
   *
   * @serial
   */
  private ServiceID serviceID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;
  /**
   * The new lease expiration time.
   *
   * @serial
   */
  private long leaseExpTime;

  /** Simple constructor */
  public ServiceLeaseRenewedLogObj(ServiceID serviceID,
           Uuid leaseID,
           long leaseExpTime)
  {
      this.serviceID = serviceID;
      this.leaseID = leaseID;
      this.leaseExpTime = leaseExpTime;
  }
 
  /**
   * Modifies the state of the Registrar by renewing the lease
         * having ID equal to the contents of the leaseID field; and
         * corresponding to the service with ID equal to the contents
         * of the serviceID field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.renewServiceLeaseAbs(serviceID, leaseID, leaseExpTime);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a lease on a registered event is cancelled.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class EventLeaseCancelledLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The event id.
   *
   * @serial
   */
  private long eventID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;

  /** Simple constructor */
  public EventLeaseCancelledLogObj(long eventID, Uuid leaseID) {
      this.eventID = eventID;
      this.leaseID = leaseID;
  }
 
  /**
   * Modifies the state of the Registrar by cancelling the lease
         * corresponding to the event with ID equal to the contents of
         * the eventID field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.cancelEventLeaseDo(eventID, leaseID);
      } catch (UnknownLeaseException e) {
    /* this exception should never occur when recovering */
      }
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a lease on a registered event is renewed.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class EventLeaseRenewedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The event id.
   *
   * @serial
   */
  private long eventID;
  /**
   * The lease id.
   *
   * @serial
   */
  private Uuid leaseID;
  /**
   * The new lease expiration time.
   *
   * @serial
   */
  private long leaseExpTime;

  /** Simple constructor */
  public EventLeaseRenewedLogObj(long eventID,
               Uuid leaseID,
               long leaseExpTime)
  {
      this.eventID = eventID;
      this.leaseID = leaseID;
      this.leaseExpTime = leaseExpTime;
  }

  /**
   * Modifies the state of the Registrar by renewing the lease
         * corresponding to the event with ID equal to the contents of
         * the eventID field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.renewEventLeaseAbs(eventID, leaseID, leaseExpTime);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * a leases in the Registrar is renewed via a LeaseMap.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LeasesRenewedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service and event ids.
   *
   * @serial
   */
  private Object[] regIDs;
  /**
   * The lease ids.
   *
   * @serial
   */
  private Uuid[] leaseIDs;
  /**
   * The new lease expiration times.
   *
   * @serial
   */
  private long[] leaseExpTimes;

  /** Simple constructor */
  public LeasesRenewedLogObj(Object[] regIDs,
           Uuid[] leaseIDs,
           long[] leaseExpTimes)
  {
      this.regIDs = regIDs;
      this.leaseIDs = leaseIDs;
      this.leaseExpTimes = leaseExpTimes;
  }
 
  /**
   * Modifies the state of the Registrar by renewing the specified
         * leases.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.renewLeasesAbs(regIDs, leaseIDs, leaseExpTimes);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * lease are cancelled via a LeaseMap.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LeasesCancelledLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The service and event ids.
   *
   * @serial
   */
  private Object[] regIDs;
  /**
   * The lease ids.
   *
   * @serial
   */
  private Uuid[] leaseIDs;

  /** Simple constructor */
  public LeasesCancelledLogObj(Object[] regIDs, Uuid[] leaseIDs) {
      this.regIDs = regIDs;
      this.leaseIDs = leaseIDs;
  }
 
  /**
   * Modifies the state of the Registrar by cancelling the specified
         * leases.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      /* Exceptions can be returned, since we didn't weed out unknown
       * leases before logging, but we can just ignore them anyway.
       */
      regImpl.cancelLeasesDo(regIDs, leaseIDs);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the Unicast Port Number is set to a new value.
     * <p>
     * Note: the apply() method of this class merely sets the private field
     *       unicastPort. This means that during a recovery, the unicaster
     *       thread will be created with this new port number ONLY IF that
     *       thread is created AFTER recovery is complete. Thus, it is
     *       important that at re-initialization during a re-activation
     *       of the Registrar, the recovery() method is invoked before
     *       the unicaster thread is created.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class UnicastPortSetLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The new port number.
   *
   * @serial
   */
  private int newPort;

  /** Simple constructor */
  public UnicastPortSetLogObj(int newPort) {
      this.newPort = newPort;
  }

  /**
   * Modifies the state of the Registrar by setting the value of the
         * private unicastPort field to the value of the newPort field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.unicastPort = newPort;
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the set of groups to join is changed.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LookupGroupsChangedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The new groups to join.
   *
   * @serial
   */
  private String[] groups;

  /** Simple constructor */
  public LookupGroupsChangedLogObj(String[] groups) {
      this.groups = groups;
  }

  /**
   * Modifies the state of the Registrar by setting the private
         * field lookupGroups to the reference to the groups field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.lookupGroups = groups;
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the set of locators of lookup services to join is changed.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LookupLocatorsChangedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The new locators to join.
   */
  private transient LookupLocator[] locators;

  /** Simple constructor */
  public LookupLocatorsChangedLogObj(LookupLocator[] locators) {
      this.locators = locators;
  }

  /**
   * Modifies the state of the Registrar by setting the private
   * field lookupLocators to the reference to the locators field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      try {
    regImpl.lookupLocators = prepareLocators(
        locators, regImpl.recoveredLocatorPreparer, true);
      } catch (RemoteException e) {
    throw new AssertionError(e);
      }
  }

  /**
   * Writes locators as a null-terminated list of MarshalledInstances.
   */
  private void writeObject(ObjectOutputStream stream)
      throws IOException
  {
      stream.defaultWriteObject();
      marshalLocators(locators, stream);
  }

  /**
   * Reads in null-terminated list of MarshalledInstances, from which
   * locators are unmarshalled.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException
  {
      stream.defaultReadObject();
      locators = unmarshalLocators(stream);
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the memberGroups array is set to reference a new array of strings.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class MemberGroupsChangedLogObj implements LogRecord {

  private static final long serialVersionUID = 2L;

  /**
   * The new groups to be a member of.
   *
   * @serial
   */
  private String[] groups;

  /** Simple constructor */
  public MemberGroupsChangedLogObj(String[] groups) {
      this.groups = groups;
  }

  /**
   * Modifies the state of the Registrar by setting the private
         * memberGroups field to the reference to the groups field.
         *
         * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.memberGroups = groups;
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the attributes for the lookup service are changed.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LookupAttributesChangedLogObj implements LogRecord {

        private static final long serialVersionUID = 1L;

  /**
   * The new lookup service attributes.
   */
  private transient Entry[] attrs;

  /** Simple constructor */
  public LookupAttributesChangedLogObj(Entry[] attrs) {
      this.attrs = attrs;
  }

  /**
   * Modifies the state of the Registrar by setting the private
   * field lookupAttrs to the reference to the attrs field.
   *
   * @see RegistrarImpl.LocalLogHandler#applyUpdate
   */
  public void apply(RegistrarImpl regImpl) {
      regImpl.lookupAttrs = attrs;
  }

  /**
   * Writes attributes as a null-terminated list of MarshalledInstances.
   */
  private void writeObject(ObjectOutputStream stream)
      throws IOException
  {
      stream.defaultWriteObject();
      marshalAttributes(attrs, stream);
  }

  /**
   * Reads in null-terminated list of MarshalledInstances, from which
   * attributes are unmarshalled.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException
  {
      stream.defaultReadObject();
      attrs = unmarshalAttributes(stream);
  }
    }

    /**
     * Handler class for the persistent storage facility.
     * <p>
     * At any point during processing in a persistent Registrar instance, there
     * will exist both a 'snapshot' of the Registrar's state and a set of
     * records detailing each significant change that has occurred to the state
     * since the snapshot was taken. The snapshot information and the
     * incremental change information will be stored in separate files called,
     * respectively, the snapshot file and the log file. Together, these files
     * are used to recover the state of the Registrar when it is restarted or
     * reactivated (for example, after a crash or network outage).
     * <p>
     * This class contains the methods that are used to record and recover
     * the snapshot of the Registrar's state; as well as the method used to
     * apply the state changes that were recorded in the log file.
     * <p>
     * When the ReliableLog class is instantiated, a new instance of this
     * class is passed to its constructor so that the methods of this
     * class may be invoked by the methods defined in the ReliableLog.
     * Because this class extends the LogHandler class associated with
     * the ReliableLog class, this class must provide implementations of
     * the abstract methods declared in the LogHandler. Also, some of the
     * methods defined in this class override the methods of the LogHandler
     * in order to customize the handling of snapshot creation and
     * retrieval.
     * <p>
     * Each significant change to the persistent Registrar's state is written
     * to the log file as an individual record (when addLogRecord() is
     * invoked).  After the number of records logged exceeds a pre-defined
     * threshold, a snapshot of the state is recorded by invoking -- through
     * the ReliableLog and its LogHandler -- the snapshot() method defined in
     * this class. After the snapshot is taken, the log file is cleared and the
     * incremental log process starts over.
     * <p>
     * The contents of the snapshot file reflect the DATA contained in
     * the fields making up the current state of the Registrar. That data
     * represents many changes -- over time -- to the Registrar's state.
     * On the other hand, each record written to the log file is an object
     * that reflects both the data used and the ACTIONS taken to make one
     * change to the Registrar's state at a particular point in time.
     * <p>
     * The data written to the snapshot file is shown below:
     * <ul>
     * <li> our service ID
     * <li> current event ID
     * <li> current values of administrable parameters
     * <li> contents of the container holding the current registered services
     * <li> null (termination 'marker' for the set of registered services)
     * <li> contents of the container holding the current registered events
     * <li> null (termination 'marker' for the set of registered events)
     * </ul>
     * The type of state changes that will generate a new record in the log
     * file are:
     * <ul>
     * <li> a new service was registered
     * <li> a new event was registered
     * <li> new attributes were added to an existing service
     * <li> existing attributes of a service were modified
     * <li> a service lease was cancelled
     * <li> a service lease was renewed
     * <li> an event lease was cancelled
     * <li> an event lease was renewed
     * <li> an administrable parameter was changed
     * </ul>
     * During recovery, the state of the Registrar at the time of a crash
     * or outage is re-constructed by first retrieving the 'base' state from
     * the snapshot file; and then modifying that base state according to
     * the records retrieved from the log file. The reconstruction of the
     * base state is achieved by invoking the recover() method defined in
     * this class. The modifications recorded in the log file are then
     * applied to the base state by invoking the applyUpdate() method
     * defined in this class. Both recover() and applyUpdate() are invoked
     * through the ReliableLog and its associated LogHandler.
     * <p>
     * NOTE: The following lines must be added to the Registrar's policy file
     * <pre>
     *     permission java.io.FilePermission "dirname",   "read,write,delete";
     *     permission java.io.FilePermission "dirname/-", "read,write,delete";
     * </pre>
     *     where 'dirname' is the name of the directory path (relative or
     *     absolute) where the snapshot and log file will be maintained.
     */
    private class LocalLogHandler extends LogHandler {
 
  /** Simple constructor */
  public LocalLogHandler() { }

  /* Overrides snapshot() defined in ReliableLog's LogHandler class. */
  public void snapshot(OutputStream out) throws IOException {
      takeSnapshot(out);
  }

  /* Overrides recover() defined in ReliableLog's LogHandler class. */
  public void recover(InputStream in)
      throws IOException, ClassNotFoundException
  {
      recoverSnapshot(in);
  }

  /**
   * Required method implementing the abstract applyUpdate()
   * defined in ReliableLog's associated LogHandler class.
   * <p>
   * During state recovery, the recover() method defined in the
   * ReliableLog class is invoked. That method invokes the method
   * recoverUpdates() which invokes the method readUpdates(). Both
   * of those methods are defined in ReliableLog. The method
   * readUpdates() retrieves a record from the log file and then
   * invokes this method.
   * <p>
   * This method invokes the version of the method apply() that
   * corresponds to the particular type of 'log record' object
   * that is input as the first argument. The log record object and its
   * corresponding apply() method are defined in one of the so-called
   * LogObj classes. Any instance of one the LogObj classes is an
   * implementation of the LogRecord interface. The particular
   * implementation that is input to this method is dependent on the
   * type of record that was originally logged. The apply() method
   * will then modify the state of the Registrar in a way dictated
   * by the type of record that was retrieved.
   */
  public void applyUpdate(Object logRecObj) {
      ((LogRecord)logRecObj).apply(RegistrarImpl.this);
  }
    }

    /** Base class for iterating over all Items that match a Template. */
    private abstract class ItemIter {
  /** Current time */
  public final long now = System.currentTimeMillis();
  /** True means duplicate items are possible */
  public boolean dupsPossible = false;
  /** Template to match */
  protected final Template tmpl;
  /** Next item to return */
  protected SvcReg reg;

  /** Subclass constructors must initialize reg */
  protected ItemIter(Template tmpl) {
      this.tmpl = tmpl;
  }

  /** Returns true if the iteration has more elements. */
  public boolean hasNext() {
      return reg != null;
  }

  /** Returns the next element in the iteration as an Item. */
  public Item next() {
      if (reg == null)
    throw new NoSuchElementException();
      Item item = reg.item;
      step();
      return item;
  }

  /** Returns the next element in the iteration as a SvcReg. */
  public SvcReg nextReg() {
      if (reg == null)
    throw new NoSuchElementException();
      SvcReg cur = reg;
      step();
      return cur;
  }

  /** Set reg to the next matching element, or null if none */
  protected abstract void step();
    }

    /** Iterate over all Items. */
    private class AllItemIter extends ItemIter {
  /** Iterator over serviceByID */
  private final Iterator iter;

  /** Assumes the empty template */
  public AllItemIter() {
      super(null);
      iter = serviceByID.values().iterator();
      step();
  }

  /** Set reg to the next matching element, or null if none */
  protected void step() {
      while (iter.hasNext()) {
    reg = (SvcReg)iter.next();
    if (reg.leaseExpiration > now)
        return;
      }
      reg = null;
  }
    }

    /** Iterates over all services that match template's service types */
    private class SvcIterator extends ItemIter {
  /** Iterator for list of matching services. */
        private final Iterator services;
 
  /**
   * tmpl.serviceID == null and
   * tmpl.serviceTypes is not empty
   */
        public SvcIterator(Template tmpl) {
      super(tmpl);
      Map map = (Map) serviceByTypeName.get(
         tmpl.serviceTypes[0].getName());
      services = map != null ? map.values().iterator() :
    Collections.EMPTY_LIST.iterator();
      step();
  }
 
        /** Set reg to the next matching element, or null if none. */
        protected void step() {
      if (tmpl.serviceTypes.length > 1) {
    while (services.hasNext()) {
        reg = (SvcReg) services.next();
        if (reg.leaseExpiration > now &&
      matchType(tmpl.serviceTypes, reg.item.serviceType) &&
      matchAttributes(tmpl, reg.item))
      return;
    }   
      } else {
    while (services.hasNext()) {
        reg = (SvcReg) services.next();
        if (reg.leaseExpiration > now &&
      matchAttributes(tmpl, reg.item))
      return;
    }
      }
      reg = null;
  }
    }

    /** Iterate over all matching Items by attribute value. */
    private class AttrItemIter extends ItemIter {
  /** SvcRegs obtained from serviceByAttr for chosen attr */
  protected ArrayList svcs;
  /** Current index into svcs */
  protected int svcidx;

  /**
   * tmpl.serviceID == null and
   * tmpl.serviceTypes is empty and
   * tmpl.attributeSetTemplates[setidx].fields[fldidx] != null
   */
  public AttrItemIter(Template tmpl, int setidx, int fldidx) {
      super(tmpl);
      EntryRep set = tmpl.attributeSetTemplates[setidx];
      HashMap[] attrMaps =
    (HashMap[])serviceByAttr.get(getDefiningClass(set.eclass,
                    fldidx));
      if (attrMaps != null && attrMaps[fldidx] != null) {
    svcs = (ArrayList)attrMaps[fldidx].get(set.fields[fldidx]);
    if (svcs != null) {
        svcidx = svcs.size();
        step();
    }
      }
  }

  /** Simple constructor */
  protected AttrItemIter(Template tmpl) {
      super(tmpl);
  }

  /** Set reg to the next matching element, or null if none. */
  protected void step() {
      while (--svcidx >= 0) {
    reg = (SvcReg)svcs.get(svcidx);
    if (reg.leaseExpiration > now &&
        matchAttributes(tmpl, reg.item))
        return;
      }
      reg = null;
  }
    }

    /** Iterate over all matching Items by no-fields entry class. */
    private class EmptyAttrItemIter extends AttrItemIter {

  /**
   * tmpl.serviceID == null and
   * tmpl.serviceTypes is empty and
   * eclass has no fields
   */
  public EmptyAttrItemIter(Template tmpl, EntryClass eclass) {
      super(tmpl);
      svcs = (ArrayList)serviceByEmptyAttr.get(eclass);
      if (svcs != null) {
    svcidx = svcs.size();
    step();
      }
  }
    }

    /** Iterate over all matching Items by entry class, dups possible. */
    private class ClassItemIter extends ItemIter {
  /** Entry class to match on */
  private final EntryClass eclass;
  /** Current index into entryClasses */
  private int classidx;
  /** Values iterator for current HashMap */
  private Iterator iter;
  /** SvcRegs obtained from iter or serviceByEmptyAttr */
  private ArrayList svcs;
  /** Current index into svcs */
  private int svcidx = 0;

  /**
   * tmpl.serviceID == null and
   * tmpl.serviceTypes is empty and
   * tmpl.attributeSetTemplates is non-empty
   */
  public ClassItemIter(Template tmpl) {
      super(tmpl);
      dupsPossible = true;
      eclass = tmpl.attributeSetTemplates[0].eclass;
      classidx = entryClasses.size();
      step();
  }

  /** Set reg to the next matching element, or null if none */
  protected void step() {
      do {
    while (--svcidx >= 0) {
        reg = (SvcReg)svcs.get(svcidx);
        if (reg.leaseExpiration > now &&
      matchAttributes(tmpl, reg.item))
      return;
    }
      } while (stepValue());
      reg = null;
  }

  /**
   * Step to the next HashMap value, if any, reset svcs and svcidx,
   * and return false if everything exhausted.
   */
  private boolean stepValue() {
      while (true) {
    if (iter != null && iter.hasNext()) {
        svcs = (ArrayList)iter.next();
        svcidx = svcs.size();
        return true;
    }
    if (!stepClass())
        return false;
    if (iter == null)
        return true;
      }
  }

  /**
   * Step to the next matching entry class, if any, reset iter
   * using the HashMap for the last field of the class (and reset
   * (svcs and svcidx if the entry class has no fields), and
   * return false if everything exhausted.
   */
  private boolean stepClass() {
      while (--classidx >= 0) {
    EntryClass cand = (EntryClass)entryClasses.get(classidx);
    if (!eclass.isAssignableFrom(cand))
        continue;
    if (cand.getNumFields() > 0) {
        cand = getDefiningClass(cand, cand.getNumFields() - 1);
        HashMap[] attrMaps = (HashMap[])serviceByAttr.get(cand);
        iter = attrMaps[attrMaps.length - 1].values().iterator();
    } else {
        iter = null;
        svcs = (ArrayList)serviceByEmptyAttr.get(cand);
        svcidx = svcs.size();
    }
    return true;
      }
      return false;
  }
    }

    /** Iterate over a singleton matching Item by serviceID. */
    private class IDItemIter extends ItemIter {

  /** tmpl.serviceID != null */
  public IDItemIter(Template tmpl) {
      super(tmpl);
      reg = (SvcReg)serviceByID.get(tmpl.serviceID);
      if (reg != null &&
    (reg.leaseExpiration <= now || !matchItem(tmpl, reg.item)))
    reg = null;
  }

  /** Set reg to null */
  protected void step() {
      reg = null;
  }
    }

    /** An event to be sent, and the listener to send it to. */
    private final class EventTask implements TaskManager.Task {

  /** The event registration */
  public final EventReg reg;
  /** The sequence number of this event */
  public final long seqNo;
  /** The service id */
  public final ServiceID sid;
  /** The new state of the item, or null if deleted */
  public final Item item;
  /** The transition that fired */
  public final int transition;

  /** Simple constructor, except increments reg.seqNo. */
  public EventTask(EventReg reg,
       ServiceID sid,
       Item item,
       int transition)
  {
      this.reg = reg;
      seqNo = ++reg.seqNo;
      this.sid = sid;
      this.item = item;
      this.transition = transition;
  }

  /** Send the event */
  public void run() {
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "notifying listener {0} of event {1}",
        new Object[]{ reg.listener, new Long(reg.eventID) });
      }
      try {
    reg.listener.notify(new RegistrarEvent(proxy, reg.eventID,
                   seqNo, reg.handback,
                   sid, transition, item));
      } catch (Throwable e) {
    switch (ThrowableConstants.retryable(e)) {
    case ThrowableConstants.BAD_OBJECT:
        if (e instanceof Error) {
      logger.log(
          Levels.HANDLED, "exception sending event", e);
      throw (Error) e;
        }
    case ThrowableConstants.BAD_INVOCATION:
    case ThrowableConstants.UNCATEGORIZED:
        /* If the listener throws UnknownEvent or some other
         * definite exception, we can cancel the lease.
         */
        logger.log(Level.INFO, "exception sending event", e);
        try {
      cancelEventLease(reg.eventID, reg.leaseID);
        } catch (UnknownLeaseException ee) {
      logger.log(
          Levels.HANDLED,
          "exception canceling event lease",
          e);
        } catch (RemoteException ee) {
      logger.log(
          Levels.HANDLED,
          "The server has been shutdown",
          e);
        }
    }
      }
  }

  /** Keep events going to the same listener ordered. */
  public boolean runAfter(List tasks, int size) {
      for (int i = size; --i >= 0; ) {
    Object obj = tasks.get(i);
    if (obj instanceof EventTask &&
        reg.listener.equals(((EventTask)obj).reg.listener))
        return true;
      }
      return false;
  }
    }

    /** Task for decoding multicast request packets. */
    private final class DecodeRequestTask implements TaskManager.Task {

  /** The multicast packet to decode */
  private final DatagramPacket datagram;
  /** The decoder for parsing the packet */
  private final Discovery decoder;

  public DecodeRequestTask(DatagramPacket datagram, Discovery decoder) {
      this.datagram = datagram;
      this.decoder = decoder;
  }

  /**
   * Decodes this task's multicast request packet, spawning an
   * AddressTask if the packet satisfies the configured constraints,
   * matches this registrar's groups, and does not already contain this
   * registrar's service ID in its list of known registrars.  This method
   * assumes that the protocol version of the request has already been
   * checked.
   */
  public void run() {
      MulticastRequest req;
      try {
    req = decoder.decodeMulticastRequest(
        datagram,
        multicastRequestConstraints.getUnfulfilledConstraints(),
        multicastRequestSubjectChecker, true);
      } catch (Exception e) {
    if (!(e instanceof InterruptedIOException) &&
        logger.isLoggable(Levels.HANDLED))
    {
        logThrow(
      Levels.HANDLED,
      getClass().getName(),
      "run",
      "exception decoding multicast request from {0}:{1}",
      new Object[]{
          datagram.getAddress(),
          new Integer(datagram.getPort()) },
      e);
    }
    return;
      }
      String[] groups = req.getGroups();
      if ((groups.length == 0 || overlap(memberGroups, groups)) &&
    indexOf(req.getServiceIDs(), myServiceID) < 0)
      {
    try {
        req.checkConstraints();
    } catch (Exception e) {
        if (!(e instanceof InterruptedIOException) &&
      logger.isLoggable(Levels.HANDLED))
        {
      logThrow(
          Levels.HANDLED,
          getClass().getName(),
          "run",
          "exception decoding multicast request from {0}:{1}",
          new Object[]{
        datagram.getAddress(),
        new Integer(datagram.getPort()) },
          e);
        }
        return;
    }
    tasker.addIfNew(new AddressTask(req.getHost(), req.getPort()));
      }
  }

  /** No ordering */
  public boolean runAfter(List tasks, int size) {
      return false;
  }
    }

    /** Address for unicast discovery response. */
    private final class AddressTask implements TaskManager.Task {

  /** The address */
  public final String host;
  /** The port */
  public final int port;

  /** Simple constructor */
  public AddressTask(String host, int port) {
      this.host = host;
      this.port = port;
  }

  public int hashCode() {
      return host.hashCode();
  }

  /** Two tasks are equal if they have the same address and port */
  public boolean equals(Object obj) {
      if (!(obj instanceof AddressTask))
    return false;
      AddressTask ua = (AddressTask)obj;
      return host.equals(ua.host) && port == ua.port;
  }

  /** Connect and then process a unicast discovery request */
  public void run() {
      InetAddress[] addr = new InetAddress[]{};
      try {
    try {
        addr = InetAddress.getAllByName(host);
        if (addr == null)
      addr = new InetAddress[]{};
    } catch (UnknownHostException e) {
        if (logger.isLoggable(Level.INFO)) {
      logThrow(
          Level.INFO,
          getClass().getName(),
          "run",
          "failed to resolve host {0};"
          + " connection will still be attempted",
          new Object[]{ host },
          e);
        }
    }
          long deadline = DiscoveryConstraints.process(
              rawUnicastDiscoveryConstraints).getConnectionDeadline(
                  Long.MAX_VALUE);
    long now = System.currentTimeMillis();
    if (deadline <= now)
        throw new SocketTimeoutException("timeout expired before"
                 + " connection attempt");
    long timeLeft = deadline - now;
    int timeout = timeLeft >= Integer.MAX_VALUE ?
                  Integer.MAX_VALUE : (int) timeLeft;
    // attempt connection even if host name was not resolved
    if (addr.length == 0) {
        attemptResponse(
                        new InetSocketAddress(host, port), timeout);
        return;
    }
    for (int i = 0; i < addr.length; i++) {
        try {
      attemptResponse(
           new InetSocketAddress(addr[i], port), timeout);
      return;
        } catch (Exception e) {
      if (logger.isLoggable(Levels.HANDLED)) {
          logThrow(Levels.HANDLED, getClass().getName(),
             "run", "exception responding to {0}:{1}",
             new Object[] {addr[i], new Integer(port)}
             , e);
      }
        }
        timeLeft = deadline - System.currentTimeMillis();
        timeout = timeLeft >= Integer.MAX_VALUE ?
            Integer.MAX_VALUE : (int) timeLeft;
        if (timeLeft <= 0)
      throw new SocketTimeoutException("timeout expired"
                + " before successful response");
    }
      } catch (Exception e) {
    if (logger.isLoggable(Level.INFO)) {
        logThrow(
      Level.INFO,
      getClass().getName(),
      "run",
      "failed to respond to {0} on port {1}",
      new Object[]{Arrays.asList(addr), new Integer(port)},
      e);
    }
      }
  }

  /** No ordering */
  public boolean runAfter(List tasks, int size) {
      return false;
  }

        /** attempt a connection to multicast request client */
        private void attemptResponse(InetSocketAddress addr, int timeout)
            throws Exception
        {
            Socket s = new Socket();
            try {
                s.connect(addr, timeout);
                respond(s);
            } finally {
                try {
                    s.close();
                } catch (IOException e) {
                    logger.log(Levels.HANDLED, "exception closing socket", e);
                }
            }
        }
    }

    /** Socket for unicast discovery response. */
    private final class SocketTask implements TaskManager.Task {

  /** The socket */
  public final Socket socket;

  /** Simple constructor */
  public SocketTask(Socket socket) {
      this.socket = socket;
  }

  /** Process a unicast discovery request */
  public void run() {
      try {
    respond(socket);
      } catch (Exception e) {
          if (logger.isLoggable(Levels.HANDLED)) {
        logThrow(
      Levels.HANDLED,
      getClass().getName(),
      "run",
      "exception handling unicast discovery from {0}:{1}",
      new Object[] {
      socket.getInetAddress(),
      new Integer(socket.getPort())}
      ,
      e);
    }
      }
  }

  /** No ordering */
  public boolean runAfter(List tasks, int size) {
      return false;
  }
    }

    /** Service lease expiration thread code */
    private class ServiceExpireThread extends InterruptedStatusThread {

  /** Create a daemon thread */
  public ServiceExpireThread() {
      super("service expire");
      setDaemon(true);
  }

  public void run() {
      try {
    concurrentObj.writeLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!hasBeenInterrupted()) {
        long now = System.currentTimeMillis();
        while (true) {
      SvcReg reg = (SvcReg)serviceByTime.firstKey();
      minSvcExpiration = reg.leaseExpiration;
      if (minSvcExpiration > now)
          break;
      deleteService(reg, now);
      addLogRecord(new ServiceLeaseCancelledLogObj(
              reg.item.serviceID, reg.leaseID));
      if (logger.isLoggable(Level.FINE)) {
          logger.log(
        Level.FINE,
        "expired service registration {0}",
        new Object[]{ reg.item.serviceID });
      }
        }
        queueEvents();
        try {
      concurrentObj.writerWait(serviceNotifier,
             minSvcExpiration - now);
        } catch (ConcurrentLockException e) {
      return;
        }
    }
       } finally {
     concurrentObj.writeUnlock();
       }
  }
    }

    /** Event lease expiration thread code */
    private class EventExpireThread extends InterruptedStatusThread {

  /** Create a daemon thread */
  public EventExpireThread() {
      super("event expire");
      setDaemon(true);
  }

  public void run() {
      try {
    concurrentObj.writeLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!hasBeenInterrupted()) {
        long now = System.currentTimeMillis();
        minEventExpiration = Long.MAX_VALUE;
        while (!eventByTime.isEmpty()) {
      EventReg reg = (EventReg)eventByTime.firstKey();
      if (reg.leaseExpiration > now) {
          minEventExpiration = reg.leaseExpiration;
          break;
      }
      deleteEvent(reg);
      if (logger.isLoggable(Level.FINE)) {
          logger.log(
        Level.FINE,
        "expired event registration {0} for {1}",
        new Object[]{ reg.leaseID, reg.listener });
      }
        }
        try {
      concurrentObj.writerWait(eventNotifier,
             minEventExpiration - now);
        } catch (ConcurrentLockException e) {
      return;
        }
    }
       } finally {
     concurrentObj.writeUnlock();
       }
  }
    }

    /**
     * Termination thread code.  We do this in a separate thread to
     * avoid deadlock, because ActivationGroup.inactive will block until
     * in-progress RMI calls are finished.
     */
    private class DestroyThread extends InterruptedStatusThread {

  /** Create a non-daemon thread */
  public DestroyThread() {
      super("destroy");
      /* override inheritance from RMI daemon thread */
      setDaemon(false);
  }

  public void run() {
      long now = System.currentTimeMillis();
      long endTime = now + unexportTimeout;
      if (endTime < 0)
    endTime = Long.MAX_VALUE;
      boolean unexported = false;
      /* first try unexporting politely */
      while(!unexported && (now < endTime)) {
    unexported = serverExporter.unexport(false);
    if (!unexported) {
        try {
      final long sleepTime =
          Math.min(unexportWait, endTime - now);
      sleep(sleepTime);
      now = System.currentTimeMillis();
        } catch (InterruptedException e) {
      logger.log(
           Levels.HANDLED, "exception during unexport wait", e);
        }
    }
      }
      /* if still not unexported, forcibly unexport */
            if(!unexported) {
    serverExporter.unexport(true);
            }

      /* all daemons must terminate before deleting persistent store */
      serviceExpirer.interrupt();
      eventExpirer.interrupt();
      unicaster.interrupt();
      multicaster.interrupt();
      announcer.interrupt();
      snapshotter.interrupt();
      tasker.terminate();
      joiner.terminate();
      discoer.terminate();
      try {
    serviceExpirer.join();
    eventExpirer.join();
    unicaster.join();
    multicaster.join();
    announcer.join();
    snapshotter.join();
      } catch (InterruptedException e) {
      }
      closeRequestSockets(tasker.getPending());
      if (log != null) {
    log.deletePersistentStore();
    logger.finer("deleted persistence directory");
      }
      if (activationID != null) {
    try {
        ActivationGroup.inactive(activationID, serverExporter);
    } catch (Exception e) {
        logger.log(Level.INFO, "exception going inactive", e);
    }
      }
      if (lifeCycle != null) {
    lifeCycle.unregister(RegistrarImpl.this);
      }
      if (loginContext != null) {
    try {
        loginContext.logout();
    } catch (LoginException e) {
        logger.log(Level.INFO, "logout failed", e);
    }
      }
      logger.info("Reggie shutdown completed");
  }
    }

    /** Multicast discovery request thread code. */
    private class MulticastThread extends InterruptedStatusThread {

  /** Multicast group address used by multicast requests */
  private final InetAddress requestAddr;
  /** Multicast socket to receive packets */
  private final MulticastSocket socket;
  /** Interfaces for which configuration failed */
  private final List failedInterfaces = new ArrayList();

  /**
   * Create a high priority daemon thread.  Set up the socket now
   * rather than in run, so that we get any exception up front.
   */
  public MulticastThread() throws IOException {
      super("multicast request");
      setDaemon(true);
      if (multicastInterfaces != null && multicastInterfaces.length == 0)
      {
    requestAddr = null;
    socket = null;
    return;
      }
      requestAddr = Constants.getRequestAddress();
      socket = new MulticastSocket(Constants.discoveryPort);
      if (multicastInterfaces != null) {
    Level failureLogLevel = multicastInterfacesSpecified ?
        Level.WARNING : Levels.HANDLED;
    for (int i = 0; i < multicastInterfaces.length; i++) {
        NetworkInterface nic = multicastInterfaces[i];
        try {
      socket.setNetworkInterface(nic);
      socket.joinGroup(requestAddr);
        } catch (IOException e) {
      failedInterfaces.add(nic);
      if (logger.isLoggable(failureLogLevel)) {
          logThrow(
        failureLogLevel,
        getClass().getName(),
        "<init>",
        "exception enabling {0}",
        new Object[]{ nic },
        e);
      }
        }
    }
      } else {
    try {
        socket.joinGroup(requestAddr);
    } catch (IOException e) {
        failedInterfaces.add(null);
        logger.log(
      Level.WARNING,
      "exception enabling default interface", e);
    }
      }
  }

  public void run() {
      if (multicastInterfaces != null && multicastInterfaces.length == 0)
      {
    return;
      }
      byte[] buf = new byte[
    multicastRequestConstraints.getMulticastMaxPacketSize(
        DEFAULT_MAX_PACKET_SIZE)];
      DatagramPacket dgram = new DatagramPacket(buf, buf.length);
      long retryTime =
    System.currentTimeMillis() + multicastInterfaceRetryInterval;
      while (!hasBeenInterrupted()) {
    try {
        int timeout = 0;
        if (!failedInterfaces.isEmpty()) {
      timeout =
          (int) (retryTime - System.currentTimeMillis());
      if (timeout <= 0) {
          retryFailedInterfaces();
          if (failedInterfaces.isEmpty()) {
        timeout = 0;
          } else {
        timeout = multicastInterfaceRetryInterval;
        retryTime =
            System.currentTimeMillis() + timeout;
          }
      }
        }
        socket.setSoTimeout(timeout);
        dgram.setLength(buf.length);
        try {
      socket.receive(dgram);
        } catch (NullPointerException e) {
      break; // workaround for bug 4190513
        }

        int pv;
        try {
      pv = ByteBuffer.wrap(dgram.getData(),
               dgram.getOffset(),
               dgram.getLength()).getInt();
        } catch (BufferUnderflowException e) {
      throw new DiscoveryProtocolException(null, e);
        }
        multicastRequestConstraints.checkProtocolVersion(pv);
        tasker.add(new DecodeRequestTask(dgram, getDiscovery(pv)));

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

    } catch (SocketTimeoutException e) {
        // retry failed network interfaces in next iteration
    } catch (InterruptedIOException e) {
        break;
    } catch (Exception e) {
        if (hasBeenInterrupted()) {
      break;
        }
        logger.log(Levels.HANDLED,
             "exception receiving multicast request", e);
    }
      }
      socket.close();
  }

  /* This is a workaround for Thread.interrupt not working on
   * MulticastSocket.receive on all platforms.
   */
  public synchronized void interrupt() {
      socket.close();
      super.interrupt();
  }

  /**
   * Attempts to configure each interface contained in the
   * failedInterfaces list, removing it from the list if configuration
   * succeeds.  The null value is used to indicate the default network
   * interface.
   */
  private void retryFailedInterfaces() {
      for (Iterator i = failedInterfaces.iterator(); i.hasNext(); ) {
    NetworkInterface nic = (NetworkInterface) i.next();
    try {
        if (nic != null) {
      socket.setNetworkInterface(nic);
        }
        socket.joinGroup(requestAddr);
        i.remove();

        Level l = multicastInterfacesSpecified ?
      Level.INFO : Level.FINE;
        if (logger.isLoggable(l)) {
      if (nic != null) {
          logger.log(l, "enabled {0}", new Object[]{ nic });
      } else {
          logger.log(l, "enabled default interface");
      }
        }
    } catch (IOException e) {
        // keep nic in failedInterfaces
    }
      }
  }
    }

    /** Unicast discovery request thread code. */
    private class UnicastThread extends InterruptedStatusThread {
  /** Server socket to accepts connections on. */
  private ServerSocket listen;
  /** Listen port */
  public int port;

  /**
   * Create a daemon thread.  Set up the socket now rather than in run,
   * so that we get any exception up front.
   */
  public UnicastThread(int port) throws IOException {
      super("unicast request");
      setDaemon(true);
      if (port == 0) {
    try {
        listen = new ServerSocket(Constants.discoveryPort);
    } catch (IOException e) {
        logger.log(
      Levels.HANDLED, "failed to bind to default port", e);
    }
      }
      if (listen == null) {
    listen = new ServerSocket(port);
      }
      this.port = listen.getLocalPort();
  }

  public void run() {
      while (!hasBeenInterrupted()) {
    try {
        Socket socket = listen.accept();
        if (hasBeenInterrupted()) {
      try {
          socket.close();
      } catch (IOException e) {
          logger.log(
        Levels.HANDLED, "exception closing socket", e);
      }
      break;
        }
        tasker.add(new SocketTask(socket));
    } catch (InterruptedIOException e) {
        break;
    } catch (Exception e) {
        logger.log(
      Levels.HANDLED, "exception listening on socket", e);
    }
    /* if we fail in any way, just forget about it */
      }
      try {
    listen.close();
      } catch (IOException e) {
    logger.log(
        Levels.HANDLED, "exception closing server socket", e);
      }
  }

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

    }

    /** Multicast discovery announcement thread code. */
    private class AnnounceThread extends InterruptedStatusThread {
  /** Multicast socket to send packets on */
  private final MulticastSocket socket;
  /** Cached datagram packets */
  private DatagramPacket[] dataPackets = null;
  /** LookupLocator associated with cached datagram packets */
  private LookupLocator lastLocator;
  /** Groups associated with cached datagram packets */
  private String[] lastGroups;

  /**
   * Create a daemon thread.  Set up the socket now rather than in run,
   * so that we get any exception up front.
   */
  public AnnounceThread() throws IOException {
      super("discovery announcement");
      setDaemon(true);
      if (multicastInterfaces == null || multicastInterfaces.length > 0)
      {
    socket = new MulticastSocket();
    socket.setTimeToLive(
        multicastAnnouncementConstraints.getMulticastTimeToLive(
      DEFAULT_MULTICAST_TTL));
      } else {
    socket = null;
      }
  }

  public synchronized void run() {
      if (multicastInterfaces != null && multicastInterfaces.length == 0)
      {
    return;
      }
      try {
    while (!hasBeenInterrupted() && announce(memberGroups)) {
        wait(multicastAnnouncementInterval);
    }
      } catch (InterruptedException e) {
      }
      if (memberGroups.length > 0)
    announce(new String[0]);//send NO_GROUPS just before shutdown
      socket.close();
  }

  /**
   * Announce membership in the specified groups, and return false if
   * interrupted, otherwise return true.  This method is run from
   * synchronized run method in thread.
   */
  private boolean announce(String[] groups) {
      if (dataPackets == null || !lastLocator.equals(myLocator) ||
          !Arrays.equals(lastGroups, groups))
      {
          List packets = new ArrayList();
          Discovery disco;
          try {
        disco = getDiscovery(multicastAnnouncementConstraints
           .chooseProtocolVersion());
          } catch (DiscoveryProtocolException e) {
        throw new AssertionError(e);
          }
          EncodeIterator ei = disco.encodeMulticastAnnouncement(
        new MulticastAnnouncement(announcementSeqNo++,
            myLocator.getHost(),
            myLocator.getPort(),
            groups,
            myServiceID),
        multicastAnnouncementConstraints
        .getMulticastMaxPacketSize(DEFAULT_MAX_PACKET_SIZE),
        multicastAnnouncementConstraints
        .getUnfulfilledConstraints());
          while (ei.hasNext()) {
        try {
            packets.addAll(Arrays.asList(ei.next()));
        } catch (Exception e) {
      logger.log( (e instanceof
             UnsupportedConstraintException)
            ? Levels.HANDLED : Level.INFO,
            "exception encoding multicast"
            + " announcement", e);
        }
    }
    lastLocator = myLocator;
    lastGroups = groups;
    dataPackets = (DatagramPacket[]) packets.toArray(
        new DatagramPacket[packets.size()]);
      }
      try {
          send(dataPackets);
      } catch (InterruptedIOException e) {
    return false;
      } catch (IOException e) {
    logger.log(
        Level.INFO, "exception sending multicast announcement", e);
      }
      return true;
  }

  /**
   * Attempts to multicast the given packets on each of the configured
   * network interfaces.
   */
  private void send(DatagramPacket[] packets)
      throws InterruptedIOException
  {
      if (multicastInterfaces != null) {
    Level failureLogLevel = multicastInterfacesSpecified ?
        Level.WARNING : Levels.HANDLED;
    for (int i = 0; i < multicastInterfaces.length; i++) {
        send(packets, multicastInterfaces[i], failureLogLevel);
    }
      } else {
    send(packets, null, Level.WARNING);
      }
  }

  /**
   * Attempts to multicast the given packets on the specified network
   * interface, logging failures at the given logging level.  If the
   * specified network interface is null, then the default interface is
   * used.
   */
  private void send(DatagramPacket[] packets,
        NetworkInterface nic,
        Level failureLogLevel)
      throws InterruptedIOException
  {
      if (nic != null) {
    try {
        socket.setNetworkInterface(nic);
    } catch (SocketException e) {
        if (logger.isLoggable(failureLogLevel)) {
      logThrow(
          failureLogLevel,
          getClass().getName(),
          "send",
          "exception setting {0}",
          new Object[]{ nic },
          e);
        }
        return;
    }
      }
      for (int i = 0; i < packets.length; i++) {
    try {
        socket.send(packets[i]);
    } catch (InterruptedIOException e) {
        throw e;
    } catch (IOException e) {
        if (nic != null) {
      if (logger.isLoggable(failureLogLevel)) {
          logThrow(
        failureLogLevel,
        getClass().getName(),
        "send",
        "exception sending packet on {0}",
        new Object[]{ nic },
        e);
      }
        } else {
      logger.log(
          failureLogLevel,
          "exception sending packet on default interface",
          e);
        }
    }
      }
  }
    }

    /**
     * Snapshot-taking thread.
     * <p>
     * A snapshot is taken when -- after writing a new record to the
     * log file -- it is determined that the size of the log file has
     * exceeded a certain threshold. The code which adds the new record
     * to the log file and which, in turn, decides that a snapshot
     * must be taken is "wrapped" in a writer mutex. That is, synchronization
     * of processing is achieved in the Registrar through a "reader/writer"
     * mutex construct. This construct allows only one writer at any one
     * time; but allows an unlimited number of simultaneous readers as
     * long as no writer has locked the mutex. During steady-state, it is
     * anticipated that there will be far more readers (e.g. lookups) in use
     * than writers (e.g. add/mod/del Attributes). Since the process of
     * taking a snapshot can be time-consuming, if the whole snapshot-taking
     * process occupies that single writer mutex, then a significant number
     * of read requests will be un-necessarily blocked; possibly resulting
     * in an unacceptable degradation in response time.
     * <p>
     * It is for the above reason that the process of taking a snapshot is
     * performed in a separate thread. The thread waits on the monitor
     * belonging to the snapshotNotifier instance until it is notified
     * (or "signalled") that a snapshot must be taken. The notification
     * is sent by another thread, created by the Registrar, which
     * determines when the conditions are right for a snapshot. The
     * notification takes the form of an interrupt indicating that the
     * Snapshot monitor is available. Although the interrupt is sent
     * while the writer mutex is locked, the act of sending the notification
     * is less time-consuming than the act of taking the snapshot itself.
     * When the thread receives a notification, it awakens and requests a
     * lock on the reader mutex (this is all done in the readerWait() method).
     * Because a reader -- not a writer -- mutex is locked, read-only
     * processes still have access to the system state, so lookups can be
     * performed; and the reader mutex prevents changes to the data while
     * the snapshot is in progress. 
     * <p>
     * Note that the current snapshot is guaranteed to complete before the
     * next snapshot request is received. This is because even though
     * the act of taking a snapshot can be viewed as a writer process,
     * the fact that the next snapshot notification will be wrapped in a
     * writer mutex, combined with the fact that a writer mutex can not
     * be locked while a reader mutex is locked, allows the snapshot to
     * be treated as a reader process.
     */
    private class SnapshotThread extends InterruptedStatusThread {

  /** Create a daemon thread */
  public SnapshotThread() {
      super("snapshot thread");
      setDaemon(true);
  }

  public void run() {
      if (log == null) {
    return;
      }
      try {
    concurrentObj.readLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!hasBeenInterrupted()) {
        try {
      concurrentObj.readerWait(snapshotNotifier,
             Long.MAX_VALUE);
                        try {
                            log.snapshot();
                logFileSize = 0;
                  } catch (Exception e) {
          if (hasBeenInterrupted())
        return;
          logger.log(Level.WARNING, "snapshot failed", e);
            }
        } catch (ConcurrentLockException e) {
      return;
        }
    }
      } finally {
    concurrentObj.readUnlock();
      }
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Object getServiceProxy() throws NoSuchObjectException {
  ready.check();
  return proxy;
    }

    // This method's javadoc is inherited from an interface of this class
    public Object getProxy() {
  /** locally-called method - no need to check initialization state */
  return myRef;
    }

    // This method's javadoc is inherited from an interface of this class
    public TrustVerifier getProxyVerifier() throws NoSuchObjectException {
  ready.check();
  return new ProxyVerifier(myRef, myServiceID);
    }

    // This method's javadoc is inherited from an interface of this class
    public ServiceRegistration register(Item nitem, long leaseDuration)
        throws NoSuchObjectException
   
  concurrentObj.writeLock();
  try {
      ready.check();
      ServiceRegistration reg = registerDo(nitem, leaseDuration);
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "registered instance of {0} as {1}",
        new Object[]{
      nitem.serviceType.getName(), reg.getServiceID() });
      }
      return reg;
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public MarshalledWrapper lookup(Template tmpl) throws NoSuchObjectException
   
  concurrentObj.readLock();
  try {
      ready.check();
      return lookupDo(tmpl);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Matches lookup(Template tmpl, int maxMatches)
  throws NoSuchObjectException
   
  concurrentObj.readLock();
  try {
      ready.check();
      return lookupDo(tmpl, maxMatches);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public EventRegistration notify(Template tmpl,
            int transitions,
            RemoteEventListener listener,
            MarshalledObject handback,
            long leaseDuration)
  throws RemoteException
   
  concurrentObj.writeLock();
  try {
      ready.check();
      EventRegistration reg = notifyDo(
    tmpl, transitions, listener, handback, leaseDuration);
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "registered event listener {0} as {1}",
        new Object[]{
      listener,
      ((ReferentUuid) reg.getLease()).getReferentUuid()
        });
      }
      return reg;
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public EntryClassBase[] getEntryClasses(Template tmpl)
        throws NoSuchObjectException
    {
  concurrentObj.readLock();
  try {
      ready.check();
      return getEntryClassesDo(tmpl);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Object[] getFieldValues(Template tmpl, int setIndex, int field)
        throws NoSuchObjectException
    {
  concurrentObj.readLock();
  try {
      ready.check();
      return getFieldValuesDo(tmpl, setIndex, field);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public ServiceTypeBase[] getServiceTypes(Template tmpl, String prefix)
        throws NoSuchObjectException
    {
  concurrentObj.readLock();
  try {
      ready.check();
      return getServiceTypesDo(tmpl, prefix);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public LookupLocator getLocator() throws NoSuchObjectException {
  ready.check();
  return myLocator;
    }

    // This method's javadoc is inherited from an interface of this class
    public Object getAdmin() throws NoSuchObjectException {
  ready.check();
  return AdminProxy.getInstance(myRef, myServiceID);
    }

    // This method's javadoc is inherited from an interface of this class
    public void addAttributes(ServiceID serviceID,
            Uuid leaseID,
            EntryRep[] attrSets)
  throws NoSuchObjectException, UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      if (serviceID.equals(myServiceID))
          throw new SecurityException("privileged service id");
      addAttributesDo(serviceID, leaseID, attrSets);
      addLogRecord(new AttrsAddedLogObj(serviceID, leaseID, attrSets));
      queueEvents();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void modifyAttributes(ServiceID serviceID,
         Uuid leaseID,
         EntryRep[] attrSetTmpls,
         EntryRep[] attrSets)
  throws NoSuchObjectException, UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      if (serviceID.equals(myServiceID))
          throw new SecurityException("privileged service id");
      modifyAttributesDo(serviceID, leaseID, attrSetTmpls, attrSets);
      addLogRecord(new AttrsModifiedLogObj(serviceID, leaseID,
             attrSetTmpls, attrSets));
      queueEvents();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setAttributes(ServiceID serviceID,
            Uuid leaseID,
            EntryRep[] attrSets)
  throws NoSuchObjectException, UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      if (serviceID.equals(myServiceID))
          throw new SecurityException("privileged service id");
      setAttributesDo(serviceID, leaseID, attrSets);
      addLogRecord(new AttrsSetLogObj(serviceID, leaseID, attrSets));
      queueEvents();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void cancelServiceLease(ServiceID serviceID, Uuid leaseID)
  throws NoSuchObjectException, UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      cancelServiceLeaseDo(serviceID, leaseID);
      addLogRecord(new ServiceLeaseCancelledLogObj(serviceID, leaseID));
      queueEvents();
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "cancelled service registration {0}",
        new Object[]{ serviceID });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long renewServiceLease(ServiceID serviceID,
          Uuid leaseID,
          long renewDuration)
  throws NoSuchObjectException, UnknownLeaseException
   
  concurrentObj.priorityWriteLock();
  try {
      ready.check();
      return renewServiceLeaseDo(serviceID, leaseID, renewDuration);
      /* addLogRecord is in renewServiceLeaseDo */
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void cancelEventLease(long eventID, Uuid leaseID)
  throws NoSuchObjectException, UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      cancelEventLeaseDo(eventID, leaseID);
      addLogRecord(new EventLeaseCancelledLogObj(eventID, leaseID));
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "cancelled event registration {0}",
        new Object[]{ leaseID });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long renewEventLease(long eventID, Uuid leaseID, long renewDuration)
  throws NoSuchObjectException, UnknownLeaseException
   
  concurrentObj.priorityWriteLock();
  try {
      ready.check();
      return renewEventLeaseDo(eventID, leaseID, renewDuration);
      /* addLogRecord is in renewEventLeaseDo */
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public RenewResults renewLeases(Object[] regIDs,
            Uuid[] leaseIDs,
            long[] renewDurations)
        throws NoSuchObjectException
   
  concurrentObj.priorityWriteLock();
  try {
      ready.check();
      return renewLeasesDo(regIDs, leaseIDs, renewDurations);
      /* addLogRecord is in renewLeasesDo */
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Exception[] cancelLeases(Object[] regIDs, Uuid[] leaseIDs)
        throws NoSuchObjectException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      Exception[] exceptions = cancelLeasesDo(regIDs, leaseIDs);
      addLogRecord(new LeasesCancelledLogObj(regIDs, leaseIDs));
      queueEvents();
      if (logger.isLoggable(Level.FINE)) {
    for (int i = 0; i < regIDs.length; i++) {
        if (exceptions != null && exceptions[i] != null) {
      continue;
        }
        if (regIDs[i] instanceof ServiceID) {
      logger.log(
          Level.FINE,
          "cancelled service registration {0}",
          new Object[]{ regIDs[i] });
        } else {
      logger.log(
          Level.FINE,
          "cancelled event registration {0}",
          new Object[]{ leaseIDs[i] });
        }
    }
      }
      return exceptions;
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Entry[] getLookupAttributes() throws NoSuchObjectException {
  concurrentObj.readLock();
  try {
      ready.check();
      /* no need to clone, never modified once created */
      return lookupAttrs;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void addLookupAttributes(Entry[] attrSets) throws RemoteException {
  concurrentObj.writeLock();
  try {
      ready.check();
      EntryRep[] attrs = EntryRep.toEntryRep(attrSets, true);
      addAttributesDo(myServiceID, myLeaseID, attrs);
      joiner.addAttributes(attrSets);
      lookupAttrs = joiner.getAttributes();
      addLogRecord(new LookupAttributesChangedLogObj(lookupAttrs));
      queueEvents();
  } catch (UnknownLeaseException e) {
      throw new AssertionError("Self-registration never expires");
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void modifyLookupAttributes(Entry[] attrSetTemplates,
               Entry[] attrSets)
  throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      EntryRep[] tmpls = EntryRep.toEntryRep(attrSetTemplates, false);
      EntryRep[] attrs = EntryRep.toEntryRep(attrSets, false);
      modifyAttributesDo(myServiceID, myLeaseID, tmpls, attrs);
      joiner.modifyAttributes(attrSetTemplates, attrSets, true);
      lookupAttrs = joiner.getAttributes();
      addLogRecord(new LookupAttributesChangedLogObj(lookupAttrs));
      queueEvents();
  } catch (UnknownLeaseException e) {
      throw new AssertionError("Self-registration never expires");
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public String[] getLookupGroups() throws NoSuchObjectException {
  concurrentObj.readLock();
  try {
      ready.check();
      /* no need to clone, never modified once created */
      return lookupGroups;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void addLookupGroups(String[] groups) throws NoSuchObjectException {
  concurrentObj.writeLock();
  try {
      ready.check();
      DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
      try {
    dgm.addGroups(groups);
      } catch (IOException e) {
    throw new RuntimeException(e.toString());
      }
      lookupGroups = dgm.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "added lookup groups {0}",
        new Object[]{ Arrays.asList(groups) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeLookupGroups(String[] groups)
  throws NoSuchObjectException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
      dgm.removeGroups(groups);
      lookupGroups = dgm.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "removed lookup groups {0}",
        new Object[]{ Arrays.asList(groups) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setLookupGroups(String[] groups) throws NoSuchObjectException {
  concurrentObj.writeLock();
  try {
      ready.check();
      DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
      try {
    dgm.setGroups(groups);
      } catch (IOException e) {
    throw new RuntimeException(e.toString());
      }
      lookupGroups = dgm.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "set lookup groups {0}",
        new Object[]{
      (groups != null) ? Arrays.asList(groups) : null });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public LookupLocator[] getLookupLocators() throws NoSuchObjectException {
  concurrentObj.readLock();
  try {
      ready.check();
      /* no need to clone, never modified once created */
      return lookupLocators;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void addLookupLocators(LookupLocator[] locators)
  throws RemoteException
   
  ready.check();
  locators = prepareLocators(locators, locatorPreparer, false);
  concurrentObj.writeLock();
  try {
      ready.check();     
      DiscoveryLocatorManagement dlm =
    (DiscoveryLocatorManagement) discoer;
      dlm.addLocators(locators);
      lookupLocators = dlm.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "added lookup locators {0}",
        new Object[]{ Arrays.asList(locators) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeLookupLocators(LookupLocator[] locators)
  throws RemoteException
   
      ready.check();
      locators = prepareLocators(locators, locatorPreparer, false);
  concurrentObj.writeLock();
  try {
      ready.check();     
      DiscoveryLocatorManagement dlm =
    (DiscoveryLocatorManagement) discoer;
      dlm.removeLocators(locators);
      lookupLocators = dlm.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "removed lookup locators {0}",
        new Object[]{ Arrays.asList(locators) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setLookupLocators(LookupLocator[] locators)
  throws RemoteException
    {
      ready.check();
      locators = prepareLocators(locators, locatorPreparer, false);
  concurrentObj.writeLock();
  try {
      ready.check();
      DiscoveryLocatorManagement dlm =
    (DiscoveryLocatorManagement) discoer;
      dlm.setLocators(locators);
      lookupLocators = dlm.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "set lookup locators {0}",
        new Object[]{ Arrays.asList(locators) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void addMemberGroups(String[] groups) throws NoSuchObjectException {
  concurrentObj.writeLock();
  try {
      ready.check();
      for (int i = 0; i < groups.length; i++) {
          if (indexOf(memberGroups, groups[i]) < 0)
        memberGroups = (String[])arrayAdd(memberGroups, groups[i]);
      }
      synchronized (announcer) {
    announcer.notify();
      }
      addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "added member groups {0}",
        new Object[]{ Arrays.asList(groups) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeMemberGroups(String[] groups)
  throws NoSuchObjectException
    {
  concurrentObj.writeLock();
  try {
      ready.check();
      for (int i = 0; i < groups.length; i++) {
                int j = indexOf(memberGroups, groups[i]);
          if (j >= 0)
        memberGroups = (String[])arrayDel(memberGroups, j);
      }
      synchronized (announcer) {
    announcer.notify();
      }
      addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "removed member groups {0}",
        new Object[]{ Arrays.asList(groups) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public String[] getMemberGroups() throws NoSuchObjectException {
  concurrentObj.readLock();
  try {
      ready.check();
      /* no need to clone, never modified once created */
      return memberGroups;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setMemberGroups(String[] groups) throws NoSuchObjectException {
  concurrentObj.writeLock();
  try {
      ready.check();
      memberGroups = (String[])removeDups(groups);
      addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
      synchronized (announcer) {
    announcer.notify();
      }
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "set member groups {0}",
        new Object[]{ Arrays.asList(groups) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public int getUnicastPort() throws NoSuchObjectException {
  concurrentObj.readLock();
  try {
      ready.check();
      return unicastPort;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setUnicastPort(int port) throws IOException,RemoteException {
  concurrentObj.writeLock();
  try {
      ready.check();
      if (port == unicastPort)
    return;
      if ((port == 0 && unicaster.port == Constants.discoveryPort) ||
    port == unicaster.port)
      {
    unicastPort = port;
    addLogRecord(new UnicastPortSetLogObj(port));
    return;
      }
      /* create a UnicastThread that listens on the new port */
      UnicastThread newUnicaster = new UnicastThread(port);
      /* terminate the current UnicastThread listening on the old port */
      unicaster.interrupt();
      try {
    unicaster.join();
      } catch (InterruptedException e) { }
      /* start the UnicastThread listening on the new port */
      unicaster = newUnicaster;
      unicaster.start();
      unicastPort = port;
      myLocator = (proxy instanceof RemoteMethodControl) ?
    new ConstrainableLookupLocator(
        myLocator.getHost(), unicaster.port, null) :
    new LookupLocator(myLocator.getHost(), unicaster.port);
      synchronized (announcer) {
    announcer.notify();
      }
      addLogRecord(new UnicastPortSetLogObj(port));
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(
        Level.CONFIG,
        "changed unicast discovery port to {0}",
        new Object[]{ new Integer(unicaster.port) });
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void destroy() throws RemoteException {
  concurrentObj.priorityWriteLock();
  try {
      ready.check();
      logger.info("starting Reggie shutdown");
      /* unregister with activation system if activatable */
      if (activationID != null) {
    try {
        activationSystem.unregisterObject(activationID);
    } catch (ActivationException e) {
        logger.log(Levels.HANDLED,
             "exception unregistering activation ID", e);
    } catch (RemoteException e) {
        logger.log(Level.WARNING,
             "aborting Reggie shutdown", e);
        throw e;
    }
      }
      ready.shutdown();     
      new DestroyThread().start();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    /**
     * Return a new array containing the elements of the given array
     * plus the given element added to the end.
     */
    private static Object[] arrayAdd(Object[] array, Object elt) {
  int len = array.length;
  Object[] narray =
      (Object[])Array.newInstance(array.getClass().getComponentType(),
          len + 1);
  System.arraycopy(array, 0, narray, 0, len);
  narray[len] = elt;
  return narray;
    }

    /**
     * Return a new array containing all the elements of the given array
     * except the one at the specified index.
     */
    private static Object[] arrayDel(Object[] array, int i) {
  int len = array.length - 1;
  Object[] narray =
      (Object[])Array.newInstance(array.getClass().getComponentType(),
          len);
  System.arraycopy(array, 0, narray, 0, i);
  System.arraycopy(array, i + 1, narray, i, len - i);
  return narray;
    }

    /** Returns the first index of elt in the array, else -1. */
    private static int indexOf(Object[] array, Object elt) {
  return indexOf(array, array.length, elt);
    }

    /** Returns the first index of elt in the array if < len, else -1. */
    private static int indexOf(Object[] array, int len, Object elt) {
  for (int i = 0; i < len; i++) {
      if (elt.equals(array[i]))
    return i;
  }
  return -1;
    }

    /** Return true if the array is null or zero length */
    private static boolean isEmpty(Object[] array) {
  return (array == null || array.length == 0);
    }

    /** Return true if some object is an element of both arrays */
    private static boolean overlap(Object[] arr1, Object[] arr2) {
  for (int i = arr1.length; --i >= 0; ) {
      if (indexOf(arr2, arr1[i]) >= 0)
    return true;
  }
  return false;
    }

    /** Test if all elements of the array are null. */
    private static boolean allNull(Object[] array) {
  for (int i = array.length; --i >= 0; ) {
      if (array[i] != null)
    return false;
  }
  return true;
    }

    /** Weed out duplicates. */
    private static Object[] removeDups(Object[] arr) {
  for (int i = arr.length; --i >= 0; ) {
      if (indexOf(arr, i, arr[i]) >= 0)
    arr = arrayDel(arr, i);
  }
  return arr;
    }

    /** Delete item.attributeSets[i] and return the new array. */
    private static EntryRep[] deleteSet(Item item, int i) {
  item.attributeSets = (EntryRep[])arrayDel(item.attributeSets, i);
  return item.attributeSets;
    }

    /**
     * Do a deep copy of the item, and substitute replacements for all
     * embedded EntryClass instances and null for the ServiceType and
     * codebase (since they aren't needed on the client side).
     */
    private static Item copyItem(Item item) {
  item = (Item)item.clone();
  item.serviceType = null;
  item.codebase = null;
  EntryRep[] attrSets = item.attributeSets;
  for (int i = attrSets.length; --i >= 0; ) {
      attrSets[i].eclass = attrSets[i].eclass.getReplacement();
  }
  return item;
    }

    /**
     * Return the first (highest) class that defines the given field.  This
     * would be a method on EntryClass, but we want to minimize code
     * downloaded into the client.
     */
    private static EntryClass getDefiningClass(EntryClass eclass, int fldidx) {
  while (true) {
      EntryClass sup = eclass.getSuperclass();
      if (sup.getNumFields() <= fldidx)
    return eclass;
      eclass = sup;
  }
    }

    /** Adds a service registration to types in its hierarchy */
    private void addServiceByTypes(ServiceType type, SvcReg reg) {
  Map map = (Map) serviceByTypeName.get(type.getName());
  if (map == null) {
      map = new HashMap();
      serviceByTypeName.put(type.getName(), map);
  }
  map.put(reg.item.serviceID, reg)
  ServiceType[] ifaces = type.getInterfaces();
  for (int i = ifaces.length; --i >= 0; ) {
      addServiceByTypes(ifaces[i], reg);
  }
  ServiceType sup = type.getSuperclass();
  if (sup != null)
      addServiceByTypes(sup, reg);
    }

    /** Deletes a service registration from types in its hierarchy */
    private void deleteServiceFromTypes(ServiceType type, SvcReg reg)
    {
  Map map = (Map) serviceByTypeName.get(type.getName());
  if (map != null) {
      map.remove(reg.item.serviceID);
      if ((map.isEmpty()) && !type.equals(objectServiceType))
    serviceByTypeName.remove(type.getName());
      ServiceType[] ifaces = type.getInterfaces();
      for (int j = ifaces.length; --j >= 0; ) {
    deleteServiceFromTypes(ifaces[j], reg);
      }
      ServiceType sup = type.getSuperclass();
      if (sup != null)
    deleteServiceFromTypes(sup, reg);
  }
    }

    /**
     * Test if an item matches a template.  This would be a method on
     * Template, but we want to minimize code downloaded into the client.
     */
    private static boolean matchItem(Template tmpl, Item item) {
  return ((tmpl.serviceID == null ||
     tmpl.serviceID.equals(item.serviceID)) &&
    matchType(tmpl.serviceTypes, item.serviceType) &&
    matchAttributes(tmpl, item));
    }

    /** Test if a type is equal to or a subtype of every type in an array. */
    private static boolean matchType(ServiceType[] types, ServiceType type) {
  if (types != null) {
      for (int i = types.length; --i >= 0; ) {
    if (!types[i].isAssignableFrom(type))
        return false;
      }
  }
  return true;
    }

    /**
     * Test if an entry matches a template.  This would be a method on
     * EntryRep, but we want to minimize code downloaded into the client.
     */
    private static boolean matchEntry(EntryRep tmpl, EntryRep entry) {
  if (!tmpl.eclass.isAssignableFrom(entry.eclass) ||
      tmpl.fields.length > entry.fields.length)
      return false;
  for (int i = tmpl.fields.length; --i >= 0; ) {
      if (tmpl.fields[i] != null &&
    !tmpl.fields[i].equals(entry.fields[i]))
    return false;
  }
  return true;
    }

    /**
     * Test if there is at least one matching entry in the Item for
     * each entry template in the Template.
     */
    private static boolean matchAttributes(Template tmpl, Item item) {
  EntryRep[] tmpls = tmpl.attributeSetTemplates;
  if (tmpls != null) {
      EntryRep[] entries = item.attributeSets;
  outer:
      for (int i = tmpls.length; --i >= 0; ) {
    EntryRep etmpl = tmpls[i];
    for (int j = entries.length; --j >= 0; ) {
        if (matchEntry(etmpl, entries[j]))
      continue outer;
    }
    return false;
      }
  }
  return true;
    }

    /**
     * Test if an entry either doesn't match any template in an array,
     * or matches a template but is a subclass of the template type.
     */
    private static boolean attrMatch(EntryRep[] tmpls, EntryRep attrSet) {
  boolean good = true;
  if (tmpls != null) {
      for (int i = tmpls.length; --i >= 0; ) {
    EntryRep tmpl = tmpls[i];
    if (matchEntry(tmpl, attrSet)) {
        if (tmpl.eclass.isAssignableFrom(attrSet.eclass) &&
      !tmpl.eclass.equals(attrSet.eclass))
      return true;
        good = false;
    }
      }
  }
  return good;
    }

    /**
     * Test if the service has an entry of the given class or subclass
     * with a field of the given value.
     */
    private static boolean hasAttr(SvcReg reg,
           EntryClass eclass,
           int fldidx,
           Object value)
    {
  EntryRep[] sets = reg.item.attributeSets;
  for (int i = sets.length; --i >= 0; ) {
      EntryRep set = sets[i];
      if (eclass.isAssignableFrom(set.eclass) &&
    ((value == null && set.fields[fldidx] == null) ||
     (value != null && value.equals(set.fields[fldidx]))))
    return true;
  }
  return false;
    }

    /**
     * Test if the service has an entry of the exact given class (assumed
     * to have no fields).
     */
    private static boolean hasEmptyAttr(SvcReg reg, EntryClass eclass) {
  EntryRep[] sets = reg.item.attributeSets;
  for (int i = sets.length; --i >= 0; ) {
      if (eclass.equals(sets[i].eclass))
    return true;
  }
  return false;
    }

    /**
     * Find the most specific types (of type) that don't match prefix and
     * aren't equal to or a supertype of any types in bases, and add them
     * to types.
     */
    private static void addTypes(ArrayList types,
         ArrayList codebases,
         ServiceType[] bases,
         String prefix,
         ServiceType type,
         String codebase)
    {
  if (types.contains(type))
      return;
  if (bases != null) {
      for (int i = bases.length; --i >= 0; ) {
    if (type.isAssignableFrom(bases[i]))
        return;
      }
  }
  if (prefix == null || type.getName().startsWith(prefix)) {
      types.add(type);
      codebases.add(codebase);
      return;
  }
  ServiceType[] ifs = type.getInterfaces();
  for (int i = ifs.length; --i >= 0; ) {
      addTypes(types, codebases, bases, prefix, ifs[i], codebase);
  }
  ServiceType sup = type.getSuperclass();
  if (sup != null)
      addTypes(types, codebases, bases, prefix, sup, codebase);
    }

    /** Limit leaseDuration by limit, and check for negative value. */
    private static long limitDuration(long leaseDuration, long limit) {
  if (leaseDuration == Lease.ANY || leaseDuration > limit)
      leaseDuration = limit;
  else if (leaseDuration < 0)
      throw new IllegalArgumentException("negative lease duration");
  return leaseDuration;
    }

    /**
     * Writes reggie's attributes to ObjectOutputStream as a
     * null-terminated list of MarshalledInstances.
     */
    private static void marshalAttributes(Entry[] attrs,
            ObjectOutputStream out)
  throws IOException
    {
  for (int i=0; i < attrs.length; i++) {
      out.writeObject(new MarshalledInstance(attrs[i]));
  }
  out.writeObject(null);
    }

    /**
     * Returns reggie's attributes unmarshalled from a null-terminated list of
     * MarshalledInstances read from the given stream, logging (but tolerating)
     * unmarshalling failures.
     */
    private static Entry[] unmarshalAttributes(ObjectInputStream in)
  throws IOException, ClassNotFoundException
    {
  ArrayList attributes = new ArrayList();
  MarshalledInstance mi = null;
  while ((mi = (MarshalledInstance) in.readObject()) != null) {
      try {
    attributes.add((Entry) mi.get(false));
      } catch (Throwable e) {
    if (e instanceof Error &&
        ThrowableConstants.retryable(e) ==
      ThrowableConstants.BAD_OBJECT)
    {
        throw (Error) e;
    }
    logger.log(
        Level.WARNING, "failed to recover LUS attribute", e);
      }
  }
  Entry[] attrs = new Entry[attributes.size()];
  return (Entry[])attributes.toArray(attrs);

    }


    /**
     * Writes locators to the given stream as a null-terminated list of
     * MarshalledInstances.
     */
    private static void marshalLocators(LookupLocator[] locators,
          ObjectOutputStream out)
  throws IOException
    {
  for (int i = 0; i < locators.length; i++) {
      out.writeObject(new MarshalledInstance(locators[i]));
  }
  out.writeObject(null);
    }

    /**
     * Returns locators unmarshalled from a null-terminated list of
     * MarshalledInstances read from the given stream, logging (but tolerating)
     * unmarshalling failures.
     */
    private static LookupLocator[] unmarshalLocators(ObjectInputStream in)
  throws IOException, ClassNotFoundException
    {
  List l = new ArrayList();
  MarshalledInstance mi;
  while ((mi = (MarshalledInstance) in.readObject()) != null) {
      try {
    l.add((LookupLocator) mi.get(false));
      } catch (Throwable e) {
    if (e instanceof Error &&
        ThrowableConstants.retryable(e) ==
      ThrowableConstants.BAD_OBJECT)
    {
        throw (Error) e;
    }
    logger.log(
        Level.WARNING, "failed to recover lookup locator", e);
      }
  }
  return (LookupLocator[]) l.toArray(new LookupLocator[l.size()]);
    }
       
    /**
     * Returns new array containing locators from the given non-null array
     * prepared using the given proxy preparer.  If tolerateFailures is false,
     * then any proxy preparation exception is propagated to the caller.
     * Otherwise, such exceptions are logged, and only successfully prepared
     * locators are included in the returned array.
     */
    private static LookupLocator[] prepareLocators(LookupLocator[] locators,
               ProxyPreparer preparer,
               boolean tolerateFailures)
  throws RemoteException
    {
  List l = new ArrayList();
  for (int i = 0; i < locators.length; i++) {
      try {
    l.add((LookupLocator) preparer.prepareProxy(locators[i]));
      } catch (Exception e) {
    if (!tolerateFailures) {
        if (e instanceof RemoteException) {
      throw (RemoteException) e;
        } else {
      throw (RuntimeException) e;
        }
    }
    if (logger.isLoggable(Level.WARNING)) {
        logThrow(
      Level.WARNING,
      RegistrarImpl.class.getName(),
      "prepareLocators",
      "failed to prepare lookup locator {0}",
      new Object[]{ locators[i] },
      e);
    }
      }
  }
  return (LookupLocator[]) l.toArray(new LookupLocator[l.size()]);
    }

    /**
     * Logs a thrown exception.
     */
    private static void logThrow(Level level,
         String className,
         String methodName,
         String message,
         Object[] args,
         Throwable thrown)
    {
  java.util.logging.LogRecord lr =
      new java.util.logging.LogRecord(level, message);
  lr.setLoggerName(logger.getName());
  lr.setSourceClassName(className);
  lr.setSourceMethodName(methodName);
  lr.setParameters(args);
  lr.setThrown(thrown);
  logger.log(lr);
    }

    /**
     * Add a service to our state.  This includes putting it in the
     * serviceByID map under the serviceID, in the serviceByTime map,
     * in the serviceByType map under the service's most-specific
     * ServiceType, and in the serviceByAttr map under all of its
     * attribute values, incrementing the number of instances of each
     * EntryClass, and updating entryClasses as necessary.  If this is
     * the first instance of that ServiceType registered, then we need
     * to add concrete class information to the type and all supertypes.
     */
    private void addService(SvcReg reg) {
  serviceByID.put(reg.item.serviceID, reg);
  serviceByTime.put(reg, reg);
  addServiceByTypes(reg.item.serviceType, reg);
  EntryRep[] entries = reg.item.attributeSets;
  for (int i = entries.length; --i >= 0; ) {
      addAttrs(reg, entries[i]);
  }
  computeMaxLeases();
    }

    /**
     * Delete given service, generating events as necessary.  This includes
     * deleting from the serviceByID, serviceByTime, serviceByType, and
     * serviceByAttr maps, decrementing the number of instances of each
     * EntryClass, and updating entryClasses as necessary.  If this is the
     * last registered instance of the service type, then we delete the
     * concrete class information from the type and all supertypes.
     */
    private void deleteService(SvcReg reg, long now) {
  Item item = reg.item;
  generateEvents(item, null, now);
  serviceByID.remove(item.serviceID);
  serviceByTime.remove(reg);
  deleteServiceFromTypes(item.serviceType, reg);
  EntryRep[] entries = item.attributeSets;
  for (int i = entries.length; --i >= 0; ) {
      deleteAttrs(reg, entries[i], false);
  }
  computeMaxLeases();
    }

    /**
     * Add an event registration to our state.  This includes adding a
     * template of each EntryClass, putting the registration in the
     * eventByID map, in the eventByTime map, and in either
     * subEventByService (if the template is for a specific service id)
     * or subEventByID.  Since we expect in most cases there will only
     * ever be a single event registration for a given service id, we
     * avoid creating a singleton array in that case.
     */
    private void addEvent(EventReg reg) {
  if (reg.listener == null)
      return; /* failed to recover from log */
  EntryRep[] tmpls = reg.tmpl.attributeSetTemplates;
  if (tmpls != null) {
      for (int i = tmpls.length; --i >= 0; ) {
    EntryClass eclass = tmpls[i].eclass;
    eclass.setNumTemplates(eclass.getNumTemplates() + 1);
      }
  }
  Long id = new Long(reg.eventID);
  eventByID.put(id, reg);
  eventByTime.put(reg, reg);
  if (reg.tmpl.serviceID != null) {
      Object val = subEventByService.get(reg.tmpl.serviceID);
      if (val == null)
    val = reg;
      else if (val instanceof EventReg)
    val = new EventReg[]{(EventReg)val, reg};
      else
    val = arrayAdd((EventReg[])val, reg);
      subEventByService.put(reg.tmpl.serviceID, val);
  } else {
      subEventByID.put(id, reg);
  }
  computeMaxLeases();
    }

    /**
     * Remove an event registration from our state.  This includes deleting
     * a template of each EntryClass, deleting the registration from the
     * eventByID map, the eventByTime map, and either the subEventByService
     * or subEventByID map.
     */
    private void deleteEvent(EventReg reg) {
  EntryRep[] tmpls = reg.tmpl.attributeSetTemplates;
  if (tmpls != null) {
      for (int i = tmpls.length; --i >= 0; ) {
    EntryClass eclass = tmpls[i].eclass;
    eclass.setNumTemplates(eclass.getNumTemplates() - 1);
      }
  }
  Long id = new Long(reg.eventID);
  eventByID.remove(id);
  eventByTime.remove(reg);
  if (reg.tmpl.serviceID != null) {
      Object val = subEventByService.get(reg.tmpl.serviceID);
      if (val == reg) {
    subEventByService.remove(reg.tmpl.serviceID);
      } else {
    Object[] array = (EventReg[])val;
    array = arrayDel(array, indexOf(array, reg));
    if (array.length == 1)
        val = array[0];
    else
        val = array;
    subEventByService.put(reg.tmpl.serviceID, val);
      }
  } else {
      subEventByID.remove(id);
  }
  computeMaxLeases();
    }

    /**
     * Put the service in the serviceByAttr map under all attribute values
     * in the given entry, or in the serviceByEmptyAttr map if the entry
     * has no attributes, add a new instance of the EntryClass, and update
     * entryClasses as necessary.
     */
    private void addAttrs(SvcReg reg, EntryRep entry) {
  EntryClass eclass = entry.eclass;
  addInstance(eclass);
  Object[] fields = entry.fields;
  if (fields.length > 0) {
      /* walk backwards to make getDefiningClass more efficient */
      for (int i = fields.length; --i >= 0; ) {
    eclass = getDefiningClass(eclass, i);
    addAttr(reg, eclass, i, fields[i]);
      }
      return;
  }
  ArrayList regs = (ArrayList)serviceByEmptyAttr.get(eclass);
  if (regs == null) {
      regs = new ArrayList(2);
      regs.add(reg);
      serviceByEmptyAttr.put(eclass, regs);
  } else if (!regs.contains(reg)) {
      regs.add(reg);
  }
    }

    /**
     * If checkDups is false, delete the service (if present) from
     * serviceByAttr under all attribute values of the given entry or
     * from serviceByEmptyAttr if the entry has no attributes.
     * If checkDups is true, only delete for a given attribute value if the
     * service has no other entries of similar type that still have the
     * same value.  Either way, delete an instance of the EntryClass,
     * and update entryClasses as necessary.
     */
    private void deleteAttrs(SvcReg reg, EntryRep entry, boolean checkDups) {
  EntryClass eclass = entry.eclass;
  deleteInstance(eclass);
  Object[] fields = entry.fields;
  if (fields.length == 0) {
      ArrayList regs = (ArrayList)serviceByEmptyAttr.get(eclass);
      if (regs == null || (checkDups && hasEmptyAttr(reg, eclass)))
    return;
      int idx = regs.indexOf(reg);
      if (idx >= 0) {
    regs.remove(idx);
    if (regs.isEmpty())
        serviceByEmptyAttr.remove(eclass);
      }
      return;
  }
  /* walk backwards to make getDefiningClass more efficient */
  for (int fldidx = fields.length; --fldidx >= 0; ) {
      eclass = getDefiningClass(eclass, fldidx);
      HashMap[] attrMaps = (HashMap[])serviceByAttr.get(eclass);
      if (attrMaps == null ||
    attrMaps[fldidx] == null ||
    (checkDups && hasAttr(reg, eclass, fldidx, fields[fldidx])))
    continue;
      HashMap map = attrMaps[fldidx];
      Object value = fields[fldidx];
      ArrayList regs = (ArrayList)map.get(value);
      if (regs == null)
    continue;
      int idx = regs.indexOf(reg);
      if (idx < 0)
    continue;
      regs.remove(idx);
      if (!regs.isEmpty())
    continue;
      map.remove(value);
      if (!map.isEmpty())
    continue;
      attrMaps[fldidx] = null;
      if (allNull(attrMaps))
    serviceByAttr.remove(eclass);
  }
    }

    /**
     * Store all non-null elements of values into the given entry,
     * and update serviceByAttr to match.
     */
    private void updateAttrs(SvcReg reg, EntryRep entry, Object[] values)
    {
  EntryClass eclass = entry.eclass;
  /* walk backwards to make getDefiningClass more efficient */
  for (int fldidx = values.length; --fldidx >= 0; ) {
      Object oval = entry.fields[fldidx];
      Object nval = values[fldidx];
      if (nval != null && !nval.equals(oval)) {
    eclass = getDefiningClass(eclass, fldidx);
    HashMap map = addAttr(reg, eclass, fldidx, nval);
    entry.fields[fldidx] = nval;
    if (hasAttr(reg, eclass, fldidx, oval))
        continue;
    ArrayList regs = (ArrayList)map.get(oval);
    regs.remove(regs.indexOf(reg));
    if (regs.isEmpty())
        map.remove(oval); /* map cannot become empty */
      }
  }
    }

    /**
     * Put the service under the given attribute value for the given
     * defining class and field, if it isn't already there.  Return
     * the HashMap for the given class and field.
     */
    private HashMap addAttr(SvcReg reg,
          EntryClass eclass,
          int fldidx,
          Object value)
    {
  HashMap[] attrMaps = (HashMap[])serviceByAttr.get(eclass);
  if (attrMaps == null) {
      attrMaps = new HashMap[eclass.getNumFields()];
      serviceByAttr.put(eclass, attrMaps);
  }
  HashMap map = attrMaps[fldidx];
  if (map == null) {
      map = new HashMap(11);
      attrMaps[fldidx] = map;
  }
  ArrayList regs = (ArrayList)map.get(value);
  if (regs == null) {
      regs = new ArrayList(3);
      map.put(value, regs);
  } else if (regs.contains(reg))
      return map;
  regs.add(reg);
  return map;
    }

    /**
     * Add an instance of the EntryClass, and add the class to entryClasses
     * if this is the first such instance.
     */
     private void addInstance(EntryClass eclass) {
  int idx = entryClasses.indexOf(eclass);
  if (idx < 0) {
      entryClasses.add(eclass);
      idx = entryClasses.size() - 1;
  }
  eclass = (EntryClass) entryClasses.get(idx);
  eclass.setNumInstances(eclass.getNumInstances() + 1)
    }

    /**
     * Delete an instance of the EntryClass, and remove the class from
     * entryClasses if this is the last such instance.
     */
    private void deleteInstance(EntryClass eclass) {
  int idx = entryClasses.indexOf(eclass);
  eclass = (EntryClass) entryClasses.get(idx);
  int num = eclass.getNumInstances() - 1;
  if (num == 0)
      entryClasses.remove(idx);
  eclass.setNumInstances(num);
    }

    /** Return an appropriate iterator for Items matching the Template. */
    private ItemIter matchingItems(Template tmpl) {
  if (tmpl.serviceID != null)
      return new IDItemIter(tmpl);
  if (!isEmpty(tmpl.serviceTypes))
      return new SvcIterator(tmpl);
  EntryRep[] sets = tmpl.attributeSetTemplates;
  if (isEmpty(sets))
      return new AllItemIter();
  for (int i = sets.length; --i >= 0; ) {
      Object[] fields = sets[i].fields;
      if (fields.length == 0) {
    EntryClass eclass = getEmptyEntryClass(sets[i].eclass);
    if (eclass != null)
        return new EmptyAttrItemIter(tmpl, eclass);
      } else {
    /* try subclass fields before superclass fields */
    for (int j = fields.length; --j >= 0; ) {
        if (fields[j] != null)
      return new AttrItemIter(tmpl, i, j);
    }
      }
  }
  return new ClassItemIter(tmpl);
    }

    /**
     * Return member of entryClasses that is equal to or a subclass of
     * the specified class, provided there is exactly one such member
     * and it has no fields.
     */
    private EntryClass getEmptyEntryClass(EntryClass eclass) {
  EntryClass match = null;
  for (int i = entryClasses.size(); --i >= 0; ) {
      EntryClass cand = (EntryClass)entryClasses.get(i);
      if (eclass.isAssignableFrom(cand)) {
    if (cand.getNumFields() != 0 || match != null)
        return null;
    match = cand;
      }
  }
  return match;
    }

    /** Returns a list of services that match all types passed in */
    private ArrayList matchingServices(ServiceType[] types) {
  ArrayList matches = new ArrayList();
  if (isEmpty(types)) {
      Map map = (Map) serviceByTypeName.get(objectServiceType.getName());
      matches.addAll(map.values());
  } else {
      Map map = (Map) serviceByTypeName.get(types[0].getName());
      if (map != null)
          matches.addAll(map.values());
      if (types.length > 1) {
          for (Iterator it = matches.iterator(); it.hasNext(); ) {
        SvcReg reg = (SvcReg) it.next();
        if (!matchType(types, reg.item.serviceType))
            it.remove();
          }
      }
  }
  return matches;
    }

    /** Return any valid codebase for an entry class that has instances. */
    private String pickCodebase(EntryClass eclass, long now)
  throws ClassNotFoundException
    {
  if (eclass.getNumFields() == 0)
      return pickCodebase(eclass,
        (ArrayList)serviceByEmptyAttr.get(eclass),
        now);
  int fldidx = eclass.getNumFields() - 1;
  HashMap[] attrMaps =
      (HashMap[])serviceByAttr.get(getDefiningClass(eclass, fldidx));
  for (Iterator iter = attrMaps[fldidx].values().iterator();
       iter.hasNext(); )
  {
      try {
    return pickCodebase(eclass, (ArrayList)iter.next(), now);
      } catch (ClassNotFoundException e) {
      }
  }
  throw new ClassNotFoundException();
    }

    /** Return any valid codebase for an entry of the exact given class. */
    private String pickCodebase(EntryClass eclass, ArrayList svcs, long now)
  throws ClassNotFoundException
    {
  for (int i = svcs.size(); --i >= 0; ) {
      SvcReg reg = (SvcReg)svcs.get(i);
      if (reg.leaseExpiration <= now)
    continue;
      EntryRep[] sets = reg.item.attributeSets;
      for (int j = sets.length; --j >= 0; ) {
    if (eclass.equals(sets[j].eclass))
        return sets[j].codebase;
      }
  }
  throw new ClassNotFoundException();
    }

    /**
     * Compute new maxServiceLease and maxEventLease values.  This needs to
     * be called whenever the number of services (#S) or number of events
     * (#E) changes, or whenever any of the configuration parameters change.
     * The two base equations driving the computation are:
     *     #S/maxServiceLease + #E/maxEventLease <= 1/minRenewalInterval
     *     maxServiceLease/maxEventLease = minMaxServiceLease/minMaxEventLease
     */
    private void computeMaxLeases() {
  if (inRecovery)
      return;
  maxServiceLease =
      Math.max(minMaxServiceLease,
         minRenewalInterval *
         (serviceByID.size() +
          ((eventByID.size() * minMaxServiceLease) /
           minMaxEventLease)));
  maxEventLease = Math.max(minMaxEventLease,
         ((maxServiceLease * minMaxEventLease) /
          minMaxServiceLease));
    }

    /** Process a unicast discovery request, and respond. */
    private void respond(Socket socket) throws Exception {
  try {
      try {
    socket.setTcpNoDelay(true);
    socket.setKeepAlive(true);   
      } catch (SocketException e) {
    if (logger.isLoggable(Levels.HANDLED))
        logger.log(Levels.HANDLED,
             "problem setting socket options", e);
      }
      socket.setSoTimeout(
         unicastDiscoveryConstraints.getUnicastSocketTimeout(
             DEFAULT_SOCKET_TIMEOUT));
      int pv = new DataInputStream(socket.getInputStream()).readInt();
      unicastDiscoveryConstraints.checkProtocolVersion(pv);
      getDiscovery(pv).handleUnicastDiscovery(
    new UnicastResponse(myLocator.getHost(),
            myLocator.getPort(),
            memberGroups,
            proxy),
    socket,
    unicastDiscoveryConstraints.getUnfulfilledConstraints(),
    unicastDiscoverySubjectChecker,
    Collections.EMPTY_LIST)
  } finally {
      try {
    socket.close();
      } catch (IOException e) {
    logger.log(Levels.HANDLED, "exception closing socket", e);
      }
  }
    }

    /** Returns Discovery instance implementing the given protocol version */
    private Discovery getDiscovery(int version)
  throws DiscoveryProtocolException
    {
  switch (version) {
      case Discovery.PROTOCOL_VERSION_1:
    return Discovery.getProtocol1();
      case Discovery.PROTOCOL_VERSION_2:
    return protocol2;
      default:
    throw new DiscoveryProtocolException(
        "unsupported protocol version: " + version);
  }
    }

    /** Close any sockets that were sitting in the task queue. */
    private void closeRequestSockets(ArrayList tasks) {
  for (int i = tasks.size(); --i >= 0; ) {
      Object obj = tasks.get(i);
      if (obj instanceof SocketTask) {
    try {
        ((SocketTask)obj).socket.close();
    } catch (IOException e) {
    }
      }
  }
    }

    /** Post-login (if login configured) initialization. */
    private void init(Configuration config,
          ActivationID activationID,
          boolean persistent,
          LifeCycle lifeCycle)
  throws IOException, ConfigurationException, ActivationException
    {
  this.lifeCycle = lifeCycle;

  /* persistence-specific initialization */
  if (persistent) {
      persistenceSnapshotThreshold = Config.getIntEntry(
    config, COMPONENT, "persistenceSnapshotThreshold",
    persistenceSnapshotThreshold, 0, Integer.MAX_VALUE);
      String persistenceDirectory = (String) config.getEntry(
    COMPONENT, "persistenceDirectory", String.class);
      recoveredListenerPreparer = (ProxyPreparer) Config.getNonNullEntry(
    config, COMPONENT, "recoveredListenerPreparer",
    ProxyPreparer.class, recoveredListenerPreparer);
      recoveredLocatorPreparer = (ProxyPreparer) Config.getNonNullEntry(
    config, COMPONENT, "recoveredLocatorPreparer",
    ProxyPreparer.class, recoveredLocatorPreparer);
      persistenceSnapshotWeight = Config.getFloatEntry(
    config, COMPONENT, "persistenceSnapshotWeight",
    persistenceSnapshotWeight, 0F, Float.MAX_VALUE);

      log = new ReliableLog(persistenceDirectory, new LocalLogHandler());
      if (logger.isLoggable(Level.CONFIG)) {
    logger.log(Level.CONFIG, "using persistence directory {0}",
         new Object[]{ persistenceDirectory });
      }
      inRecovery = true;
      log.recover();
      inRecovery = false;
  } else {
      log = null;
  }

  /* activation-specific initialization */
  if (activationID != null) {
      ProxyPreparer activationIdPreparer = (ProxyPreparer)
    Config.getNonNullEntry(
        config, COMPONENT, "activationIdPreparer",
        ProxyPreparer.class, new BasicProxyPreparer());
      ProxyPreparer activationSystemPreparer = (ProxyPreparer)
    Config.getNonNullEntry(
        config, COMPONENT, "activationSystemPreparer",
        ProxyPreparer.class, new BasicProxyPreparer());

      this.activationID = (ActivationID)
    activationIdPreparer.prepareProxy(activationID);
      activationSystem = (ActivationSystem)
    activationSystemPreparer.prepareProxy(
        ActivationGroup.getSystem());

      serverExporter = (Exporter) Config.getNonNullEntry(
    config, COMPONENT, "serverExporter", Exporter.class,
    new ActivationExporter(
        this.activationID,
        new BasicJeriExporter(
      TcpServerEndpoint.getInstance(0),
      new BasicILFactory())),
    this.activationID);
  } else {
      this.activationID = null;
      activationSystem = null;

      serverExporter = (Exporter) Config.getNonNullEntry(
    config, COMPONENT, "serverExporter", Exporter.class,
    new BasicJeriExporter(
        TcpServerEndpoint.getInstance(0),
        new BasicILFactory()));
  }

  /* fetch "initial*" config entries, if first time starting up */
  if (!recoveredSnapshot) {
      Entry[] initialLookupAttributes = (Entry[]) config.getEntry(
    COMPONENT, "initialLookupAttributes", Entry[].class,
    new Entry[0]);
      lookupGroups = (String[]) config.getEntry(
    COMPONENT, "initialLookupGroups", String[].class,
    lookupGroups);
      lookupLocators = (LookupLocator[]) config.getEntry(
    COMPONENT, "initialLookupLocators", LookupLocator[].class,
    lookupLocators);
      memberGroups = (String[]) config.getEntry(
    COMPONENT, "initialMemberGroups", String[].class,
    memberGroups);
      if (memberGroups == null) {
    throw new ConfigurationException(
        "member groups cannot be ALL_GROUPS (null)");
      }
      memberGroups = (String[]) removeDups(memberGroups);
      unicastPort = Config.getIntEntry(
    config, COMPONENT, "initialUnicastDiscoveryPort",
    unicastPort, 0, 0xFFFF);

      if (initialLookupAttributes != null &&
    initialLookupAttributes.length > 0)
      {
    List l = new ArrayList(Arrays.asList(baseAttrs));
    l.addAll(Arrays.asList(initialLookupAttributes));
    lookupAttrs = (Entry[]) l.toArray(new Entry[l.size()]);
      } else {
    lookupAttrs = baseAttrs;
      }
  }

  /* fetch remaining config entries */
  MethodConstraints discoveryConstraints =
      (MethodConstraints) config.getEntry(COMPONENT,
            "discoveryConstraints",
            MethodConstraints.class, null);
  if (discoveryConstraints == null) {
      discoveryConstraints =
    new BasicMethodConstraints(InvocationConstraints.EMPTY);
  }
  try {
      discoer = (DiscoveryManagement) config.getEntry(
    COMPONENT, "discoveryManager", DiscoveryManagement.class);
  } catch (NoSuchEntryException e) {
      discoer = new LookupDiscoveryManager(
    DiscoveryGroupManagement.NO_GROUPS, null, null, config);
  }
  listenerPreparer = (ProxyPreparer) Config.getNonNullEntry(
      config, COMPONENT, "listenerPreparer", ProxyPreparer.class,
      listenerPreparer);
  locatorPreparer = (ProxyPreparer) Config.getNonNullEntry(
      config, COMPONENT, "locatorPreparer", ProxyPreparer.class,
      locatorPreparer);
  minMaxEventLease = Config.getLongEntry(
      config, COMPONENT, "minMaxEventLease",
      minMaxEventLease, 1, MAX_LEASE);
  minMaxServiceLease = Config.getLongEntry(
      config, COMPONENT, "minMaxServiceLease",
      minMaxServiceLease, 1, MAX_LEASE);
  minRenewalInterval = Config.getLongEntry(
      config, COMPONENT, "minRenewalInterval",
      minRenewalInterval, 0, MAX_RENEW);
  multicastAnnouncementInterval = Config.getLongEntry(
      config, COMPONENT, "multicastAnnouncementInterval",
      multicastAnnouncementInterval, 1, Long.MAX_VALUE);

  multicastInterfaceRetryInterval = Config.getIntEntry(
      config, COMPONENT, "multicastInterfaceRetryInterval",
      multicastInterfaceRetryInterval, 1, Integer.MAX_VALUE);
  try {
      multicastInterfaces = (NetworkInterface[]) config.getEntry(
    COMPONENT, "multicastInterfaces", NetworkInterface[].class);
      multicastInterfacesSpecified = true;
  } catch (NoSuchEntryException e) {
      Enumeration en = NetworkInterface.getNetworkInterfaces();
      List l = (en != null) ?
    Collections.list(en) : Collections.EMPTY_LIST;
      multicastInterfaces = (NetworkInterface[])
    l.toArray(new NetworkInterface[l.size()]);
      multicastInterfacesSpecified = false;
  }
  if (multicastInterfaces == null) {
      logger.config("using system default interface for multicast");
  } else if (multicastInterfaces.length == 0) {
      if (multicastInterfacesSpecified) {
    logger.config("multicast disabled");
      } else {
    logger.severe("no network interfaces detected");
      }
  } else if (logger.isLoggable(Level.CONFIG)) {
      logger.log(Level.CONFIG, "multicasting on interfaces {0}",
           new Object[]{ Arrays.asList(multicastInterfaces) });
  }

  try {
      multicastRequestSubjectChecker =
    (ClientSubjectChecker) Config.getNonNullEntry(
        config, COMPONENT, "multicastRequestSubjectChecker",
        ClientSubjectChecker.class);
  } catch (NoSuchEntryException e) {
      // leave null
  }
  resourceIdGenerator = (UuidGenerator) Config.getNonNullEntry(
      config, COMPONENT, "resourceIdGenerator", UuidGenerator.class,
      resourceIdGenerator);
  serviceIdGenerator = (UuidGenerator) Config.getNonNullEntry(
      config, COMPONENT, "serviceIdGenerator", UuidGenerator.class,
      serviceIdGenerator);
  tasker = (TaskManager) Config.getNonNullEntry(
      config, COMPONENT, "taskManager", TaskManager.class,
      new TaskManager(50, 1000 * 15, 1.0F));
  unexportTimeout = Config.getLongEntry(
         config, COMPONENT, "unexportTimeout", unexportTimeout,
         0, Long.MAX_VALUE);
  unexportWait = Config.getLongEntry(
         config, COMPONENT, "unexportWait", unexportWait,
         0, Long.MAX_VALUE);
  String unicastDiscoveryHost;
  try {
      unicastDiscoveryHost = (String) Config.getNonNullEntry(
    config, COMPONENT, "unicastDiscoveryHost", String.class);
  } catch (NoSuchEntryException e) {
      // fix for 4906732: only invoke getCanonicalHostName if needed
      unicastDiscoveryHost =
    InetAddress.getLocalHost().getCanonicalHostName();
  }
  try {
      unicastDiscoverySubjectChecker =
    (ClientSubjectChecker) Config.getNonNullEntry(
        config, COMPONENT, "unicastDiscoverySubjectChecker",
        ClientSubjectChecker.class);
  } catch (NoSuchEntryException e) {
      // leave null
  }

  /* initialize state based on recovered/configured values */
  objectServiceType = new ServiceType(Object.class, null, null);
  computeMaxLeases();
  protocol2 = Discovery.getProtocol2(null);
  /* cache unprocessed unicastDiscovery constraints to handle
           reprocessing of time constraints associated with that method */
  rawUnicastDiscoveryConstraints = discoveryConstraints.getConstraints(
         DiscoveryConstraints.unicastDiscoveryMethod);   
  multicastRequestConstraints = DiscoveryConstraints.process(
      discoveryConstraints.getConstraints(
    DiscoveryConstraints.multicastRequestMethod));
  multicastAnnouncementConstraints = DiscoveryConstraints.process(
      discoveryConstraints.getConstraints(
    DiscoveryConstraints.multicastAnnouncementMethod));
  unicastDiscoveryConstraints = DiscoveryConstraints.process(
      rawUnicastDiscoveryConstraints);
  serviceExpirer = new ServiceExpireThread();
  eventExpirer = new EventExpireThread();
  unicaster = new UnicastThread(unicastPort);
  multicaster = new MulticastThread();
  announcer = new AnnounceThread();
  snapshotter = new SnapshotThread();
  if (myServiceID == null) {
      myServiceID = newServiceID();
  }
  myRef = (Registrar) serverExporter.export(this);
  proxy = RegistrarProxy.getInstance(myRef, myServiceID);
  myLocator = (proxy instanceof RemoteMethodControl) ?
      new ConstrainableLookupLocator(
    unicastDiscoveryHost, unicaster.port, null) :
      new LookupLocator(unicastDiscoveryHost, unicaster.port);
  /* register myself */
  Item item = new Item(new ServiceItem(myServiceID,
               proxy,
               lookupAttrs));
  SvcReg reg = new SvcReg(item, myLeaseID, Long.MAX_VALUE);
  addService(reg);
  if (log != null) {
      log.snapshot();
  }

  try {
      DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
      String[] groups = dgm.getGroups();
      if (groups == null || groups.length > 0) {
    throw new ConfigurationException(
        "discoveryManager must be initially configured with " +
        "no groups");
      }
      DiscoveryLocatorManagement dlm =
    (DiscoveryLocatorManagement) discoer;
      if (dlm.getLocators().length > 0) {
    throw new ConfigurationException(
        "discoveryManager must be initially configured with " +
        "no locators");
      }
      dgm.setGroups(lookupGroups);
      dlm.setLocators(lookupLocators);
  } catch (ClassCastException e) {
      throw new ConfigurationException(null, e);
  }
  joiner = new JoinManager(proxy, lookupAttrs, myServiceID,
         discoer, null, config);

  /* start up all the daemon threads */
  serviceExpirer.start();
  eventExpirer.start();
  unicaster.start();
  multicaster.start();
  announcer.start();
        snapshotter.start();
  if (logger.isLoggable(Level.INFO)) {
      logger.log(Level.INFO, "started Reggie: {0}, {1}, {2}",
           new Object[]{ myServiceID,
             Arrays.asList(memberGroups),
             myLocator });
  }
  ready.ready();
    }

    /** The code that does the real work of register. */
    private ServiceRegistration registerDo(Item nitem, long leaseDuration)
    {
  if (nitem.service == null)
      throw new NullPointerException("null service");
  if (myServiceID.equals(nitem.serviceID))
      throw new IllegalArgumentException("reserved service id");
  if (nitem.attributeSets == null)
      nitem.attributeSets = emptyAttrs;
  else
      nitem.attributeSets = (EntryRep[])removeDups(nitem.attributeSets);
  leaseDuration = limitDuration(leaseDuration, maxServiceLease);
  long now = System.currentTimeMillis();
  if (nitem.serviceID == null) {
      /* new service, match on service object */
      Map svcs = (Map)serviceByTypeName.get(nitem.serviceType.getName());
      if (svcs != null) {
    for (Iterator it = svcs.values().iterator(); it.hasNext(); ) {
        SvcReg reg = (SvcReg)it.next();
        if (nitem.service.equals(reg.item.service)) {
      nitem.serviceID = reg.item.serviceID;
      deleteService(reg, now);
      break;
        }
    }
      }
      if (nitem.serviceID == null)
    nitem.serviceID = newServiceID();
  } else {
      /* existing service, match on service id */
      SvcReg reg = (SvcReg)serviceByID.get(nitem.serviceID);
      if (reg != null)
    deleteService(reg, now);
  }
  Util.checkRegistrantServiceID(nitem.serviceID, logger, Level.FINE);
  SvcReg reg = new SvcReg(nitem, newLeaseID(), now + leaseDuration);
  addService(reg);
  generateEvents(null, nitem, now);
  addLogRecord(new SvcRegisteredLogObj(reg));
  queueEvents();
  /* see if the expire thread needs to wake up earlier */
  if (reg.leaseExpiration < minSvcExpiration) {
      minSvcExpiration = reg.leaseExpiration;
      concurrentObj.waiterNotify(serviceNotifier);
  }
  return Registration.getInstance(
      myRef,
      ServiceLease.getInstance(
    myRef,
    myServiceID,
    nitem.serviceID,
    reg.leaseID,
    reg.leaseExpiration));
    }

   /**
    * The code that does the real work of lookup.  As a special case,
    * if the template specifies at least one service type to match,
    * and there are multiple items that match the template, then we
    * make a random pick among them, in an attempt to load balance
    * use of "equivalent" services and avoid starving any of them.
    */
    private MarshalledWrapper lookupDo(Template tmpl)
    {
  if (isEmpty(tmpl.serviceTypes) || tmpl.serviceID != null)
  {
      ItemIter iter = matchingItems(tmpl);
      if (iter.hasNext())
    return iter.next().service;
      return null;

  }
  List services = matchingServices(tmpl.serviceTypes);
  long now = System.currentTimeMillis();
  int slen = services.size();
  if (slen == 0)
      return null;
  int srand = Math.abs(random.nextInt()) % slen;
  for (int i = 0; i < slen; i++) {
      SvcReg reg = (SvcReg)services.get((i + srand) % slen);
      if (reg.leaseExpiration > now && matchAttributes(tmpl, reg.item))
        return reg.item.service;
  }
  return null;
    }

    /**
     * The code that does the real work of lookup.  We do a deep copy of the
     * items being returned, both to avoid having them modified while being
     * marshalled (by a concurrent update method), and to substitute
     * replacements for embedded EntryClass and ServiceType instances, to
     * minimize data sent back to the client.  If duplicates are possible
     * from the iterator, we save all matches, weeding out duplicates as we
     * go, then trim to maxMatches and deep copy.
     */
    private Matches lookupDo(Template tmpl, int maxMatches)
    {
  if (maxMatches < 0)
      throw new IllegalArgumentException("negative maxMatches");
  int totalMatches = 0;
  ArrayList matches = null;
  ItemIter iter = matchingItems(tmpl);
  if (maxMatches > 0 || iter.dupsPossible)
      matches = new ArrayList();
  if (iter.dupsPossible) {
      while (iter.hasNext()) {
    Item item = iter.next();
    if (!matches.contains(item))
        matches.add(item);
      }
      totalMatches = matches.size();
      if (maxMatches > 0) {
    for (int i = matches.size(); --i >= maxMatches; )
        matches.remove(i);
    for (int i = matches.size(); --i >= 0; ) {
        matches.set(i, copyItem((Item)matches.get(i)));
    }
      } else {
    matches = null;
      }
  } else {
      while (iter.hasNext()) {
    Item item = iter.next();
    totalMatches++;
    if (--maxMatches >= 0)
        matches.add(copyItem(item));
      }
  }
  return new Matches(matches, totalMatches);
    }

    /**
     * The code that does the real work of notify.
     * Every registration is given a unique event id.  The event id
     * can thus also serve as a lease id.
     *
     */
    private EventRegistration notifyDo(Template tmpl,
               int transitions,
               RemoteEventListener listener,
               MarshalledObject handback,
               long leaseDuration)
  throws RemoteException
    {
  if (transitions == 0 ||
      transitions !=
      (transitions & (ServiceRegistrar.TRANSITION_MATCH_NOMATCH |
          ServiceRegistrar.TRANSITION_NOMATCH_MATCH |
          ServiceRegistrar.TRANSITION_MATCH_MATCH)))
      throw new IllegalArgumentException("invalid transitions");
  if (listener == null)
      throw new NullPointerException("listener");
  listener =
      (RemoteEventListener) listenerPreparer.prepareProxy(listener);
  leaseDuration = limitDuration(leaseDuration, maxEventLease);
  long now = System.currentTimeMillis();
  EventReg reg = new EventReg(eventID, newLeaseID(), tmpl, transitions,
            listener, handback, now + leaseDuration);
  eventID++;
  addEvent(reg);
  addLogRecord(new EventRegisteredLogObj(reg));
  /* see if the expire thread needs to wake up earlier */
  if (reg.leaseExpiration < minEventExpiration) {
      minEventExpiration = reg.leaseExpiration;
      concurrentObj.waiterNotify(eventNotifier);
  }
  return new EventRegistration(
      reg.eventID,
      proxy,
      EventLease.getInstance(
    myRef,
    myServiceID,
    reg.eventID,
    reg.leaseID,
    reg.leaseExpiration),
      reg.seqNo);
    }

    /**
     * The code that does the real work of getEntryClasses. If the
     * template is empty, then we can just use entryClasses, without
     * having to iterate over items, but we have to work harder to
     * get codebases.
     */
    private EntryClassBase[] getEntryClassesDo(Template tmpl)
    {
  ArrayList classes = new ArrayList();
  ArrayList codebases = new ArrayList();
  if (tmpl.serviceID == null &&
      isEmpty(tmpl.serviceTypes) &&
      isEmpty(tmpl.attributeSetTemplates)) {
      long now = System.currentTimeMillis();
      for (int i = entryClasses.size(); --i >= 0; ) {
    EntryClass eclass = (EntryClass)entryClasses.get(i);
    try {
        codebases.add(pickCodebase(eclass, now));
        classes.add(eclass);
    } catch (ClassNotFoundException e) {
    }
      }
  } else {
      for (ItemIter iter = matchingItems(tmpl); iter.hasNext(); ) {
    Item item = iter.next();
    for (int i = item.attributeSets.length; --i >= 0; ) {
        EntryRep attrSet = item.attributeSets[i];
        if (attrMatch(tmpl.attributeSetTemplates, attrSet) &&
      !classes.contains(attrSet.eclass)) {
      classes.add(attrSet.eclass);
      codebases.add(attrSet.codebase);
        }
    }
      }
  }
  if (classes.isEmpty())
      return null; /* spec says null */
  EntryClassBase[] vals = new EntryClassBase[classes.size()];
  for (int i = vals.length; --i >= 0; ) {
      vals[i] = new EntryClassBase(
        ((EntryClass)classes.get(i)).getReplacement(),
        (String)codebases.get(i));
  }
  return vals;
    }

    /**
     * The code that does the real work of getFieldValues.  If the
     * template is just a singleton entry with all null fields, then
     * we can do a faster computation by iterating over keys in the
     * given attribute's serviceByAttr map, rather than iterating
     * over items.
     */
    private Object[] getFieldValuesDo(Template tmpl, int setidx, int fldidx)
    {
  ArrayList values = new ArrayList();
  EntryRep etmpl = tmpl.attributeSetTemplates[setidx];
  if (tmpl.serviceID == null &&
      isEmpty(tmpl.serviceTypes) &&
      tmpl.attributeSetTemplates.length == 1 &&
      allNull(etmpl.fields))
  {
      long now = System.currentTimeMillis();
      EntryClass eclass = getDefiningClass(etmpl.eclass, fldidx);
      boolean checkAttr = !eclass.equals(etmpl.eclass);
      HashMap[] attrMaps = (HashMap[])serviceByAttr.get(eclass);
      if (attrMaps != null && attrMaps[fldidx] != null) {
    for (Iterator iter = attrMaps[fldidx].entrySet().iterator();
         iter.hasNext(); )
    {
        Map.Entry ent = (Map.Entry)iter.next();
        ArrayList regs = (ArrayList)ent.getValue();
        Object value = ent.getKey();
        for (int i = regs.size(); --i >= 0; ) {
      SvcReg reg = (SvcReg)regs.get(i);
      if (reg.leaseExpiration > now &&
          (!checkAttr ||
           hasAttr(reg, etmpl.eclass, fldidx, value))) {
          values.add(value);
          break;
      }
        }
    }
      }
  } else {
      for (ItemIter iter = matchingItems(tmpl); iter.hasNext(); ) {
    Item item = iter.next();
    for (int j = item.attributeSets.length; --j >= 0; ) {
        if (matchEntry(etmpl, item.attributeSets[j])) {
      Object value = item.attributeSets[j].fields[fldidx];
      if (!values.contains(value))
          values.add(value);
        }
    }
      }
  }
  if (values.isEmpty())
      return null;
  return values.toArray();
    }

    /**
     * The code that does the real work of getServiceTypes.  If the
     * template has at most service types, then we can do a fast
     * computation based solely on concrete classes, without having
     * to iterate over items, but we have to work a bit harder to
     * get codebases.
     */
    private ServiceTypeBase[] getServiceTypesDo(Template tmpl, String prefix)
    {
  ArrayList classes = new ArrayList();
  ArrayList codebases = new ArrayList();
  if (tmpl.serviceID == null && isEmpty(tmpl.attributeSetTemplates)) {
      List services = matchingServices(tmpl.serviceTypes);
      for (Iterator it = services.iterator(); it.hasNext(); ) {
    Item item = ((SvcReg)it.next()).item;
    addTypes(classes, codebases, tmpl.serviceTypes, prefix,
       item.serviceType, item.codebase);
      }
  } else {
      for (ItemIter iter = matchingItems(tmpl); iter.hasNext(); ) {
    Item item = iter.next();
    addTypes(classes, codebases, tmpl.serviceTypes, prefix,
       item.serviceType, item.codebase);
      }
  }
  if (classes.isEmpty())
      return null; /* spec says null */
  ServiceTypeBase[] vals = new ServiceTypeBase[classes.size()];
  for (int i = vals.length; --i >= 0; ) {
      vals[i] = new ServiceTypeBase(
             ((ServiceType)classes.get(i)).getReplacement(),
             (String)codebases.get(i));
  }
  return vals;
    }

    /**
     * The code that does the real work of addAttributes.
     * Add every element of attrSets to item, updating serviceByAttr as
     * necessary, incrementing the number of EntryClass instances, and
     * updating entryClasses as necessary.
     */
    private void addAttributesDo(ServiceID serviceID,
         Uuid leaseID,
         EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  long now = System.currentTimeMillis();
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  Item pre = (Item)reg.item.clone();
  EntryRep[] sets = reg.item.attributeSets;
  int i = 0;
  /* don't add if it's a duplicate */
  for (int j = 0; j < attrSets.length; j++) {
      EntryRep set = attrSets[j];
      if (indexOf(sets, set) < 0 && indexOf(attrSets, j, set) < 0) {
    attrSets[i++] = set;
    addAttrs(reg, set);
      }
  }
  if (i > 0) {
      int len = sets.length;
      EntryRep[] nsets = new EntryRep[len + i];
      System.arraycopy(sets, 0, nsets, 0, len);
      System.arraycopy(attrSets, 0, nsets, len, i);
      reg.item.attributeSets = nsets;
  }
  generateEvents(pre, reg.item, now);
    }

    /**
     * The code that does the real work of modifyAttributes.
     * Modify the attribute sets that match attrSetTmpls, updating
     * or deleting based on attrSets, updating serviceByAttr as necessary,
     * decrementing the number of EntryClass instances, and updating
     * entryClasses as necessary.
     */
    private void modifyAttributesDo(ServiceID serviceID,
            Uuid leaseID,
            EntryRep[] attrSetTmpls,
            EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  if (attrSetTmpls.length != attrSets.length)
      throw new IllegalArgumentException(
               "attribute set length mismatch");
  for (int i = attrSets.length; --i >= 0; ) {
      if (attrSets[i] != null &&
    !attrSets[i].eclass.isAssignableFrom(attrSetTmpls[i].eclass))
    throw new IllegalArgumentException(
             "attribute set type mismatch");
  }
  long now = System.currentTimeMillis();
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  Item pre = (Item)reg.item.clone();
  EntryRep[] preSets = pre.attributeSets;
  EntryRep[] sets = reg.item.attributeSets;
  for (int i = preSets.length; --i >= 0; ) {
      EntryRep preSet = preSets[i];
      EntryRep set = sets[i];
      for (int j = attrSetTmpls.length; --j >= 0; ) {
    if (matchEntry(attrSetTmpls[j], preSet)) {
        EntryRep attrs = attrSets[j];
        if (attrs == null) {
      sets = deleteSet(reg.item, i);
      deleteAttrs(reg, set, true);
      break;
        } else {
      updateAttrs(reg, set, attrs.fields);
        }
    }
      }
  }
  for (int i = sets.length; --i >= 0; ) {
      EntryRep set = sets[i];
      if (indexOf(sets, i, set) >= 0) {
    sets = deleteSet(reg.item, i);
    deleteInstance(set.eclass);
      }
  }
  reg.item.attributeSets = sets;
  generateEvents(pre, reg.item, now);
    }

    /**
     * The code that does the real work of setAttributes.
     * Replace all attributes of item with attrSets, updating serviceByAttr
     * as necessary, incrementing the number of EntryClass instances, and
     * updating entryClasses as necessary.
     */
    private void setAttributesDo(ServiceID serviceID,
         Uuid leaseID,
         EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  if (attrSets == null)
      attrSets = emptyAttrs;
  else
      attrSets = (EntryRep[])removeDups(attrSets);
  long now = System.currentTimeMillis();
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  Item pre = (Item)reg.item.clone();
  EntryRep[] entries = reg.item.attributeSets;
  for (int i = entries.length; --i >= 0; ) {
      deleteAttrs(reg, entries[i], false);
  }
  reg.item.attributeSets = attrSets;
  for (int i = attrSets.length; --i >= 0; ) {
      addAttrs(reg, attrSets[i]);
  }
  generateEvents(pre, reg.item, now);
    }

    /** The code that does the real work of cancelServiceLease. */
    private void cancelServiceLeaseDo(ServiceID serviceID, Uuid leaseID)
  throws UnknownLeaseException
    {
  if (serviceID.equals(myServiceID))
      throw new SecurityException("privileged service id");
  long now = System.currentTimeMillis();
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  deleteService(reg, now);
  /* wake up thread if this might be the (only) earliest time */
  if (reg.leaseExpiration == minSvcExpiration)
      concurrentObj.waiterNotify(serviceNotifier);
    }

    /** The code that does the real work of renewServiceLease. */
    private long renewServiceLeaseDo(ServiceID serviceID,
             Uuid leaseID,
             long renewDuration)
  throws UnknownLeaseException
    {
  long now = System.currentTimeMillis();
  long renewExpiration = renewServiceLeaseInt(serviceID, leaseID,
                renewDuration, now);
  addLogRecord(new ServiceLeaseRenewedLogObj(serviceID, leaseID,
               renewExpiration));
  return renewExpiration - now;
    }

    /** Renew a service lease for a relative duration from now. */
    private long renewServiceLeaseInt(ServiceID serviceID,
              Uuid leaseID,
              long renewDuration,
              long now)
  throws UnknownLeaseException
    {
  if (serviceID.equals(myServiceID))
      throw new SecurityException("privileged service id");
  if (renewDuration == Lease.ANY)
      renewDuration = maxServiceLease;
  else if (renewDuration < 0)
      throw new IllegalArgumentException("negative lease duration");
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  if (renewDuration > maxServiceLease &&
      renewDuration > reg.leaseExpiration - now)
      renewDuration = Math.max(reg.leaseExpiration - now,
             maxServiceLease);
  long renewExpiration = now + renewDuration;
  /* force a re-sort: must remove before changing, then reinsert */
  serviceByTime.remove(reg);
  reg.leaseExpiration = renewExpiration;
  serviceByTime.put(reg, reg);
  /* see if the expire thread needs to wake up earlier */
  if (renewExpiration < minSvcExpiration) {
      minSvcExpiration = renewExpiration;
      concurrentObj.waiterNotify(serviceNotifier);
  }
  return renewExpiration;
    }

    /** Renew the service lease for an absolute expiration time. */
    private void renewServiceLeaseAbs(ServiceID serviceID,
              Uuid leaseID,
              long renewExpiration)
    {
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null || !reg.leaseID.equals(leaseID))
      return;
  /* force a re-sort: must remove before changing, then reinsert */
  serviceByTime.remove(reg);
  reg.leaseExpiration = renewExpiration;
  serviceByTime.put(reg, reg);
    }

    /** The code that does the real work of cancelEventLease. */
    private void cancelEventLeaseDo(long eventID, Uuid leaseID)
  throws UnknownLeaseException
    {
  long now = System.currentTimeMillis();
  EventReg reg = (EventReg)eventByID.get(new Long(eventID));
  if (reg == null || reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  deleteEvent(reg);
  /* wake up thread if this might be the (only) earliest time */
  if (reg.leaseExpiration == minEventExpiration)
      concurrentObj.waiterNotify(eventNotifier);
    }

    /** The code that does the real work of renewEventLease. */
    private long renewEventLeaseDo(long eventID,
           Uuid leaseID,
           long renewDuration)
  throws UnknownLeaseException
    {
  long now = System.currentTimeMillis();
  long renewExpiration = renewEventLeaseInt(eventID, leaseID,
              renewDuration, now);
  addLogRecord(new EventLeaseRenewedLogObj(eventID, leaseID,
             renewExpiration));
  return renewExpiration - now;
    }

    private long renewEventLeaseInt(long eventID,
            Uuid leaseID,
            long renewDuration,
            long now)
  throws UnknownLeaseException
    {
  if (renewDuration == Lease.ANY)
      renewDuration = maxEventLease;
  else if (renewDuration < 0)
      throw new IllegalArgumentException("negative lease duration");
  EventReg reg = (EventReg)eventByID.get(new Long(eventID));
  if (reg == null ||
      !reg.leaseID.equals(leaseID) ||
      reg.leaseExpiration <= now)
      throw new UnknownLeaseException();
  if (renewDuration > maxEventLease &&
      renewDuration > reg.leaseExpiration - now)
      renewDuration = Math.max(reg.leaseExpiration - now, maxEventLease);
  long renewExpiration = now + renewDuration;
  /* force a re-sort: must remove before changing, then reinsert */
  eventByTime.remove(reg);
  reg.leaseExpiration = renewExpiration;
  eventByTime.put(reg, reg);
  /* see if the expire thread needs to wake up earlier */
  if (renewExpiration < minEventExpiration) {
      minEventExpiration = renewExpiration;
      concurrentObj.waiterNotify(eventNotifier);
  }
  return renewExpiration;
    }

    /** Renew the event lease for an absolute expiration time. */
    private void renewEventLeaseAbs(long eventID,
            Uuid leaseID,
            long renewExpiration)
    {
  EventReg reg = (EventReg)eventByID.get(new Long(eventID));
  if (reg == null || !reg.leaseID.equals(leaseID))
      return;
  /* force a re-sort: must remove before changing, then reinsert */
  eventByTime.remove(reg);
  reg.leaseExpiration = renewExpiration;
  eventByTime.put(reg, reg);
    }

    /**
     * The code that does the real work of renewLeases.  Each element of
     * regIDs must either be a ServiceID (for a service lease) or a Long
     * (for an event lease).  Renewals contains durations.  All three
     * arrays must be the same length.
     */
    private RenewResults renewLeasesDo(Object[] regIDs,
               Uuid[] leaseIDs,
               long[] renewals)
    {
  long now = System.currentTimeMillis();
  Exception[] exceptions = null;
  for (int i = 0; i < regIDs.length; i++) {
      Object id = regIDs[i];
      try {
    if (id instanceof ServiceID)
        renewals[i] = renewServiceLeaseInt((ServiceID)id,
                   leaseIDs[i],
                   renewals[i], now);
    else
        renewals[i] = renewEventLeaseInt(((Long)id).longValue(),
                 leaseIDs[i], renewals[i],
                 now);
      } catch (Exception e) {
    renewals[i] = -1;
    if (exceptions == null)
        exceptions = new Exception[]{e};
    else
        exceptions = (Exception[])arrayAdd(exceptions, e);
      }
  }
  /* don't bother to weed out problem leases */
  addLogRecord(new LeasesRenewedLogObj(regIDs, leaseIDs, renewals));
  for (int i = regIDs.length; --i >= 0; ) {
      if (renewals[i] >= 0)
    renewals[i] -= now;
  }
  return new RenewResults(renewals, exceptions);
    }

    /**
     * Renew the leases for absolute expiration times.  Skip any leases
     * with negative expiration times.
     */
    private void renewLeasesAbs(Object[] regIDs,
        Uuid[] leaseIDs,
        long[] renewExpirations)
    {
  for (int i = regIDs.length; --i >= 0; ) {
      long expiration = renewExpirations[i];
      if (expiration < 0)
    continue;
      Object id = regIDs[i];
      if (id instanceof ServiceID)
    renewServiceLeaseAbs((ServiceID)id, leaseIDs[i], expiration);
      else
    renewEventLeaseAbs(((Long)id).longValue(), leaseIDs[i],
           expiration);
  }
    }

    /**
     * The code that does the real work of cancelLeases.  Each element of
     * regIDs must either be a ServiceID (for a service lease) or a Long
     * (for an event lease).  The two arrays must be the same length.
     * If there are no exceptions, the return value is null.  Otherwise,
     * the return value has the same length as regIDs, and has nulls for
     * leases that successfully renewed.
     */
    private Exception[] cancelLeasesDo(Object[] regIDs, Uuid[] leaseIDs) {
  Exception[] exceptions = null;
  for (int i = regIDs.length; --i >= 0; ) {
      Object id = regIDs[i];
      try {
    if (id instanceof ServiceID)
        cancelServiceLeaseDo((ServiceID)id, leaseIDs[i]);
    else
        cancelEventLeaseDo(((Long)id).longValue(), leaseIDs[i]);
      } catch (Exception e) {
    if (exceptions == null)
        exceptions = new Exception[regIDs.length];
    exceptions[i] = e;
      }
  }
  return exceptions;
    }

    /**
     * Generate events for all matching event registrations.  A null pre
     * represents creation of a new item, a null post represents deletion
     * of an item.
     */
    private void generateEvents(Item pre, Item post, long now) {
  if (inRecovery)
      return;
  ServiceID sid = (pre != null) ? pre.serviceID : post.serviceID;
  Object val = subEventByService.get(sid);
  if (val instanceof EventReg) {
      generateEvent((EventReg)val, pre, post, sid, now);
  } else if (val != null) {
      EventReg[] regs = (EventReg[])val;
      for (int i = regs.length; --i >= 0; ) {
    generateEvent(regs[i], pre, post, sid, now);
      }
  }
  for (Iterator iter = subEventByID.values().iterator();
       iter.hasNext(); )
  {
      generateEvent((EventReg)iter.next(), pre, post, sid, now);
  }
    }

    /**
     * Generate an event if the event registration matches.  A null pre
     * represents creation of a new item, a null post represents deletion
     * of an item.  sid is the serviceID of the item.
     */
    private void generateEvent(EventReg reg,
             Item pre,
             Item post,
             ServiceID sid,
             long now)
    {
  if (reg.leaseExpiration <= now)
      return;
  if ((reg.transitions &
      ServiceRegistrar.TRANSITION_NOMATCH_MATCH) != 0 &&
     (pre == null || !matchItem(reg.tmpl, pre)) &&
     (post != null && matchItem(reg.tmpl, post)))
      pendingEvent(reg, sid, post,
       ServiceRegistrar.TRANSITION_NOMATCH_MATCH);
  else if ((reg.transitions &
      ServiceRegistrar.TRANSITION_MATCH_NOMATCH) != 0 &&
     (pre != null && matchItem(reg.tmpl, pre)) &&
     (post == null || !matchItem(reg.tmpl, post)))
      pendingEvent(reg, sid, post,
       ServiceRegistrar.TRANSITION_MATCH_NOMATCH);
  else if ((reg.transitions &
      ServiceRegistrar.TRANSITION_MATCH_MATCH) != 0 &&
     (pre != null && matchItem(reg.tmpl, pre)) &&
     (post != null && matchItem(reg.tmpl, post)))
      pendingEvent(reg, sid, post,
       ServiceRegistrar.TRANSITION_MATCH_MATCH);
    }

    /** Add a pending EventTask for this event registration. */
    private void pendingEvent(EventReg reg,
            ServiceID sid,
            Item item,
            int transition)
    {
  if (item != null)
      item = copyItem(item);
  newNotifies.add(new EventTask(reg, sid, item, transition));
    }

    /** Queue all pending EventTasks for processing by the task manager. */
    private void queueEvents() {
  if (!newNotifies.isEmpty()) {
      tasker.addAll(newNotifies);
      newNotifies.clear();
  }
    }

    /** Generate a new service ID */
    private ServiceID newServiceID() {
  Uuid uuid = serviceIdGenerator.generate();
  return new ServiceID(
      uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
    }

    /** Generate a new lease ID */
    private Uuid newLeaseID() {
  return resourceIdGenerator.generate();
    }

    /**
     * Write the current state of the Registrar to persistent storage.
     * <p>
     * A 'snapshot' of the Registrar's current state is represented by
     * the data contained in certain fields of the Registrar. That data
     * represents many changes -- over time -- to the Registrar's state.
     * This method will record that data to a file referred to as the
     * snapshot file.
     * <p>
     * The data written by this method to the snapshot file -- as well as
     * the format of the file -- is shown below:
     * <ul>
     * <li> our class name
     * <li> log format version number
     * <li> our service ID
     * <li> current event ID
     * <li> current values of administrable parameters and current multicast
     * announcement sequence number
     * <li> contents of the container holding the current registered services
     * <li> null (termination 'marker' for the set of registered services)
     * <li> contents of the container holding the current registered events
     * <li> null (termination 'marker' for the set of registered events)
     * </ul>
     * Each data item is written to the snapshot file in serialized form.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private void takeSnapshot(OutputStream  out) throws IOException {
  ObjectOutputStream stream = new ObjectOutputStream(out);

  stream.writeUTF(getClass().getName());
  stream.writeInt(LOG_VERSION);
  stream.writeObject(myServiceID);
  stream.writeLong(eventID);
  stream.writeInt(unicastPort);
  stream.writeObject(memberGroups);
  stream.writeObject(lookupGroups);
  stream.writeLong(announcementSeqNo);
  marshalAttributes(lookupAttrs, stream);
  marshalLocators(lookupLocators, stream);
  for (Iterator iter = serviceByID.entrySet().iterator();
       iter.hasNext(); )
  {
      Map.Entry entry = (Map.Entry) iter.next();
      if (myServiceID != entry.getKey())
    stream.writeObject(entry.getValue());
  }
  stream.writeObject(null);
  for (Iterator iter = eventByID.values().iterator(); iter.hasNext(); )
  {
      stream.writeObject(iter.next());
  }
  stream.writeObject(null);
  stream.flush();
  logger.finer("wrote state snapshot");
    }

    /**
     * Retrieve the contents of the snapshot file and reconstitute the 'base'
     * state of the Registrar from the retrieved data.
     * <p>
     * The data retrieved by this method from the snapshot file is shown
     * below:
     * <ul>
     * <li> our class name
     * <li> log format version number
     * <li> our service ID
     * <li> current event ID
     * <li> current values of administrable parameters and multicast
     * announcement sequence number at time of last snapshot
     * <li> contents of the container holding the current registered services
     * <li> contents of the container holding the current registered events
     * </ul>
     * During recovery, the state of the Registrar at the time of a crash
     * or outage is re-constructed by first reconstituting the 'base state'
     * from the snapshot file; and then modifying that base state according
     * to the records retrieved from the log file. This method is invoked to
     * perform the first step in that reconstruction. As each registered
     * service or event is retrieved, it is resolved and then inserted into
     * its appropriate container object.
     * <p>
     * Because events can be generated before the next snapshot is taken,
     * the event sequence numbers must be incremented. This is because the
     * event specification requires that set of event sequence numbers
     * be monotonically increasing. Since the number of events that might
     * have been sent is arbitrary, each sequence number will be incremented
     * by a 'large' number so as to guarantee adherence to the specification.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private void recoverSnapshot(InputStream in)
  throws IOException, ClassNotFoundException
    {
  ObjectInputStream stream = new ObjectInputStream(in);
  if (!getClass().getName().equals(stream.readUTF()))
      throw new IOException("log from wrong implementation");
  int logVersion = stream.readInt();
  if (logVersion != LOG_VERSION)
      throw new IOException("wrong log format version");
  myServiceID = (ServiceID)stream.readObject();
  eventID = stream.readLong();
  unicastPort = stream.readInt();
  memberGroups = (String[])stream.readObject();
  lookupGroups = (String[])stream.readObject();
  announcementSeqNo = stream.readLong() + Integer.MAX_VALUE;
  lookupAttrs = unmarshalAttributes(stream);
  lookupLocators = prepareLocators(
      unmarshalLocators(stream), recoveredLocatorPreparer, true);
  recoverServiceRegistrations(stream, logVersion);
  recoverEventRegistrations(stream);
  recoveredSnapshot = true;
  logger.finer("recovered state from snapshot");
    }

    /** Recovers service registrations and reggie's lookup attributes */
    private void recoverServiceRegistrations(ObjectInputStream stream,
               int logVersion)
  throws IOException, ClassNotFoundException
    {
  SvcReg sReg;
  while ((sReg = (SvcReg)stream.readObject()) != null) {
      addService(sReg);
  }
    }

    /** Recovers event registrations */
    private void recoverEventRegistrations(ObjectInputStream stream)
  throws IOException, ClassNotFoundException
    {
  EventReg eReg;
  while ((eReg = (EventReg)stream.readObject()) != null) {
      eReg.prepareListener(recoveredListenerPreparer);
      eReg.seqNo += Integer.MAX_VALUE;
      addEvent(eReg);
  }
    }

    /**
     * Add a state-change record to persistent storage.
     * <p>
     * Whenever a significant change occurs to the Registrar's state, this
     * method is invoked to record that change in a file called a log file.
     * Each record written to the log file is an object reflecting both
     * the data used and the ACTIONS taken to make one change to the
     * Registrar's state at a particular point in time. If the number of
     * records contained in the log file exceeds the pre-defined threshold,
     * a snapshot of the current state of the Registrar will be recorded.
     * <p>
     * Whenever one of the following state changes occurs, this method
     * will be invoked with the appropriate implementation of the
     * LogRecord interface as the input argument.
     * <ul>
     * <li> a new service was registered
     * <li> a new event was registered
     * <li> new attributes were added to an existing service
     * <li> existing attributes of a service were modified
     * <li> new attributes were set in an existing service
     * <li> a single service lease was renewed
     * <li> a single service lease was cancelled
     * <li> a single event lease was renewed
     * <li> a single event lease was cancelled
     * <li> a set of service leases were renewed
     * <li> a set of service leases were cancelled
     * <li> a set of event leases were renewed
     * <li> a set of event leases were cancelled
     * <li> the unicast port number was set
     * <li> the set of Lookup Groups were changed
     * <li> the set of Lookup Locators were changed
     * <li> the set of Member Groups were changed
     * </ul>
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private void addLogRecord(LogRecord rec) {
  if (log == null) {
      return;
  }
  try {
      log.update(rec, true);
      if (logger.isLoggable(Level.FINER)) {
    logger.log(Level.FINER, "wrote log record {0}",
         new Object[]{ rec });
      }
      if (++logFileSize >= persistenceSnapshotThreshold) {
    int snapshotSize = serviceByID.size() + eventByID.size();
    if (logFileSize >= persistenceSnapshotWeight * snapshotSize) {
                    concurrentObj.waiterNotify(snapshotNotifier);
    }
      }
  } catch (Exception e) {
      if (!Thread.currentThread().isInterrupted()) {
    logger.log(Level.WARNING, "log update failed", e);
      }
  }
    }
}
TOP

Related Classes of com.sun.jini.reggie.RegistrarImpl$SnapshotThread

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