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

import com.sun.jini.fiddler.FiddlerAdmin;
import com.sun.jini.fiddler.ProxyUtil;

import com.sun.jini.admin.DestroyAdmin;
import com.sun.jini.proxy.ConstrainableProxyUtil;

import net.jini.admin.JoinAdmin;
import net.jini.id.ReferentUuid;
import net.jini.id.ReferentUuids;
import net.jini.id.Uuid;
import net.jini.security.proxytrust.ProxyTrustIterator;
import net.jini.security.proxytrust.SingletonProxyTrustIterator;

import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;

import javax.security.auth.Subject;

import java.lang.reflect.Method;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.rmi.RemoteException;

/**
 * This class is a proxy providing access to the methods of an implementation
 * of the lookup discovery service which allow the administration of the
 * service. Clients only see instances of this class via the FiddlerAdmin
 * interface.
 *
 * @author Sun Microsystems, Inc.
 *
 */
class FiddlerAdminProxy implements FiddlerAdmin, ReferentUuid, Serializable {

    private static final long serialVersionUID = 2L;

    /**
     * The reference through which communication occurs between the
     * client-side and the server-side of the lookup discovery service
     *
     * @serial
     */
    final Fiddler server;
    /** 
     * The unique identifier assigned to the current instance of this 
     * proxy class by the lookup discovery service. This ID is used to
     * determine equality between proxies.
     *
     * @serial
     */
    final Uuid proxyID;

    /**
     * Public static factory method that creates and returns an instance of 
     * <code>FiddlerAdminProxy</code>. If the server associated with this proxy
     * implements <code>RemoteMethodControl</code>, then the object returned by
     * this method will also implement <code>RemoteMethodControl</code>.
     * 
     * @param server  reference to the server object through which 
     *                communication occurs between the client-side and
     *                server-side of the associated service.
     * @param proxyID the unique identifier assigned by the service to each
     *                instance of this proxy
     * 
     * @return an instance of <code>FiddlerAdminProxy</code> that implements
     *         <code>RemoteMethodControl</code> if the given <code>server</code>
     *         does.
     */
    public static FiddlerAdminProxy createAdminProxy(Fiddler server,
                                                     Uuid proxyID)
    {
        if(server instanceof RemoteMethodControl) {
            return new ConstrainableFiddlerAdminProxy(server, proxyID, null);
        } else {
            return new FiddlerAdminProxy(server, proxyID);
        }//endif
    }//end createAdminProxy

    /**
     * Constructs a new instance of FiddlerAdminProxy.
     *
     * @param server  reference to the server object through which 
     *                communication occurs between the client-side and
     *                server-side of the associated service
     * @param proxyID the unique identifier assigned by the service to each
     *                instance of this proxy
     */
    private FiddlerAdminProxy(Fiddler server, Uuid proxyID) {
	this.server  = server;
	this.proxyID = proxyID;
    }//end constructor

    /* *** Methods of com.sun.jini.fiddler.FiddlerAdmin *** */

    /**
     * Changes the least upper bound applied to all lease durations granted
     * by the lookup discovery service.
     * <p>
     * This method is a mechanism for an entity with the appropriate
     * privileges to administratively change the value of the least upper
     * bound that will be applied by the Fiddler implementation of the lookup
     * discovery service when determining the duration to assign to the lease
     * on a requested registration.
     *
     * @param newBound <code>long</code> value representing the new least
     *        upper bound (in milliseconds) on the set of all possible
     *        lease durations that may be granted
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         bound value may or may not have been changed successfully.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#setLeaseBound
     */
    public void setLeaseBound(long newBound) throws RemoteException {
        server.setLeaseBound(newBound);
    }
    /**
     * Retrieves the least upper bound applied to all lease durations granted
     * by the lookup discovery service.
     *
     * @return <code>long</code> value representing the current least
     *         upper bound (in milliseconds) on the set of all possible
     *         lease durations that may be granted
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#setLeaseBound
     */
    public long getLeaseBound() throws RemoteException {
        return server.getLeaseBound();
    }

    /**
     * Change the weight factor applied by the lookup discovery service
     * to the snapshot size during the test to determine whether or not
     * to take a "snapshot" of the system state.
     *
     * @param weight weight factor for snapshot size
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         weight factor may or may not have been changed successfully.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#setPersistenceSnapshotWeight
     */
    public void setPersistenceSnapshotWeight(float weight)
                                                     throws RemoteException
    {
        server.setPersistenceSnapshotWeight(weight);
    }

