Package com.sun.jini.test.share.reggie

Source Code of com.sun.jini.test.share.reggie.RegistrarImpl$ItemIter

/*
* 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.test.share.reggie;

import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import java.rmi.MarshalledObject;
import java.rmi.UnmarshalException;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationSystem;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.StringTokenizer;
import java.security.SecureRandom;
import java.lang.reflect.Array;
import com.sun.jini.constants.TimeConstants;
import com.sun.jini.constants.ThrowableConstants;
//import com.sun.jini.start.SharedActivation;
import com.sun.jini.thread.ReadersWriter;
import com.sun.jini.thread.ReadersWriter.ConcurrentLockException;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.lookup.entry.BasicServiceType;
import net.jini.core.entry.Entry;
import net.jini.core.lease.*;
import net.jini.core.event.*;
import net.jini.core.lookup.*;
import net.jini.lookup.JoinManager;
import net.jini.lookup.entry.ServiceInfo;
import net.jini.core.discovery.LookupLocator;
import net.jini.discovery.*;
import com.sun.jini.reliableLog.ReliableLog;
import com.sun.jini.reliableLog.LogException;
import com.sun.jini.reliableLog.LogHandler;

/**
* The server side of an implementation of the lookup service.  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.
*/
public class RegistrarImpl implements Registrar {

    /* ServiceInfo values */
    private static final String PRODUCT = "Lookup";
    private static final String MANUFACTURER = "Sun Microsystems, Inc.";
    private static final String VENDOR = MANUFACTURER;
    private static final String VERSION =
  com.sun.jini.constants.VersionConstants.SERVER_VERSION;

    /** 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;
    /** Log format version */
    private static final int LOG_VERSION = 1;

    /** Internet Protocol (IP) addresses of the network interfaces (NICs)
     *  through which multicast packets will be sent.
     */
    private InetAddress[] nicAddresses;

    /** Empty attribute set */
    private static final EntryRep[] emptyAttrs = {};

    /** Proxy for myself */
    private RegistrarProxy proxy;
    /** Our service ID */
    private ServiceID myServiceID;
    /** Our activation id, if we're activatable */
    private ActivationID activationID;
    /** Reference to shared activation object. Used to modify the service's
     *  activation state under a shared activation group environment.
     */
    //    private SharedActivation sharedActivationRef = null;
    /** Our LookupLocator */
    private LookupLocator myLocator;

    /**
     * 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 ServiceType to ArrayList(SvcReg).  Every service is in
     * this map under its exact serviceType.
     */
    private final HashMap serviceByType = 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);

    /** random number generator for UUID generation */
    private final SecureRandom secRand = new SecureRandom();
    /** 128-bit buffer for use with secRand */
    private final byte[] secRandBuf16 = new byte[16];
    /** 64-bit buffer for use with secRand */
    private final byte[] secRandBuf8 = new byte[8];
    /** Event id generator */
    private long eventID = 0;
    /** Random number generator for use in lookup */
    private final Random random = new Random();

    /** 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 LookupDiscoveryManager discoer;
    /** Manager for joining other lookup services */
    private JoinManager joiner;
    /** Task manager for sending events and discovery responses */
    private final TaskManager tasker = new TaskManager(10, 1000 * 15, 1.0f);
    /** Service lease expiration thread */
    private final Thread serviceExpirer = new ServiceExpireThread();
    /** Event lease expiration thread */
    private final Thread eventExpirer = new EventExpireThread();
    /** 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 final Thread snapshotter = new SnapshotThread();

    /** 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();

    /** Class resolver */
    private final ClassResolver resolver = new ClassResolver();
    /** Canonical ServiceType for java.lang.Object */
    private ServiceType objectServiceType;

    /** Reliable log object to hold Service and Event Records */
    private ReliableLog log;
    /** Flag indicating whether system is in a state of recovery */
    private boolean inRecovery;
    /** Current number of records in the Log File since the last snapshot */
    private int logFileSize = 0;

    /** Name of directory for persistence using ReliableLog */
    private String logDirname;
    /** Log File must contain this many records before snapshot allowed */
    private int logToSnapshotThresh = 200;
    /** Weight factor applied to snapshotSize when deciding to take snapshot */
    private float snapshotWt = 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 String[] memberGroups = {};
    /** The groups we should join */
    private String[] lookupGroups = {};
    /** The locators of other lookups we should join */
    private LookupLocator[] lookupLocators = {};
    /** The attributes to use when joining (including with myself) */
    private Entry[] lookupAttrs;

    /** Socket timeout for unicast discovery request processing */
    private int unicastTimeout =
  Integer.getInteger("com.sun.jini.reggie.unicastTimeout",
         1000 * 60).intValue();

    /**
     * Called by the activation group.
     *
     * @param activationID our activation id
     * @param data log directory name as a MarshalledObject
     */
    public RegistrarImpl(ActivationID activationID, MarshalledObject data)
  throws IOException
    {
  this.activationID = activationID;
  Activatable.exportObject(this, activationID, 0);
  try {
      logDirname = (String)data.get();
  } catch (Exception e) {
      throw new IllegalArgumentException
            ("bad directory name in descriptor");
  }
  init();
    }
 
    /**
     * Constructs an activatable, persistent instance of the Reggie
     * implementation of the lookup service that runs in an activation
     * group (VM) in which other activatable objects may also run.
     *
     * @param activationID our activation id
     * @param data log directory name as a MarshalledObject
     * @param sharedActivationRef reference to the shared activation object
     */