    /**
     * Retrieve the weight factor applied by the lookup discovery service
     * to the snapshot size during the test to determine whether or not to
     * take a "snapshot" of the system state.
     * 
     * @return float value corresponding to the weight factor for snapshot
     *         size
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#getPersistenceSnapshotWeight
     */
    public float getPersistenceSnapshotWeight() throws RemoteException {
        return server.getPersistenceSnapshotWeight();
    }

    /**
     * Change the value of the size threshold of the snapshot; which is
     * employed by the lookup discovery service in the test to determine
     * whether or not to take a "snapshot" of the system state.
     *
     * @param threshold size threshold for taking a snapshot
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         threshold may or may not have been changed successfully.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#setPersistenceSnapshotThreshold
     */
    public void setPersistenceSnapshotThreshold(int threshold)
                                                        throws RemoteException
    {
        server.setPersistenceSnapshotThreshold(threshold);
    }

    /**
     * Retrieve the value of the size threshold of the snapshot; which is
     * employed by the lookup discovery service in the test to determine
     * whether or not to take a "snapshot" of the system state.
     * 
     * @return int value corresponding to the size threshold of the snapshot
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see com.sun.jini.fiddler.FiddlerAdmin#getPersistenceSnapshotThreshold
     */
    public int getPersistenceSnapshotThreshold() throws RemoteException {
        return server.getPersistenceSnapshotThreshold();
    }

    /* *** Methods of net.jini.admin.JoinAdmin *** */

    /** 
     * Get the current attribute sets for the lookup discovery service. 
     * 
     * @return array of net.jini.core.entry.Entry containing the current
     *         attribute sets for the lookup discovery service
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see net.jini.admin.JoinAdmin#getLookupAttributes
     */
    public Entry[] getLookupAttributes() throws RemoteException {
	return server.getLookupAttributes();
    }

    /** 
     * Add attribute sets to the current set of attributes associated
     * with the lookup discovery service. The resulting set will be used
     * for all future registrations with lookup services. The new attribute
     * sets are also added to the lookup discovery service's attributes
     * on each lookup service with which the lookup discovery service
     * is currently registered.
     *
     * @param  attrSets array of net.jini.core.entry.Entry containing the
     *         attribute sets to add
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         attributes may or may not have been added successfully.
     *
     * @see net.jini.admin.JoinAdmin#addLookupAttributes
     */
    public void addLookupAttributes(Entry[] attrSets) throws RemoteException {
	server.addLookupAttributes(attrSets);
    }

    /** 
     * Modify the current set of attributes associated with the lookup
     * discovery service. The resulting set will be used for all future
     * registrations with lookup services. The same modifications are 
     * also made to the lookup discovery service's attributes on each
     * lookup service with which the lookup discovery service is currently
     * registered.
     *
     * @param  attrSetTemplates  array of net.jini.core.entry.Entry containing
     *         the templates for matching attribute sets
     * @param  attrSets array of net.jini.core.entry.Entry containing the
     *         modifications to make to matching sets
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         attributes may or may not have been modified successfully.
     *
     * @see net.jini.admin.JoinAdmin#modifyLookupAttributes
     */
    public void modifyLookupAttributes(Entry[] attrSetTemplates,
				       Entry[] attrSets)
	throws RemoteException
    {
	server.modifyLookupAttributes(attrSetTemplates, attrSets);
    }

    /** 
     * Get the names of the groups whose members are lookup services the
     * lookup discovery services wishes to register with (join).
     * 
     * @return String array containing the names of the groups whose members
     *         are lookup services the lookup discovery service wishes to
     *         join.
     * <p>
     *         If the array returned is empty, the lookup discovery service
     *         is configured to join no groups. If null is returned, the
     *         lookup discovery service is configured to join all groups.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see net.jini.admin.JoinAdmin#getLookupGroups
     */
    public String[] getLookupGroups() throws RemoteException {
	return server.getLookupGroups();
    }

    /** 
     * Add new names to the set consisting of the names of groups whose
     * members are lookup services the lookup discovery service wishes
     * to register with (join). Any lookup services belonging to the
     * new groups that the lookup discovery service has not yet registered
     * with, will be discovered and joined.
     *
     * @param  String array containing the names of the groups to add
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         group names may or may not have been added successfully.
     *
     * @see net.jini.admin.JoinAdmin#addLookupGroups
     */
    public void addLookupGroups(String[] groups) throws RemoteException {
	server.addLookupGroups(groups);
    }

    /** 
     * Remove a set of group names from lookup discovery service's managed
     * set of groups (the set consisting of the names of groups whose
     * members are lookup services the lookup discovery service wishes
     * to join). Any leases granted to the lookup discovery service by
     * lookup services that are not members of the groups whose names 
     * remain in the managed set will be cancelled at those lookup services.
     *
     * @param  String array containing the names of the groups to remove
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         group names may or may not have been removed successfully.
     *
     * @see net.jini.admin.JoinAdmin#removeLookupGroups
     */
    public void removeLookupGroups(String[] groups) throws RemoteException {
	server.removeLookupGroups(groups);
    }

    /** 
     * Replace the lookup discovery service's managed set of groups with a
     * new set of group names. Any leases granted to the lookup discovery
     * service by lookup services that are not members of the groups whose
     * names are in the new managed set will be cancelled at those lookup
     * services. Lookup services that are members of groups reflected in
     * the new managed set will be discovered and joined.
     *
     * @param  String array containing the names of the new groups
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         group names may or may not have been replaced successfully.
     *
     * @see net.jini.admin.JoinAdmin#setLookupGroups
     */
    public void setLookupGroups(String[] groups) throws RemoteException {
	server.setLookupGroups(groups);
    }

    /** 
     * Get the lookup discovery service's managed set of locators. The
     * managed set of locators is the set of LookupLocator objects
     * corresponding to the specific lookup services with which the lookup
     * discovery service wishes to register (join).
     * 
     * @return array of objects of type net.jini.core.discovery.LookupLocator,
     *         each of which corresponds to a specific lookup service the
     *         lookup discovery service wishes to join.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service.
     *
     * @see net.jini.admin.JoinAdmin#getLookupLocators
     */
    public LookupLocator[] getLookupLocators() throws RemoteException {
	return server.getLookupLocators();
    }

    /** 
     * Add a set of LookupLocator objects to the lookup discovery service's
     * managed set of locators. The managed set of locators is the set of
     * LookupLocator objects corresponding to the specific lookup services
     * with which the lookup discovery service wishes to register (join).
     * <p>
     * Any lookup services corresponding to the new locators that the lookup
     * discovery service has not yet joined, will be discovered and joined.
     *
     * @param  array of net.jini.core.discovery.LookupLocator objects to add
     *         to the managed set of locators
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         new locators may or may not have been added successfully.
     *
     * @see net.jini.admin.JoinAdmin#addLookupLocators
     */
    public void addLookupLocators(LookupLocator[] locators)
	throws RemoteException
    {
	server.addLookupLocators(locators);
    }

    /** 
     * Remove a set of LookupLocator objects from the lookup discovery
     * service's managed set of locators. The managed set of locators is the
     * set of LookupLocator objects corresponding to the specific lookup
     * services with which the lookup discovery service wishes to register
     * (join).
     * <p>
     * Note that any leases granted to the lookup discovery service by
     * lookup services that do not correspond to any of the locators
     * remaining in the managed set will be cancelled at those lookup
     * services.
     *
     * @param  array of net.jini.core.discovery.LookupLocator objects to
     *         remove from the managed set of locators
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         new locators may or may not have been removed successfully.
     *
     * @see net.jini.admin.JoinAdmin#removeLookupLocators
     */
    public void removeLookupLocators(LookupLocator[] locators)
	throws RemoteException
    {
	server.removeLookupLocators(locators);
    }

    /** 
     * Replace the lookup discovery service's managed set of locators with
     * a new set of locators. The managed set of locators is the set of
     * LookupLocator objects corresponding to the specific lookup services
     * with which the lookup discovery service wishes to register (join).
     * <p>
     * Note that any leases granted to the lookup discovery service by
     * lookup services whose corresponding locator is removed from the
     * managed set will be cancelled at those lookup services. The lookup
     * services corresponding to the new locators in the managed set
     * will be discovered and joined.
     *
     * @param  array of net.jini.core.discovery.LookupLocator objects with
     *         which to replace the current managed set of locators
     *         remove from the managed set of locators
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         locators in the managed set may or may not have been replaced
     *         successfully.
     *
     * @see net.jini.admin.JoinAdmin#setLookupLocators
     */
    public void setLookupLocators(LookupLocator[] locators)
	throws RemoteException
    {
	server.setLookupLocators(locators);
    }