//      public RegistrarImpl(ActivationID activationID,
//                           MarshalledObject data,
//                           SharedActivation sharedActivationRef)
//                                                             throws IOException
//      {
//    this.activationID = activationID;
//          this.sharedActivationRef = sharedActivationRef;
//    Activatable.exportObject(this, activationID, 0);
//    try {
//        logDirname = (String)data.get();
//    } catch (Exception e) {
//        throw new IllegalArgumentException
//              ("bad directory name in descriptor");
//    }
//    init();
//      }

    /**
     * Called by ServiceStarter.createTransient for transient instances.
     *
     * @param logDirname log directory name
     */
    RegistrarImpl(String logDirname) throws IOException {
  UnicastRemoteObject.exportObject(this);
  this.logDirname = logDirname;
  init();
    }

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

  private static final long serialVersionUID = -1626838158255069853L;

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

  /** Simple constructor */
  public SvcReg(Item item, long 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;
      if (leaseExpiration < reg.leaseExpiration ||
    (leaseExpiration == reg.leaseExpiration &&
     leaseID < reg.leaseID))
    return -1;
      return 1;
  }
    }

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

  private static final long serialVersionUID = -1549670962624946202L;

  /**
   * The event id.
   * @serial
   */
  public final long eventID;
  /**
   * The lease id.
   * @serial
   */
  public final long 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, long 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;
  }

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

  /**
   * Unmarshals the event listener.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException
  {
      stream.defaultReadObject();
      MarshalledObject mo = (MarshalledObject)stream.readObject();
      try {
    listener = (RemoteEventListener)mo.get();
      } catch (Throwable e) {
    if (e instanceof Error &&
        ThrowableConstants.retryable(e) ==
        ThrowableConstants.BAD_OBJECT)
        throw (Error)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 = 833681392248560066L;

  /**
   * 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) {
      regImpl.resolver.resolve(reg.item);
      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 = -5347182410011723905L;

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

  /** Simple constructor */
  public AttrsAddedLogObj(ServiceID serviceID,
        long 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  */
      }
  }
    }

    /**
     * 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 = -2773350506956661576L;

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

  /** Simple constructor */
  public AttrsModifiedLogObj(ServiceID serviceID,
           long 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  */
      }
  }
    }

    /**
     * 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 = -395979667535255420L;

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

  /** Simple constructor */
  public AttrsSetLogObj(ServiceID serviceID,
            long 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 = -807655888250060611L;

  /**
   * 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) {
      regImpl.resolver.resolve(eventReg.tmpl);
      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 = 8363406735506378972L;

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

  /** Simple constructor */
  public ServiceLeaseCancelledLogObj(ServiceID serviceID, long 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 = -6941618092365889931L;

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

  /** Simple constructor */
  public ServiceLeaseRenewedLogObj(ServiceID serviceID,
           long 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 = 723479933309720973L;

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

  /** Simple constructor */
  public EventLeaseCancelledLogObj(long eventID, long 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 = -2313983070714702699L;

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

  /** Simple constructor */
  public EventLeaseRenewedLogObj(long eventID,
               long 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 = -2796802215410373050L;

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

  /** Simple constructor */
  public LeasesRenewedLogObj(Object[] regIDs,
           long[] 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 = 7462419701214242565L;

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

  /** Simple constructor */
  public LeasesCancelledLogObj(Object[] regIDs, long[] 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 = -5923066193628958723L;

  /**
   * 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 Minimum Maximum Service Lease Duration is set to a new value.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class MinMaxServiceLeaseSetLogObj implements LogRecord {

  private static final long serialVersionUID = 3205940543108355772L;

  /**
   * The new minimum maximum service lease duration.
   *
   * @serial
   */
  private long newLeaseDuration;

  /** Simple constructor */
  public MinMaxServiceLeaseSetLogObj(long newLeaseDuration) {
      this.newLeaseDuration = newLeaseDuration;
  }

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

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the Minimum Maximum Event Lease Duration is set to a new value.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class MinMaxEventLeaseSetLogObj implements LogRecord {

  private static final long serialVersionUID = -6425690586422726211L;

  /**
   * The new minimum maximum event lease duration.
   *
   * @serial
   */
  private long newLeaseDuration;

  /** Simple constructor */
  public MinMaxEventLeaseSetLogObj(long newLeaseDuration) {
      this.newLeaseDuration = newLeaseDuration;
  }

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

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the Minimum Renewal Interval is set to a new value.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class MinRenewalIntervalSetLogObj implements LogRecord {

  private static final long serialVersionUID = 5812923613520666861L;

  /**
   * The new minimum renewal interval.
   *
   * @serial
   */
  private long newRenewalInterval;

  /** Simple constructor */
  public MinRenewalIntervalSetLogObj(long newRenewalInterval) {
      this.newRenewalInterval = newRenewalInterval;
  }

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

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the Weight Factor applied to the size of the Snapshot File in the
     * "snapshot determination" expression is set to a new value.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class SnapshotWeightSetLogObj implements LogRecord {

  private static final long serialVersionUID = -7068462033891338457L;

  /**
   * The new snapshot weight factor.
   *
   * @serial
   */
  private float newWeight;

  /** Simple constructor */
  public SnapshotWeightSetLogObj(float newWeight) {
      this.newWeight = newWeight;
  }

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

    /**
     * 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 = -5164130449456011085L;

  /**
   * 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 = 7707774807971026109L;

  /**
   * The new locators to join.
   *
   * @serial
   */
  private 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) {
      regImpl.lookupLocators = locators;
  }
    }

    /**
     * LogObj class whose instances are recorded to the log file whenever
     * the Threshold employed in the "snapshot determination" expression
     * is set to a new value.
     *
     * @see RegistrarImpl.LocalLogHandler
     */
    private static class LogToSnapshotThresholdSetLogObj implements LogRecord {

  private static final long serialVersionUID = 7141538629320197819L;

  /**
   * The new snapshot threshold.
   *
   * @serial
   */
  private int newThreshold;

  /** Simple constructor */
  public LogToSnapshotThresholdSetLogObj(int newThreshold) {
      this.newThreshold = newThreshold;
  }

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

    /**
     * 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 = 4764341386996269738L;

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

    /**
     * Handler class for the persistent storage facility.
     * <p>
     * At any point during processing in the Registrar, 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 after a
     * crash or a network outage (or if the Registrar or its ActivationGroup
     * is un-registered and then re-registered through the Activation Daemon).
     * <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 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 lease ID
     * <li> current event ID
     * <li> current configuration 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> a configuration 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;
  }
    }

    /** Iterate over all matching Items by ServiceType. */
    private class TypeItemIter extends ItemIter {
  /** Concrete classes matching tmpl.serviceTypes */
  private final ServiceType[] types;
  /** Current index into types */
  private int typeidx;
  /** SvcRegs obtained from serviceByType for current service type */
  private ArrayList svcs;
  /** Current index into svcs */
  private int svcidx = 0;

  /**
   * tmp.serviceID == null and
   * tmpl.serviceTypes is non-empty
   */
  public TypeItemIter(Template tmpl) {
      super(tmpl);
      types = matchingConcreteClasses(tmpl.serviceTypes);
      typeidx = types.length;
      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 (stepType());
      reg = null;
  }

  /**
   * Step to the next type in types, if any, reset svcs and svcidx,
   * and return false if types exhausted.
   */
  private boolean stepType() {
      if (--typeidx < 0)
    return false;
      svcs = (ArrayList)serviceByType.get(types[typeidx]);
      svcidx = svcs.size();
      return true;
  }
    }

    /** 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() {
      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)
      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.
         */
        try {
      cancelEventLease(reg.eventID, reg.leaseID);
        } catch (UnknownLeaseException ee) {
        }
    }
      }
  }

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

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

  /** The address */
  public final InetAddress addr;
  /** The port */
  public final int port;

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

  public int hashCode() {
      return addr.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 addr.equals(ua.addr) && port == ua.port;
  }

  /** Connect and then process a unicast discovery request */
  public void run() {
      try {
    respond(new Socket(addr, port));
      } catch (IOException e) {
      } catch (SecurityException e) {
      }
  }

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

    /** 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() {
      respond(socket);
  }

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

    /** Service lease expiration thread code */
    private class ServiceExpireThread extends Thread {
  /** True if thread has been interrupted */
  private boolean interrupted = false;

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

  public synchronized void interrupt() {
      interrupted = true;
      super.interrupt();
  }

  public synchronized boolean isInterrupted() {
      return interrupted;
  }

  public void run() {
      try {
    concurrentObj.writeLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!isInterrupted()) {
        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));
        }
        queueEvents();
        try {
      concurrentObj.writerWait(serviceNotifier,
             minSvcExpiration - now);
        } catch (ConcurrentLockException e) {
      return;
        }
    }
       } finally {
     concurrentObj.writeUnlock();
       }
  }
    }

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

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

  public void run() {
      try {
    concurrentObj.writeLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!isInterrupted()) {
        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);
        }
        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 Activatable.inactive will block until
     * in-progress RMI calls are finished.
     */
    private class DestroyThread extends Thread {
        /** Maximum delay for unexport attempts */
        private static final long MAX_UNEXPORT_DELAY = 2*TimeConstants.MINUTES;

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

  public void run() {

      /* must unregister before unexport */
      if (activationID != null) {
    try {
        Activatable.unregister(activationID);
    } catch (RemoteException e) {
        /* give up until we can at least unregister */
        return;
    } catch (ActivationException e) {
    }
      }
            final long endTime = System.currentTimeMillis()+MAX_UNEXPORT_DELAY;
            boolean unexported = false;
            try {
                /* Unexport only if there are no pending or in progress calls*/
                while(!unexported && (System.currentTimeMillis() < endTime)) {
                    unexported = UnicastRemoteObject.unexportObject
                                                    (RegistrarImpl.this,false);
                    if(!unexported) Thread.yield();
                }//end loop
            } catch (NoSuchObjectException e) {
                unexported = true;
            }
            if(!unexported) {//Not yet unexported. Forcibly unexport
                try {
                    UnicastRemoteObject.unexportObject
                                                    (RegistrarImpl.this,true);
                } catch (NoSuchObjectException e) { }
            }//endif
      /* 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());
      log.deletePersistentStore();
      if (activationID != null) {
    /* inactive will set current group ID to null */
    ActivationGroupID gid = ActivationGroup.currentGroupID();
    try {
        Activatable.inactive(activationID);
    } catch (RemoteException e) {
    } catch (ActivationException e) {
    }
                /* If group is not 'shared', must destroy the group (VM) */
//                  if (sharedActivationRef == null) {//not in a 'shared' group
        try {
            ActivationGroup.getSystem().unregisterGroup(gid);
        } catch (RemoteException e) {
        } catch (ActivationException e) {
        }
//                  }
      }
  }
    }

    /** Multicast discovery request thread code. */
    private class MulticastThread extends Thread {
  /** Multicast socket to receive packets */
  private final MulticastSocket socket;

  /**
   * 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);
      setPriority(Thread.MAX_PRIORITY);
      socket = new MulticastSocket(Constants.discoveryPort);
            if((nicAddresses != null) && (nicAddresses.length > 0)) {
                for(int i=0;i<nicAddresses.length;i++) {
                    socket.setInterface(nicAddresses[i]);
                    socket.joinGroup(Constants.getRequestAddress());
                }//end loop
            } else {//use default interface
                socket.joinGroup(Constants.getRequestAddress());
            }//endif
  }

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

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

  public synchronized boolean isInterrupted() {
      return interrupted;
  }

  public void run() {
      byte[] buf = new byte[576];
      DatagramPacket dgram = new DatagramPacket(buf, buf.length);
      while (!isInterrupted()) {
    try {
        dgram.setLength(buf.length);
        try {
      socket.receive(dgram);
        } catch (NullPointerException e) {
      break; // workaround for bug 4190513
        }
        IncomingMulticastRequest req =
      new IncomingMulticastRequest(dgram);
        if ((req.getGroups().length != 0 &&
       !overlap(memberGroups, req.getGroups())) ||
      indexOf(req.getServiceIDs(), myServiceID) >= 0)
      continue;
        tasker.addIfNew(new AddressTask(req.getAddress(),
                req.getPort()));
    } catch (InterruptedIOException e) {
        break;
    } catch (IOException e) {
    } catch (SecurityException e) {
    }
    /* if we fail in any way, just forget about it */
      }
      socket.close();
  }
    }

    /** Unicast discovery request thread code. */
    private class UnicastThread extends Thread {
  /** 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);
        this.port = Constants.discoveryPort;
        return;
    } catch (IOException e) {
    }
      }
      listen = new ServerSocket(port);
      this.port = listen.getLocalPort();
  }

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

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

  public synchronized boolean isInterrupted() {
      return interrupted;
  }

  public void run() {
      while (!isInterrupted()) {
    try {
        Socket socket = listen.accept();
        if (isInterrupted()) {
      try {
          socket.close();
      } catch (IOException e) {
      }
      break;
        }
        tasker.add(new SocketTask(socket));
    } catch (InterruptedIOException e) {
        break;
    } catch (IOException e) {
    } catch (SecurityException e) {
    }
    /* if we fail in any way, just forget about it */
      }
      try {
    listen.close();
      } catch (IOException e) {
      }
  }
    }

    /** Multicast discovery announcement thread code. */
    private class AnnounceThread extends Thread {
  /** Multicast socket to send packets on */
  private final MulticastSocket socket;

  /**
   * 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);
      socket = new MulticastSocket();
      socket.setTimeToLive(Integer.getInteger("net.jini.discovery.ttl",
                15).intValue());
  }

  public synchronized void run() {
      int interval = Integer.getInteger("net.jini.discovery.announce",
                1000 * 60 * 2).intValue();
      try {
    while (!isInterrupted() && announce(memberGroups)) {
        wait(interval);
    }
      } 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.
   */
  private boolean announce(String[] groups) {
      try {
    DatagramPacket[] dgrams =
        OutgoingMulticastAnnouncement.marshal(myServiceID,
                myLocator,
                groups);
                try {
                    sendPacketByNIC(socket,dgrams,nicAddresses);
                } catch (InterruptedIOException e) {
                    return false;
                }
      } catch (InterruptedIOException e) {
    return false;
      } catch (IOException e) {
      }
      return true;
  }
    }

    /**
     * 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 Thread {
  /** True if thread has been interrupted */
  private boolean interrupted = false;

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

  public synchronized void interrupt() {
      interrupted = true;
      super.interrupt();
  }

  public synchronized boolean isInterrupted() {
      return interrupted;
  }

  public void run() {
      try {
    concurrentObj.readLock();
      } catch (ConcurrentLockException e) {
    return;
      }
      try {
    while (!isInterrupted()) {
        try {
      concurrentObj.readerWait(snapshotNotifier,
             Long.MAX_VALUE);
                        try {
                            log.snapshot();
                logFileSize = 0;
                  } catch (Exception e) {
          if (isInterrupted())
        return;
                      /* XXX
                       * if taking the snapshot fails for any reason,
                             * then one of the following must be done:
                       *   -- output the problem to a file and exit
                       *   -- output the problem to a file and continue
                       *   -- set an "I have a problem" attribute and
                             *      then send a notification
                       * this issue will be addressed at a later time
                       */
          e.printStackTrace();
            }
        } catch (ConcurrentLockException e) {
      return;
        }
    }
      } finally {
    concurrentObj.readUnlock();
      }
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public ServiceRegistration register(Item nitem, long leaseDuration)
    {
        /* Delay before registering so test can call JoinManager.addAttributes
         * before registration completes. This will reveal the race condition
         * in bug #4671109.
         */
        try { Thread.sleep(10*1000); } catch (InterruptedException e) { }

  concurrentObj.writeLock();
  try {
            ServiceRegistration reg = registerDo(nitem, leaseDuration);
      return reg;
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public MarshalledObject lookup(Template tmpl)
    {
  concurrentObj.readLock();
  try {
      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)
    {
  concurrentObj.readLock();
  try {
      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)
    {
  concurrentObj.writeLock();
  try {
      return notifyDo(tmpl, transitions, listener, handback,
          leaseDuration);
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public EntryClassBase[] getEntryClasses(Template tmpl)
    {
  concurrentObj.readLock();
  try {
      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)
    {
  concurrentObj.readLock();
  try {
      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)
    {
  concurrentObj.readLock();
  try {
      return getServiceTypesDo(tmpl, prefix);
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public ServiceID getServiceID() {
  return myServiceID;
    }

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

    // This method's javadoc is inherited from an interface of this class
    public Object getAdmin() throws RemoteException {
  return (new RegistrarAdminProxy(this, myServiceID));
    }

    // This method's javadoc is inherited from an interface of this class
    public void addAttributes(ServiceID serviceID,
            long leaseID,
            EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  if (serviceID.equals(myServiceID))
      throw new SecurityException("privileged service id");
  concurrentObj.writeLock();
  try {
      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,
         long leaseID,
         EntryRep[] attrSetTmpls,
         EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  if (serviceID.equals(myServiceID))
      throw new SecurityException("privileged service id");
  concurrentObj.writeLock();
  try {
      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,
            long leaseID,
            EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  if (serviceID.equals(myServiceID))
      throw new SecurityException("privileged service id");
  concurrentObj.writeLock();
  try {
      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, long leaseID)
  throws UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      cancelServiceLeaseDo(serviceID, leaseID);
      addLogRecord(new ServiceLeaseCancelledLogObj(serviceID, leaseID));
      queueEvents();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long renewServiceLease(ServiceID serviceID,
          long leaseID,
          long renewDuration)
  throws UnknownLeaseException
    {
  concurrentObj.priorityWriteLock();
  try {
      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, long leaseID)
  throws UnknownLeaseException
    {
  concurrentObj.writeLock();
  try {
      cancelEventLeaseDo(eventID, leaseID);
      addLogRecord(new EventLeaseCancelledLogObj(eventID, leaseID));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long renewEventLease(long eventID, long leaseID, long renewDuration)
  throws UnknownLeaseException
    {
  concurrentObj.priorityWriteLock();
  try {
      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,
            long[] leaseIDs,
            long[] renewDurations)
    {
  concurrentObj.priorityWriteLock();
  try {
      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, long[] leaseIDs)
    {
  concurrentObj.writeLock();
  try {
      Exception[] exceptions = cancelLeasesDo(regIDs, leaseIDs);
      addLogRecord(new LeasesCancelledLogObj(regIDs, leaseIDs));
      queueEvents();
      return exceptions;
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public Entry[] getLookupAttributes() throws RemoteException {
  concurrentObj.readLock();
  try {
      /* 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 {
      joiner.addAttributes(attrSets, true);
      lookupAttrs = joiner.getAttributes();
      EntryRep[] attrs = EntryRep.toEntryRep(attrSets, true);
      try {
    addAttributesDo(myServiceID, 0, attrs);
      } catch (UnknownLeaseException e) {
      }
      addLogRecord(new AttrsAddedLogObj(myServiceID, 0, attrs));
      queueEvents();
  } 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 {
      joiner.modifyAttributes(attrSetTemplates, attrSets, true);
      lookupAttrs = joiner.getAttributes();
      EntryRep[] tmpls = EntryRep.toEntryRep(attrSetTemplates, false);
      EntryRep[] attrs = EntryRep.toEntryRep(attrSets, false);
      try {
    modifyAttributesDo(myServiceID, 0, tmpls, attrs);
      } catch (UnknownLeaseException e) {
      }
      addLogRecord(new AttrsModifiedLogObj(myServiceID, 0,
             tmpls, attrs));
      queueEvents();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public String[] getLookupGroups() throws RemoteException {
  concurrentObj.readLock();
  try {
      /* 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 RemoteException {
  concurrentObj.writeLock();
  try {
      try {
    discoer.addGroups(groups);
      } catch (IOException e) {
    throw new RuntimeException(e.toString());
      }
      lookupGroups = discoer.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeLookupGroups(String[] groups) throws RemoteException {
  concurrentObj.writeLock();
  try {
      discoer.removeGroups(groups);
      lookupGroups = discoer.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setLookupGroups(String[] groups) throws RemoteException {
  concurrentObj.writeLock();
  try {
      try {
    discoer.setGroups(groups);
      } catch (IOException e) {
    throw new RuntimeException(e.toString());
      }
      lookupGroups = discoer.getGroups();
      addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public LookupLocator[] getLookupLocators() throws RemoteException {
  concurrentObj.readLock();
  try {
      /* 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
    {
  concurrentObj.writeLock();
  try {
      discoer.addLocators(locators);
      lookupLocators = discoer.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeLookupLocators(LookupLocator[] locators)
  throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      discoer.removeLocators(locators);
      lookupLocators = discoer.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setLookupLocators(LookupLocator[] locators)
  throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      discoer.setLocators(locators);
      lookupLocators = discoer.getLocators();
      addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void addMemberGroups(String[] groups) throws RemoteException {
  concurrentObj.writeLock();
  try {
      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));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void removeMemberGroups(String[] groups) throws RemoteException {
  concurrentObj.writeLock();
  try {
      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));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public String[] getMemberGroups() {
  concurrentObj.readLock();
  try {
      /* 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 RemoteException {
  concurrentObj.writeLock();
  try {
      memberGroups = (String[])removeDups(groups);
      addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
      synchronized (announcer) {
    announcer.notify();
      }
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public int getUnicastPort() throws RemoteException {
  concurrentObj.readLock();
  try {
      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 {
  if (port == unicastPort)
      return;
  concurrentObj.writeLock();
  try {
      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 = new LookupLocator(myLocator.getHost(), unicaster.port);
      synchronized (announcer) {
    announcer.notify();
      }
      addLogRecord(new UnicastPortSetLogObj(port));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setMinMaxServiceLease(long leaseDuration)
  throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      if (leaseDuration > MAX_LEASE)
    throw new IllegalArgumentException("max duration exceeded");
      minMaxServiceLease = leaseDuration;
      computeMaxLeases();
      addLogRecord(new MinMaxServiceLeaseSetLogObj(leaseDuration));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long getMinMaxServiceLease() throws RemoteException {
  concurrentObj.readLock();
  try {
      return minMaxServiceLease;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setMinMaxEventLease(long leaseDuration)
  throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      if (leaseDuration > MAX_LEASE)
    throw new IllegalArgumentException("max duration exceeded");
      minMaxEventLease = leaseDuration;
      computeMaxLeases();
      addLogRecord(new MinMaxEventLeaseSetLogObj(leaseDuration));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long getMinMaxEventLease() throws RemoteException {
  concurrentObj.readLock();
  try {
      return minMaxEventLease;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setMinRenewalInterval(long interval) throws RemoteException {
  concurrentObj.writeLock();
  try {
      if (interval > MAX_RENEW)
    throw new IllegalArgumentException("max interval exceeded");
      minRenewalInterval = interval;
      computeMaxLeases();
      addLogRecord(new MinRenewalIntervalSetLogObj(interval));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public long getMinRenewalInterval() throws RemoteException {
  concurrentObj.readLock();
  try {
      return minRenewalInterval;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setSnapshotWeight(float weight) throws RemoteException {
  concurrentObj.writeLock();
  try {
      snapshotWt = weight;
      addLogRecord(new SnapshotWeightSetLogObj(weight));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public float getSnapshotWeight() throws RemoteException {
  concurrentObj.readLock();
  try {
      return snapshotWt;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setLogToSnapshotThreshold(int threshold) throws RemoteException
    {
  concurrentObj.writeLock();
  try {
      logToSnapshotThresh = threshold;
      addLogRecord(new LogToSnapshotThresholdSetLogObj(threshold));
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public int getLogToSnapshotThreshold() throws RemoteException {
  concurrentObj.readLock();
  try {
      return logToSnapshotThresh;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public String getStorageLocation() throws RemoteException {
  concurrentObj.readLock();
  try {
      return logDirname;
  } finally {
      concurrentObj.readUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void setStorageLocation(String location)
  throws IOException, RemoteException
    {
  if (location.equals(logDirname)) {
      return;
  }
  concurrentObj.writeLock();
  try {
       ReliableLog newLog = new ReliableLog(location,
             new LocalLogHandler());
      newLog.snapshot();
      ReliableLog oldLog = this.log;
      this.log = newLog;
      logDirname = location;
            MarshalledObject data = new MarshalledObject(location);
      if (activationID != null) {
    try {
//                      if (sharedActivationRef == null) {
            ActivationSystem sys = ActivationGroup.getSystem();
            ActivationDesc desc = sys.getActivationDesc
                                                               (activationID);
            desc = new ActivationDesc(desc.getGroupID(),
                    desc.getClassName(),
                    desc.getLocation(),
                    data,
                    desc.getRestartMode());
            sys.setActivationDesc(activationID, desc);
//                      } else {//in a shared activation group
//                          sharedActivationRef.setInitializationData(activationID,
//                                                                    data);
//                      }//endif
    } catch (ActivationException e) {
        throw new LogException("activation system problem", e);
    }
      }
      oldLog.deletePersistentStore();
  } finally {
      concurrentObj.writeUnlock();
  }
    }

    // This method's javadoc is inherited from an interface of this class
    public void destroy() {
  (new DestroyThread()).start();
    }

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

    /**
     * Add conc as a concrete class of the given type and all supertypes,
     * if it isn't already there.  (It might already be there due to
     * diamond merges in the interface hierarchy.)  This would be a method on
     * ServiceType, but we want to minimize code downloaded into the client.
     */
    private static void addConcreteClass(ServiceType type, ServiceType conc) {
  ServiceType[] types = type.getConcreteClasses();
  if (indexOf(types, conc) >= 0)
      return;
  type.setConcreteClasses((ServiceType[])arrayAdd(types, conc));
  ServiceType[] ifaces = type.getInterfaces();
  for (int i = ifaces.length; --i >= 0; ) {
      addConcreteClass(ifaces[i], conc);
  }
  ServiceType sup = type.getSuperclass();
  if (sup != null)
      addConcreteClass(sup, conc);
    }

    /**
     * Delete conc as a concrete class of the given type and all supertypes,
     * if it's there.  (It might already be delete due to diamond merges
     * in the interface hierarchy.)  This would be a method on ServiceType,
     * but we want to minimize code downloaded into the client.
     */
    private static void deleteConcreteClass(ServiceType type, ServiceType conc)
    {
  ServiceType[] types = type.getConcreteClasses();
  int i = indexOf(types, conc);
  if (i >= 0) {
      type.setConcreteClasses((ServiceType[])arrayDel(types, i));
      ServiceType[] ifaces = type.getInterfaces();
      for (int j = ifaces.length; --j >= 0; ) {
    deleteConcreteClass(ifaces[j], conc);
      }
      ServiceType sup = type.getSuperclass();
      if (sup != null)
    deleteConcreteClass(sup, conc);
  }
    }

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

    /**
     * Sends the given packet data on the given <code>MulticastSocket</code>
     * through each of the network interfaces corresponding to elements of
     * the given array of IP addresses. If the given array of IP addresses
     * is <code>null</code> or empty, then the data will be sent through only
     * the default network interface.
     *
     * @param mcSocket   the <code>MulticastSocket</code> on which the data
     *                   will be sent
     * @param packet     <code>DatagramPacket</code> array whose elements are
     *                   the data to send
     * @param addresses  <code>InetAddress</code> array whose elements
     *                   represent the Internet Protocol (IP) addresses
     *                   corresponding to the network interfaces (NICs)
     *                   through which the multicast data should be sent
     *
     * @throws java.io.InterruptedIOException
     */
    private static void sendPacketByNIC(MulticastSocket mcSocket,
                                        DatagramPacket[] packet,
                                        InetAddress[] addresses)
                                                 throws InterruptedIOException
    {
        if( (addresses != null) && (addresses.length > 0) ) {
            for(int i=0;i<addresses.length;i++) {
                try {
                    mcSocket.setInterface(addresses[i]);
                } catch(SocketException e) {
                    continue;//skip to next interface address
                }
                sendPacket(mcSocket,packet);
            }//end loop
        } else {//use default interface
            sendPacket(mcSocket,packet);
        }//endif
    }//end sendPacketByNIC

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

    /**
     * Retrieves and parses the <code>-Dnet.jini.discovery.interface</code>
     * system property, converting each parsed value to an instance of
     * <code>InetAddress</code>, and returning the results of each conversion
     * in an array.
     *
     * @return <code>InetAddress</code> array in which each element represents
     *         the Internet Protocol (IP) address corresponding to a network
     *         interface.
     *
     * @throws java.net.UnknownHostException
     */
    private static InetAddress[] getNICAddresses() throws UnknownHostException
    {
        String str = null;
  try {
      str = System.getProperty("net.jini.discovery.interface");
  } catch (SecurityException e) { /* ignore */ }
        if (str == null) return null;
        InetAddress[] addrs = null;
        String delimiter = ",";
        StringTokenizer st = null;
        st = new StringTokenizer(str,delimiter);
        int n = st.countTokens();
        if (n > 0) {
            addrs = new InetAddress[n];
            for(int i=0;((st.hasMoreTokens())&&(i<n));i++) {
                addrs[i] = InetAddress.getByName(st.nextToken());
            }
            return addrs;
        } else {
            return addrs;
        }
    }//end getNICAddresses

    /**
     * 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);
  ServiceType type = reg.item.serviceType;
  ArrayList regs = (ArrayList)serviceByType.get(type);
  if (regs == null) {
      regs = new ArrayList(3);
      serviceByType.put(type, regs);
      addConcreteClass(type, type);
  }
  regs.add(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);
  ServiceType type = item.serviceType;
  ArrayList regs = (ArrayList)serviceByType.get(type);
  regs.remove(regs.indexOf(reg));
  if (regs.isEmpty()) {
      serviceByType.remove(type);
      deleteConcreteClass(type, type);
  }
  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 num = eclass.getNumInstances();
  if (num == 0)
      entryClasses.add(eclass);
  eclass.setNumInstances(num + 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 num = eclass.getNumInstances() - 1;
  if (num == 0)
      entryClasses.remove(entryClasses.indexOf(eclass));
  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 TypeItemIter(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 all concrete classes that are equal to or subclasses of
     * all of the types in an array.
     */
    private ServiceType[] matchingConcreteClasses(ServiceType[] types) {
  if (isEmpty(types))
      return objectServiceType.getConcreteClasses();
  ServiceType[] stypes = types[0].getConcreteClasses();
  if (types.length > 1) {
      for (int i = stypes.length; --i >= 0; ) {
    if (!matchType(types, stypes[i]))
        stypes = (ServiceType[])arrayDel(stypes, i);
      }
  }
  return stypes;
    }

    /** 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();
    }

    /** Return any valid codebase for a concrete service type. */
    private String pickCodebase(ServiceType stype, long now)
  throws ClassNotFoundException
    {
  ArrayList svcs = (ArrayList)serviceByType.get(stype);
  for (int i = svcs.size(); --i >= 0; ) {
      SvcReg reg = (SvcReg)svcs.get(i);
      if (reg.leaseExpiration > now)
    return reg.item.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) {
  try {
      try {
    socket.setSoTimeout(unicastTimeout);
    IncomingUnicastRequest req =
        new IncomingUnicastRequest(socket.getInputStream());
    OutgoingUnicastResponse.marshal(socket.getOutputStream(),
            proxy, memberGroups);
      } finally {
    socket.close();
      }
  } catch (IOException e) {
  }
    }

    /** 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) {
    }
      }
  }
    }

    /** Initialization common to both activatable and transient instances. */
    private void init() throws IOException {
  /* have to create these here because of checked exceptions */
  objectServiceType = resolver.resolve(new ServiceType(Object.class,
                   null, null));
  String host = System.getProperty("java.rmi.server.hostname");
  if (host == null)
      host = InetAddress.getLocalHost().getHostName();
  log = new ReliableLog(logDirname, new LocalLogHandler());
  inRecovery = true;
  resolver.setInRecovery(true);
  log.recover();
  resolver.setInRecovery(false);
  inRecovery = false;
  computeMaxLeases();
        nicAddresses = getNICAddresses();
  unicaster = new UnicastThread(unicastPort);
  myLocator = new LookupLocator(host, unicaster.port);
  multicaster = new MulticastThread();
  announcer = new AnnounceThread();
  if (myServiceID == null)
      myServiceID = newServiceID();
  proxy = new RegistrarProxy((Registrar)RemoteObject.toStub(this),
           myServiceID);
  if (serviceByID.isEmpty()) {
      /* register myself */
      lookupAttrs = new Entry[]{new ServiceInfo(PRODUCT, MANUFACTURER,
                  VENDOR, VERSION,
                  "", ""),
              new BasicServiceType("Lookup")};
      Item item = new Item(new ServiceItem(myServiceID,
             proxy,
             lookupAttrs));
      resolver.resolve(item);
      SvcReg reg = new SvcReg(item, 0, Long.MAX_VALUE);
      addService(reg);
  } else {
      SvcReg reg = (SvcReg)serviceByID.get(myServiceID);
      lookupAttrs = EntryRep.toEntry(reg.item.attributeSets);
      /* delete whatever we can't reconstitute */
      for (int i = lookupAttrs.length; --i >= 0; ) {
    if (lookupAttrs[i] == null) {
        lookupAttrs = (Entry[])arrayDel(lookupAttrs, i);
        EntryRep set = reg.item.attributeSets[i];
        deleteSet(reg.item, i);
        deleteAttrs(reg, set, true);
    }
      }
  }
  log.snapshot();
  discoer = new LookupDiscoveryManager(lookupGroups, lookupLocators,
               null);
  joiner = new JoinManager(proxy, lookupAttrs, myServiceID,
         discoer, null);
  /* start up all the daemon threads */
  serviceExpirer.start();
  eventExpirer.start();
  unicaster.start();
  multicaster.start();
  announcer.start();
        snapshotter.start();
    }

    /** The code that does the real work of register. */
    private ServiceRegistration registerDo(Item nitem, long leaseDuration)
    {
  resolver.resolve(nitem);
  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 */
      ArrayList svcs = (ArrayList)serviceByType.get(nitem.serviceType);
      if (svcs != null) {
    for (int i = svcs.size(); --i >= 0; ) {
        SvcReg reg = (SvcReg)svcs.get(i);
        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);
  }
  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 new Registration(this, new ServiceLease(this,
                   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 MarshalledObject lookupDo(Template tmpl)
    {
  resolver.resolve(tmpl);
  if (isEmpty(tmpl.serviceTypes) || tmpl.serviceID != null)
  {
      ItemIter iter = matchingItems(tmpl);
      if (iter.hasNext())
    return iter.next().service;
      return null;
  }
  ServiceType[] types = matchingConcreteClasses(tmpl.serviceTypes);
  long now = System.currentTimeMillis();
  int tlen = types.length;
  if (tlen == 0)
      return null;
  int trand = Math.abs(random.nextInt()) % tlen;
  for (int i = 0; i < tlen; i++) {
      ArrayList svcs = (ArrayList)serviceByType.get(
              types[(i + trand) % tlen]);
      int slen = svcs.size();
      int srand = Math.abs(random.nextInt()) % slen;
      for (int j = 0; j < slen; j++) {
    SvcReg reg = (SvcReg)svcs.get((j + 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");
  resolver.resolve(tmpl);
  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)
    {
  resolver.resolve(tmpl);
  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");
  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,
             new EventLease(this, 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)
    {
  resolver.resolve(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)
    {
  resolver.resolve(tmpl);
  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)
    {
  resolver.resolve(tmpl);
  ArrayList classes = new ArrayList();
  ArrayList codebases = new ArrayList();
  if (tmpl.serviceID == null && isEmpty(tmpl.attributeSetTemplates)) {
      long now = System.currentTimeMillis();
      ServiceType[] types = matchingConcreteClasses(tmpl.serviceTypes);
      for (int i = types.length; --i >= 0; ) {
    try {
        addTypes(classes, codebases, tmpl.serviceTypes, prefix,
           types[i], pickCodebase(types[i], now));
    } catch (ClassNotFoundException e) {
    }
      }
  } 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,
         long leaseID,
         EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  resolver.resolve(attrSets);
  long now = System.currentTimeMillis();
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null ||
      reg.leaseID != 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,
            long leaseID,
            EntryRep[] attrSetTmpls,
            EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  resolver.resolve(attrSetTmpls);
  resolver.resolveWithNulls(attrSets);
  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 != 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,
         long leaseID,
         EntryRep[] attrSets)
  throws UnknownLeaseException
    {
  resolver.resolve(attrSets);
  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 != 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, long 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 != 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,
             long 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,
              long 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 != 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,
              long leaseID,
              long renewExpiration)
    {
  SvcReg reg = (SvcReg)serviceByID.get(serviceID);
  if (reg == null || reg.leaseID != 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, long 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,
           long 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,
            long 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 != 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,
            long leaseID,
            long renewExpiration)
    {
  EventReg reg = (EventReg)eventByID.get(new Long(eventID));
  if (reg == null || reg.leaseID != 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,
               long[] 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,
        long[] 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, long[] 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 UUID */
    private ServiceID newServiceID() {
  secRand.nextBytes(secRandBuf16);
  secRandBuf16[6] &= 0x0f;
  secRandBuf16[6] |= 0x40; /* version 4 */
  secRandBuf16[8] &= 0x3f;
  secRandBuf16[8] |= 0x80; /* IETF variant */
  secRandBuf16[10] |= 0x80; /* multicast bit */
  long mostSig = 0;
  for (int i = 0; i < 8; i++) {
      mostSig = (mostSig << 8) | (secRandBuf16[i] & 0xff);
  }
  long leastSig = 0;
  for (int i = 8; i < 16; i++) {
      leastSig = (leastSig << 8) | (secRandBuf16[i] & 0xff);
  }
  return new ServiceID(mostSig, leastSig);
    }

    /** Generate a new lease id */
    private long newLeaseID() {
  secRand.nextBytes(secRandBuf8);
  long id = 0;
  for (int i = 8; --i >= 0; ) {
      id = (id << 8) + (secRandBuf8[i] & 0xFF);
  }
  return id;
    }

    /**
     * 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 configuration 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>
     * 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(RegistrarImpl.class.getName());
  stream.writeInt(LOG_VERSION);
  stream.writeObject(myServiceID);
  stream.writeLong(eventID);
  stream.writeInt(logToSnapshotThresh);
  stream.writeFloat(snapshotWt);
  stream.writeLong(minMaxServiceLease);
  stream.writeLong(minMaxEventLease);
  stream.writeLong(minRenewalInterval);
  stream.writeInt(unicastPort);
  stream.writeObject(memberGroups);
  stream.writeObject(lookupGroups);
  stream.writeObject(lookupLocators);
  for (Iterator iter = serviceByID.values().iterator(); iter.hasNext(); )
  {
      stream.writeObject(iter.next());
  }
  stream.writeObject(null);
  for (Iterator iter = eventByID.values().iterator(); iter.hasNext(); )
  {
      stream.writeObject(iter.next());
  }
  stream.writeObject(null);
  stream.flush();
    }

    /**
     * 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 configuration parameters
     * <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 (!RegistrarImpl.class.getName().equals(stream.readUTF()))
      throw new IOException("log from wrong implementation");
  if (stream.readInt() != LOG_VERSION)
      throw new IOException("wrong log format version");
  myServiceID = (ServiceID)stream.readObject();
  eventID = stream.readLong();
  logToSnapshotThresh = stream.readInt();
  snapshotWt = stream.readFloat();
  minMaxServiceLease = stream.readLong();
  minMaxEventLease = stream.readLong();
  minRenewalInterval = stream.readLong();
  unicastPort = stream.readInt();
  memberGroups = (String[])stream.readObject();
  lookupGroups = (String[])stream.readObject();
  lookupLocators = (LookupLocator[])stream.readObject();
  SvcReg sReg;
  while ((sReg = (SvcReg)stream.readObject()) != null) {
      resolver.resolve(sReg.item);
      addService(sReg);
  }

  EventReg eReg;
  while ((eReg = (EventReg)stream.readObject()) != null) {
      resolver.resolve(eReg.tmpl);
      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 value of the minMaxServiceLease was set
     * <li> the value of the minMaxEventLease was set
     * <li> the value of the minRenewalInterval was set
     * <li> the weight factor used to determine when to take a snapshot was set
     * <li> the threshold used to determine when to take a snapshot 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) {
  try {
      log.update(rec, true);
      if (++logFileSize >= logToSnapshotThresh) {
    int snapshotSize = serviceByID.size() + eventByID.size();
    if ((float)logFileSize >= snapshotWt*((float)snapshotSize)) {
                    concurrentObj.waiterNotify(snapshotNotifier);
    }
      }
  } catch (Exception e) {
      /* if log updating fails, then one of the following must be done:
       *   -- output the problem to a separate file and exit()
       *   -- output the problem to a separate file and continue
       *   -- set an "I have a problem" attribute & send notification
       * XXX this issue will be addressed at a later time
       */
      if (!Thread.currentThread().isInterrupted()) {
    e.printStackTrace();
      }
  }
    }
}
TOP

Related Classes of com.sun.jini.test.share.reggie.RegistrarImpl$ItemIter

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.