    /* From net.jini.id.ReferentUuid */

    /** 
     * Returns the universally unique identifier that has been assigned to the
     * resource this proxy represents.
     *
     * @return the instance of <code>Uuid</code> that is associated with the
     *         resource this proxy represents. This method will not return
     *         <code>null</code>.
     *
     * @see net.jini.id.ReferentUuid
     */
    public Uuid getReferentUuid() {
        return proxyID;
    }

    /* *** Methods of com.sun.jini.admin.DestroyAdmin *** */

    /**
     * Destroy the lookup discovery service, if possible, including its
     * persistent storage. This method will typically spawn a separate
     * thread to do the actual work asynchronously, so a successful
     * return from this method usually does not mean that the service
     * has been destroyed.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         lookup discovery service. When this exception does occur, the
     *         lookup discovery service may or may not have been successfully
     *         destroyed.
     *
     * @see com.sun.jini.admin.DestroyAdmin#destroy
     */
    public void destroy() throws RemoteException {
	server.destroy();
    }

    /* *** HashCode and Equals for this class *** */

    /** 
     * For any instance of this class, returns the hashcode value generated
     * by the hashCode method of the proxy ID associated with the current
     * instance of this proxy.
     *
     * @return <code>int</code> value representing the hashcode for an
     *         instance of this class.
     */
    public int hashCode() {
	return proxyID.hashCode();
    }

    /** 
     * For any instance of this class, indicates whether the object input
     * to this method is equal to the current instance of this class; where
     * equality of administrative proxies to a lookup discovery service is
     * defined by reference equality. That is, two proxies are equal if they
     * reference (are proxies to) the same backend server.
     *
     * @param obj reference to the object that is to be compared to the
     *            object on which this method is invoked.
     *
     * @return <code>true</code> if the object input is referentially
     *         equal to the object on which this method is invoked;
     *         <code>false</code> otherwise.
     */
    public boolean equals(Object obj) {
	return ReferentUuids.compare(this,obj);
    }

    /** When an instance of this class is deserialized, this method is
     *  automatically invoked. This implementation of this method validates
     *  the state of the deserialized instance.
     *
     * @throws <code>InvalidObjectException</code> if the state of the
     *         deserialized instance of this class is found to be invalid.
     */
    private void readObject(ObjectInputStream s)  
                               throws IOException, ClassNotFoundException
    {
        s.defaultReadObject();
        /* Verify server */
        if(server == null) {
            throw new InvalidObjectException("FiddlerAdminProxy.readObject "
                                             +"failure - server "
                                             +"field is null");
        }//endif
        /* Verify proxyID */
        if(proxyID == null) {
            throw new InvalidObjectException("FiddlerAdminProxy.readObject "
                                             +"failure - proxyID "
                                             +"field is null");
        }//endif
    }//end readObject

    /** During deserialization of an instance of this class, if it is found
     *  that the stream contains no data, this method is automatically
     *  invoked. Because it is expected that the stream should always 
     *  contain data, this implementation of this method simply declares
     *  that something must be wrong.
     *
     * @throws <code>InvalidObjectException</code> to indicate that there
     *         was no data in the stream during deserialization of an
     *         instance of this class; declaring that something is wrong.
     */
    private void readObjectNoData() throws InvalidObjectException {
        throw new InvalidObjectException("no data found when attempting to "
                                         +"deserialize FiddlerAdminProxy "
                                         +"instance");
    }//end readObjectNoData

    /** The constrainable version of the class <code>FiddlerAdminProxy</code>. 
     *  <p>
     *  When a client obtains an instance of this proxy class, the client
     *  should not attempt to use the proxy until the client is assured
     *  that the proxy can be trusted. In addition to implementing the
     *  methods and mechanisms required by <code>RemoteMethodControl</code>, 
     *  this class - in conjunction with the service's
     *  <code>ProxyVerifier</code> class, helps provide a mechanism
     *  for verifying trust in the proxy on behalf of a client.
     *  <p>
     *  In order to verify that an instance of this class is trusted, 
     *  trust must be verified in all subsidiary objects (contained in that
     *  instance) through which the client ultimately makes calls (local or
     *  remote).  With respect to this class, the <code>server</code> field
     *  is a proxy object through which the client makes remote calls to the 
     *  service's backend. Therefore, trust in that object must be
     *  verified. Additionally, this class also contains a field of type
     *  <code>Uuid</code> (<code>proxyID</code> which should be
     *  tested for trust. Consider the following diagram:
     *  <p>
     *  <pre>
     *    FiddlerAdminProxy {
     *        Fiddler server
     *        Uuid proxyID
     *    }//end FiddlerAdminProxy
     *  </pre>
     *  <p>
     *  Thus, in order to verify that an instance of this class is trusted,
     *  trust must be verified in the following objects from the diagram
     *  above:
     *  <ul><li> server
     *      <li> proxyID
     *  </ul>
     *
     *  When a client obtains an instance of this proxy class, the
     *  deserialization process which delivers the proxy to the client
     *  invokes the <code>readObject</code> method of this class. Part of
     *  trust verfication is performed in the <code>readObject</code> method,
     *  and part is performed when the client prepares the proxy. Thus, this
     *  class' participation in the trust verification process can be
     *  summarized as follows:
     *  <p>
     *  <ul>
     *    <li> server
     *      <ul>
     *        <li> readObject
     *          <ul>
     *            <li> verify server != null
     *            <li> verify server implements RemoteMethodControl
     *            <li> verify server's method constraints are the same
     *                 as those placed on the corresponding public Remote
     *                 methods of its outer proxy class
     *          </ul>
     *        <li> proxy preparation
     *          <ul>
     *            <li> Security.verifyObjectTrust() which calls
     *            <li> ProxyVerifier.isTrustedObject() which calls
     *            <li> canonicalServerObject.checkTrustEquivalence(server)
     *                 (whose implementation is supplied by the particular 
     *                 RMI implementation that was used to export the server)
     *          </ul>
     *      </ul>
     *    <li> proxyID
     *      <ul><li> readObject
     *          <ul><li> verify proxyID != null</ul>
     *      </ul>
     *  </ul>
     *
     * @since 2.0
     */
    static final class ConstrainableFiddlerAdminProxy 
                                                extends FiddlerAdminProxy
                                                implements RemoteMethodControl
    {
        static final long serialVersionUID = 2L;

        /* Array containing element pairs in which each pair of elements
         * represents a correspondence 'mapping' between two methods having
         * the following characteristics:
         *  - the first element in the pair is one of the public, remote
         *    method(s) that may be invoked by the client through the proxy
         *    class that this class extends
         *  - the second element in the pair is the method, implemented
         *    in the backend server class, that is ultimately executed in
         *    the server's backend when the client invokes the corresponding
         *    method in this proxy
         */
        private static final Method[] methodMapArray = 
        {
            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupAttributes", new Class[] {} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupAttributes", new Class[] {} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupAttributes",
                                new Class[] {Entry[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupAttributes",
                                new Class[] {Entry[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "modifyLookupAttributes",
                                new Class[] {Entry[].class,
                                             Entry[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "modifyLookupAttributes",
                                new Class[] {Entry[].class,
                                             Entry[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupGroups", new Class[] {} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupGroups", new Class[] {} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupGroups",
                                new Class[] {String[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupGroups",
                                new Class[] {String[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "removeLookupGroups",
                                new Class[] {String[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "removeLookupGroups",
                                new Class[] {String[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "setLookupGroups",
                                new Class[] {String[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "setLookupGroups",
                                new Class[] {String[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupLocators", new Class[] {} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "getLookupLocators", new Class[] {} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupLocators",
                                new Class[] {LookupLocator[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "addLookupLocators",
                                new Class[] {LookupLocator[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "removeLookupLocators",
                                new Class[] {LookupLocator[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "removeLookupLocators",
                                new Class[] {LookupLocator[].class} ),

            ProxyUtil.getMethod(JoinAdmin.class,
                                "setLookupLocators",
                                new Class[] {LookupLocator[].class} ),
            ProxyUtil.getMethod(JoinAdmin.class,
                                "setLookupLocators",
                                new Class[] {LookupLocator[].class} ),


            ProxyUtil.getMethod(DestroyAdmin.class,
                                "destroy", new Class[] {} ),
            ProxyUtil.getMethod(DestroyAdmin.class,
                                "destroy", new Class[] {} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setLeaseBound",
                                new Class[] {long.class} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setLeaseBound",
                                new Class[] {long.class} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getLeaseBound",
                                new Class[] {} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getLeaseBound",
                                new Class[] {} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setPersistenceSnapshotWeight",
                                new Class[] {float.class} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setPersistenceSnapshotWeight",
                                new Class[] {float.class} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getPersistenceSnapshotWeight",
                                new Class[] {} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getPersistenceSnapshotWeight",
                                new Class[] {} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setPersistenceSnapshotThreshold",
                                new Class[] {int.class} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "setPersistenceSnapshotThreshold",
                                new Class[] {int.class} ),

            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getPersistenceSnapshotThreshold",
                                new Class[] {} ),
            ProxyUtil.getMethod(FiddlerAdmin.class,
                                "getPersistenceSnapshotThreshold",
                                new Class[] {} )
        };//end methodMapArray

        /** Client constraints placed on this proxy (may be <code>null</code>).
         *
         * @serial
         */
        private MethodConstraints methodConstraints;

        /** Constructs a new <code>ConstrainableFiddlerAdminProxy</code>
         *  instance.
         *  <p>
         *  For a description of all but the <code>methodConstraints</code>
         *  argument (provided below), refer to the description for the
         *  constructor of this class' super class.
         *
         *  @param methodConstraints the client method constraints to place on
         *                           this proxy (may be <code>null</code>).
         */
        private ConstrainableFiddlerAdminProxy
                                       (Fiddler server, 
                                        Uuid proxyID,
                                        MethodConstraints methodConstraints)
        {
            super( constrainServer(server, methodConstraints), proxyID);
	    this.methodConstraints = methodConstraints;
        }//end constructor

        /** Returns a copy of the given server proxy having the client method
         *  constraints that result after the specified method mapping is
         *  applied to the given client method constraints.
         */
        private static Fiddler constrainServer( Fiddler server,
                                                MethodConstraints constraints )
        {
            MethodConstraints newConstraints 
               = ConstrainableProxyUtil.translateConstraints(constraints,
                                                             methodMapArray);
            RemoteMethodControl constrainedServer = 
                ((RemoteMethodControl)server).setConstraints(newConstraints);

            return ((Fiddler)constrainedServer);
        }//end constrainServer

        /** Returns a new copy of this proxy class 
         *  (<code>ConstrainableFiddlerAdminProxy</code>) with its client
         *  constraints set to the specified constraints. A <code>null</code>
         *  value is interpreted as mapping all methods to empty constraints.
         */
        public RemoteMethodControl setConstraints
                                              (MethodConstraints constraints)
        {
            return (new ConstrainableFiddlerAdminProxy
                                              (server, proxyID, constraints));
        }//end setConstraints

        /** Returns the client constraints placed on the current instance
         *  of this proxy class (<code>ConstrainableFiddlerAdminProxy</code>).
         *  The value returned by this method can be <code>null</code>,
         *  which is interpretted as mapping all methods to empty constraints.
         */
        public MethodConstraints getConstraints() {
            return methodConstraints;
        }//end getConstraints

        /** Returns a proxy trust iterator that is used in 
         *  <code>ProxyTrustVerifier</code> to retrieve this object's
         *  trust verifier.
         */
        private ProxyTrustIterator getProxyTrustIterator() {
	    return new SingletonProxyTrustIterator(server);
        }//end getProxyTrustIterator

        /** Performs various functions related to the trust verification
         *  process for the current instance of this proxy class, as
         *  detailed in the description for this class.
         *
         * @throws <code>InvalidObjectException</code> if any of the
         *         requirements for trust verification (as detailed in the 
         *         class description) are not satisfied.
         */
        private void readObject(ObjectInputStream s)  
                                   throws IOException, ClassNotFoundException
        {
            /* Note that basic validation of the fields of this class was
             * already performed in the readObject() method of this class'
             * super class.
             */
            s.defaultReadObject();
            /* Verify the server and its constraints */
            ConstrainableProxyUtil.verifyConsistentConstraints
                                                       (methodConstraints,
                                                        server,
                                                        methodMapArray);
        }//end readObject

    }//end class ConstrainableFiddlerAdminProxy

}//end class FiddlerAdminProxy
