/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.jini.jeri;

import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.ProtocolException;
import java.rmi.ConnectIOException;
import java.rmi.MarshalException;
import java.rmi.RemoteException;
import java.rmi.UnexpectedException;
import java.rmi.UnmarshalException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.io.MarshalInputStream;
import net.jini.io.MarshalOutputStream;
import net.jini.io.UnsupportedConstraintException;
import net.jini.io.context.IntegrityEnforcement;
import net.jini.security.proxytrust.TrustEquivalence;


/**
 * A basic implementation of the <code>InvocationHandler</code> interface.
 * This invocation handler implements Java(TM) Remote Method Invocation
 * (Java RMI) call semantics when handling
 * a remote invocation to a remote object.
 *
 * <p><code>BasicInvocationHandler</code> instances contain an
 * <code>ObjectEndpoint</code>, optional client constraints, and
 * optional server constraints.  The client and server constraints
 * control the handling of remote methods, and they are represented as
 * {@link MethodConstraints} objects that map remote methods to
 * corresponding per-method constraints.
 *
 * <p>Invocation requests sent via the {@link #invoke invoke} method use
 * the protocol defined in that method.  This invocation handler also
 * assumes that the return value conforms to the protocol outlined in the
 * {@link BasicInvocationDispatcher#dispatch
 * BasicInvocationDispatcher.dispatch} method.
 *
 * @author	Sun Microsystems, Inc.
 * @see		BasicInvocationDispatcher
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * This implementation's {@link #invoke invoke} method throws {@link
 * IllegalArgumentException} if a remote invocation is to be made and
 * the <code>proxy</code> argument is an instance of an interface
 * whose binary name is
 * <code>javax.management.MBeanServerConnection</code> or any of the
 * names produced by the following procedure:
 *
 * <blockquote>
 *
 * For each resource named
 * <code>com/sun/jini/proxy/resources/InvocationHandler.moreProhibitedProxyInterfaces</code>
 * that is visible to the system class loader, the contents of the
 * resource are parsed as UTF-8 text to produce a list of interface
 * names.  The resource must contain a list of binary names of
 * interfaces, one per line.  Space and tab characters surrounding
 * each name, as well as blank lines, are ignored.  The comment
 * character is <tt>'#'</tt>; on each line, all characters starting
 * with the first comment character are ignored.
 *
 * </blockquote>
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.jeri.BasicInvocationHandler</code> to log
 * information at the following levels:
 *
 * <table summary="Describes what is logged by BasicInvocationHandler at
 *        various logging levels" border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Levels#FAILED FAILED} <td> exception thrown from final
 * attempt to communicate a remote call
 * 
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td> exception caught in
 * attempt to communicate a remote call
 *
 * <tr> <td> {@link Level#FINE FINE} <td> remote method being invoked
 *
 * <tr> <td> {@link Level#FINE FINE} <td> successful return of remote call
 *
 * <tr> <td> {@link Level#FINEST FINEST} <td> more detailed information on
 * the above (for example, actual argument and return values) 
 *
 * </table>
 **/
public class BasicInvocationHandler
    implements InvocationHandler, TrustEquivalence, Serializable
{
    private static final long serialVersionUID = -783920361025791412L;

    /**
     * invoke logger
     **/
    private static final Logger logger =
	Logger.getLogger("net.jini.jeri.BasicInvocationHandler");

    /** size of the method constraint cache (per instance) */
    private static final int CACHE_SIZE = 3;

    /**
     * The object endpoint for communicating with the remote object.
     *
     * @serial
     **/
    private final ObjectEndpoint oe;

    /**
     * The client constraints, or <code>null</code>.
     *
     * @serial
     **/
    private final MethodConstraints clientConstraints;

    /**
     * The server constraints, or <code>null</code>.
     *
     * @serial
     **/
    private final MethodConstraints serverConstraints;

    /*
     * The method constraint cache maps remote methods to their
     * combined client and server constraints:
     */

    /** lock guarding cacheIndex, methodCache, and constraintCache */
    private transient Object cacheLock = new Object();

    /** next index to (over)write in the method constraint cache */
    private transient int cacheIndex;

    /** method constraint cache keys (unused entries are null) */
    private transient Method[] methodCache;

    /** method constraint cache values (unused entries are null) */
    private transient InvocationConstraints[] constraintCache;

    /**
     * Creates a new <code>BasicInvocationHandler</code> with the
     * specified <code>ObjectEndpoint</code> and server constraints.
     *
     * <p>The client constraints of the created
     * <code>BasicInvocationHandler</code> will be <code>null</code>.
     *
     * @param	oe the <code>ObjectEndpoint</code> for this invocation handler
     * @param	serverConstraints the server constraints, or <code>null</code>
     *
     * @throws	NullPointerException if <code>oe</code> is <code>null</code>
     **/
    public BasicInvocationHandler(ObjectEndpoint oe,
				  MethodConstraints serverConstraints)
    {
	if (oe == null) {
	    throw new NullPointerException();
	}
	this.oe = oe;
	this.clientConstraints = null;
	this.serverConstraints = serverConstraints;
    }

    /**
     * Creates a new <code>BasicInvocationHandler</code> with the
     * specified client constraints and with the same
     * <code>ObjectEndpoint</code> and server constraints as the given
     * other <code>BasicInvocationHandler</code>.
     *
     * <p>This constructor is intended for use by the
     * <code>BasicInvocationHandler</code> implementation of the
     * {@link #setClientConstraints} method.  To create a copy of a
     * given <code>BasicInvocationHandler</code> with new client
     * constraints, use the {@link RemoteMethodControl#setConstraints
     * RemoteMethodControl.setConstraints} method on the containing
     * proxy.
     *
     * @param	other the <code>BasicInvocationHandler</code> to obtain the
     *		<code>ObjectEndpoint</code> and server constraints from
     * @param	clientConstraints the client constraints, or
     *		<code>null</code>
     *
     * @throws	NullPointerException if <code>other</code> is
     *		<code>null</code>
     **/
    public BasicInvocationHandler(BasicInvocationHandler other,
				  MethodConstraints clientConstraints)
    {
	this.oe = other.oe;
	this.clientConstraints = clientConstraints;
	this.serverConstraints = other.serverConstraints;
    }

    /**
     * Processes a method invocation made on the encapsulating
     * proxy instance, <code>proxy</code>, and returns the result.
     * This method is invoked when a method is invoked on a proxy
     * instance that this handler is associated with.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * as follows:
     *
     * <p>If <code>method</code> is one of the following methods, it
     * is processed as described below:
     *
     * <ul>
     *
     * <li>{@link Object#hashCode Object.hashCode}: Returns the hash
     * code value for the proxy.
     *
     * <li>{@link Object#equals Object.equals}: Returns
     * <code>true</code> if the argument (<code>args[0]</code>) is an
     * instance of a dynamic proxy class that implements the same
     * ordered set of interfaces as <code>proxy</code> and this
     * invocation handler is equal to the invocation handler of that
     * argument, and returns <code>false</code> otherwise.
     *
     * <li>{@link Object#toString Object.toString}: Returns a string
     * representation of the proxy.
     *
     * <li>{@link RemoteMethodControl#setConstraints
     * RemoteMethodControl.setConstraints}: Returns a new proxy
     * instance of the same class as <code>proxy</code> containing a
     * new invocation handler with the specified new client
     * constraints.  The new invocation handler is created by invoking
     * the {@link #setClientConstraints setClientConstraints} method
     * of this object with the specified client constraints
     * (<code>args[0]</code>).  An exception is thrown if
     * <code>proxy</code> is not an instance of a dynamic proxy class
     * containing this invocation handler.
     *
     * <li>{@link RemoteMethodControl#getConstraints
     * RemoteMethodControl.getConstraints}: Returns this
     * <code>BasicInvocationHandler</code>'s client constraints.
     *
     * <li>{@link TrustEquivalence#checkTrustEquivalence
     * TrustEquivalence.checkTrustEquivalence}: Returns
     * <code>true</code> if the argument (<code>args[0]</code>) is an
     * instance of a dynamic proxy class that implements the same
     * ordered set of interfaces as <code>proxy</code> and invoking
     * the {@link #checkTrustEquivalence checkTrustEquivalence} method
     * of this object with the invocation handler of that argument
     * returns <code>true</code>, and returns <code>false</code>
     * otherwise.
     *
     * </ul>
     *
     * <p>Otherwise, a remote call is made as follows:
     *
     * <p>The object endpoint's {@link ObjectEndpoint#newCall newCall}
     * method is invoked to obtain an {@link OutboundRequestIterator},
     * passing constraints obtained by combining the client and server
     * constraints for the specified remote method and making them
     * absolute.  If the returned iterator's {@link
     * OutboundRequestIterator#hasNext hasNext} method returns
     * <code>false</code>, then this method throws a {@link
     * ConnectIOException}.  Otherwise, the iterator is used to make
     * one or more attempts to communicate the remote call.  Each
     * attempt proceeds as follows:
     *
     * <blockquote>
     *
     * The iterator's {@link OutboundRequestIterator#next next} method
     * is invoked to obtain an {@link OutboundRequest OutboundRequest}
     * for the current attempt.  If <code>next</code> returns
     * normally, the request's {@link
     * OutboundRequest#getUnfulfilledConstraints
     * getUnfulfilledConstraints} method is invoked, and if the
     * returned requirements or preferences include {@link
     * Integrity#YES Integrity.YES}, object integrity is enforced for
     * the current remote call attempt.  If the returned requirements
     * include any constraint other than an {@link Integrity}
     * constraint, an {@link UnsupportedConstraintException} is
     * generated and, as described below, wrapped and handled like any
     * other <code>Exception</code> thrown from a remote call attempt.
     * Otherwise, the marshalling of the remote call proceeds with the
     * following steps in order:
     *
     * <ul>
     *
     * <li>A byte of value <code>0x00</code> is written to the request
     * output stream of the <code>OutboundRequest</code> to indicate
     * the version of the marshalling protocol being used.
     *
     * <li>A byte is written to specify object integrity enforcement:
     * <code>0x01</code> if object integrity is being enforced for
     * this remote call attempt, and <code>0x00</code> otherwise.
     *
     * <li>A client context collection is created containing an {@link
     * IntegrityEnforcement} element that reflects whether or not
     * object integrity is being enforced for this remote call
     * attempt.
     *
     * <li>The {@link #createMarshalOutputStream
     * createMarshalOutputStream} method is invoked, passing
     * <code>proxy</code>, <code>method</code>, the
     * <code>OutboundRequest</code>, and the client context, to create
     * the marshal output stream for marshalling the remote call.
     *
     * <li>The {@link #marshalMethod marshalMethod} method of this
     * invocation handler is invoked with <code>proxy</code>,
     * <code>method</code>, the marshal output stream, and the client
     * context.
     *
     * <li>The {@link #marshalArguments marshalArguments} method of
     * this invocation handler is invoked with <code>proxy</code>,
     * <code>method</code>, <code>args</code>, the marshal output
     * stream, and the client context.
     *
     * <li>The marshal output stream is closed.
     *
     * </ul>
     *
     * <p>Then the object endpoint's {@link ObjectEndpoint#executeCall
     * executeCall} method is invoked with the
     * <code>OutboundRequest</code>.  If <code>executeCall</code>
     * returns a <code>RemoteException</code>, then this method throws
     * that exception (and thus the remote call attempt iteration
     * terminates).  If <code>executeCall</code> returns
     * <code>null</code>, then the unmarshalling of the call response
     * proceeds as follows:
     *
     * <p>A byte is read from the response input stream of the
     * <code>OutboundRequest</code>:
     *
     * <ul>
     *
     * <li>If the byte is <code>0x00</code>, indicating a marshalling
     * protocol version mismatch, a {@link ProtocolException} is
     * generated and, as described below, wrapped and handled like any
     * other <code>Exception</code> thrown from a remote call attempt.
     *
     * <li>If the byte is <code>0x01</code>, indicating a normal
     * return, the {@link #createMarshalInputStream
     * createMarshalInputStream} method is invoked, passing
     * <code>proxy</code>, <code>method</code>, the
     * <code>OutboundRequest</code>, a <code>boolean</code> indicating
     * whether or not object integrity is being enforced, and the
     * client context, to create the marshal input stream for
     * unmarshalling the response, and the {@link #unmarshalReturn
     * unmarshalReturn} method of this invocation handler is invoked
     * with <code>proxy</code>, <code>method</code>, the marshal input
     * stream, and the client context.  This method returns the value
     * returned by <code>unmarshalReturn</code> (and thus the remote
     * call attempt iteration terminates).
     *
     * <li>If the byte is <code>0x02</code>, indicating an exceptional
     * return, a marshal input stream is created by calling the
     * <code>createMarshalInputStream</code> method as described for
     * the previous case, and the {@link #unmarshalThrow
     * unmarshalThrow} method of this invocation handler is invoked
     * with <code>proxy</code>, <code>method</code>, the marshal input
     * stream, and the client context.  This method throws the
     * exception returned by <code>unmarshalThrow</code> (and thus the
     * remote call attempt iteration terminates).
     *
     * <li>If the byte is any other value, a
     * <code>ProtocolException</code> is generated and, as described
     * below, wrapped and handled like any other
     * <code>Exception</code> thrown from a remote call attempt.
     *
     * </ul>
     *
     * <p>If an <code>IOException</code> is thrown during the attempt
     * to communicate the remote call, then it is wrapped in a
     * <code>RemoteException</code> as follows:
     *
     * <ul>
     *
     * <li>If <code>marshalMethod</code> was not invoked for this
     * attempt, or if an invocation of {@link
     * OutboundRequest#getDeliveryStatus getDeliveryStatus} on the
     * <code>OutboundRequest</code> returns <code>false</code>, or if
     * a marshalling protocol version mismatch was detected, then
     *
     * <ul>
     *
     * <li>if the <code>IOException</code> is a {@link
     * java.net.UnknownHostException java.net.UnknownHostException},
     * it is wrapped in a {@link java.rmi.UnknownHostException
     * java.rmi.UnknownHostException};
     *
     * <li>if it is a {@link java.net.ConnectException
     * java.net.ConnectException}, it is wrapped in a {@link
     * java.rmi.ConnectException java.rmi.ConnectException};
     *
     * <li>if it is any other <code>IOException</code>, it is wrapped
     * in a {@link ConnectIOException}.
     *
     * </ul>
     *
     * <li>Otherwise, if <code>executeCall</code> was not invoked for
     * this attempt, the <code>IOException</code> is wrapped in a
     * {@link MarshalException}, and if <code>executeCall</code> was
     * invoked, it is wrapped in an {@link UnmarshalException}.
     *
     * </ul>
     *
     * <p>If a {@link ClassNotFoundException} is thrown
     * during the unmarshalling, then it is wrapped in an {@link
     * UnmarshalException}.
     *
     * <p>In all cases, either the request output stream and the
     * response input stream will be closed or the
     * <code>OutboundRequest</code> will be aborted before this
     * attempt completes.
     *
     * </blockquote>
     *
     * <p>If an attempt to communicate the remote call throws an
     * <code>Exception</code> (other than an exception returned by
     * <code>executeCall</code> or <code>unmarshalThrow</code>, which
     * terminates the remote call attempt iteration), then if
     * <code>marshalMethod</code> was not invoked for the attempt or
     * if an invocation of <code>getDeliveryStatus</code> on the
     * <code>OutboundRequest</code> returns <code>false</code>, then
     * if the iterator's <code>hasNext</code> method returns
     * <code>true</code>, then another attempt is made.  Otherwise,
     * this method throws the <code>Exception</code> thrown by the
     * last attempt (possibly wrapped as described above).
     *
     * <p>Note that invoking a remote method on a remote object via this
     * invoke method preserves "at-most-once" call semantics.  At-most-once
     * call semantics guarantees that the remote call will either a) not
     * execute, b) partially execute, or c) execute exactly once at the remote
     * site.  With Java RMI's at-most-once call semantics, arguments may be
     * marshalled more than once for a given remote call.
     *
     * <p>A subclass can override this method to handle the methods of
     * any additional non-remote interfaces implemented by the proxy
     * or to otherwise control invocation handling behavior.
     *
     * <p>The semantics of this method are unspecified if the
     * arguments could not have been produced by an instance of some
     * valid dynamic proxy class containing this invocation handler.
     * This method throws {@link IllegalArgumentException} if
     * <code>proxy</code> is an instance of
     * <code>InvocationHandler</code> or, if a remote call is to be
     * made, any of the superinterfaces of <code>proxy</code>'s class
     * have a method with the same name and parameter types as
     * <code>method</code> but that does not declare
     * <code>RemoteException</code> or a superclass of
     * <code>RemoteException</code> in its <code>throws</code> clause
     * (even if such a method is not a member of any of the direct
     * superinterfaces of <code>proxy</code>'s class because of
     * overriding).
     *
     * @throws	Throwable {@inheritDoc}
     *
     * @see	java.lang.reflect.UndeclaredThrowableException
     **/
    public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable
    {
	if (proxy instanceof InvocationHandler) {
	    throw new IllegalArgumentException(
				    "proxy cannot be an invocation handler");
	} else if (method.getDeclaringClass() == Object.class) {
	    return invokeObjectMethod(proxy, method, args);
	} else if (method.getDeclaringClass() == RemoteMethodControl.class) {
	    /*
	     * REMIND: This optimization (testing the identity of the
	     * method's declaring class) fails if the proxy's class
	     * implements, instead of RemoteMethodControl directly, a
	     * subinterface of RemoteMethodControl that overrides the
	     * setConstraints method.  This problem could be fixed by
	     * using a more expensive Class.isAssignableFrom check,
	     * but even that would fail if the proxy class implements,
	     * prior to a subinterface of RemoteMethodControl, another
	     * interface that also declares a method with the same
	     * signature as RemoteMethodControl.setConstraints (this
	     * case does seem less unlikely).  It seems that to cover
	     * all cases, we should we testing the signature (name and
	     * parameter types) of the method directly.
	     */
	    return invokeRemoteMethodControlMethod(proxy, method, args);
	} else if (method.getDeclaringClass() == TrustEquivalence.class) {
	    /*
	     * REMIND: Ditto.
	     */
	    return invokeTrustEquivalenceMethod(proxy, method, args);
	} else {
	    return invokeRemoteMethod(proxy, method, args);
	}
    }

    /**
     * Handles java.lang.Object methods.
     **/
    private Object invokeObjectMethod(Object proxy,
				      Method method,
				      Object[] args)
    {
	String name = method.getName();

	if (name.equals("hashCode")) {
	    return Integer.valueOf(hashCode());

	} else if (name.equals("equals")) {
	    Object obj = args[0];
	    boolean b =
		proxy == obj ||
		(obj != null &&
		 Util.sameProxyClass(proxy, obj) &&
		 equals(Proxy.getInvocationHandler(obj)));
	    return Boolean.valueOf(b);

	} else if (name.equals("toString")) {
	    return proxyToString(proxy);

	} else {
	    throw new IllegalArgumentException(
		"unexpected Object method: " + method);
	}
    }

    /**
     * Handles RemoteMethodControl methods.
     **/
    private Object invokeRemoteMethodControlMethod(Object proxy,
						   Method method,
						   Object[] args)
    {
	String name = method.getName();

	if (name.equals("setConstraints")) {
	    if (Proxy.getInvocationHandler(proxy) != this) {
		throw new IllegalArgumentException("not proxy for this");
	    }
	    Class proxyClass = proxy.getClass();
	    return Proxy.newProxyInstance(
		getProxyLoader(proxyClass),
		proxyClass.getInterfaces(),
		setClientConstraints((MethodConstraints) args[0]));
	    // IllegalArgumentException means proxy argument was bogus

	} else if (name.equals("getConstraints")) {
	    return clientConstraints;

	} else {
	    throw new AssertionError(method);
	}
    }

    /**
     * Handles TrustEquivalence methods.
     **/
    private Object invokeTrustEquivalenceMethod(Object proxy,
						Method method,
						Object[] args)
    {
	String name = method.getName();
	if (name.equals("checkTrustEquivalence")) {
	    Object obj = args[0];
	    boolean b =
		proxy == obj ||
		(obj != null &&
		 Util.sameProxyClass(proxy, obj) &&
		 checkTrustEquivalence(Proxy.getInvocationHandler(obj)));
	    return Boolean.valueOf(b);

	} else {
	    throw new AssertionError(method);
	}
    }

    /**
     * Holds information about the communication failure of a remote
     * call attempt.
     **/
    private static class Failure {

	/** exception representing the communication failure */
	final Throwable exception;

	/** failure is safe to retry (and possibly worth retrying) after */
	final boolean retry;

	Failure(Throwable exception, boolean retry) {
	    this.exception = exception;
	    this.retry = retry;
	}
    }

    /**
     * Handles remote methods.
     **/
    private Object invokeRemoteMethod(Object proxy,
				      Method method,
				      Object[] args)
	throws Throwable
    {
	Util.checkProxyRemoteMethod(proxy.getClass(), method);
	InvocationConstraints constraints = getConstraints(method);

	if (logger.isLoggable(Level.FINE)) {
	    logCall(method, args, constraints);
	}

	OutboundRequestIterator iter = oe.newCall(constraints);
	if (!iter.hasNext()) {
	    throw new ConnectIOException("iterator produced no requests",
		new IOException("iterator produced no requests"));
	}

	Failure failure = null;
	do {
	    if (logger.isLoggable(Levels.HANDLED)) {
		if (failure != null) {
		    logThrow(Levels.HANDLED, method, failure.exception, false);
		}
	    }

	    Object result = invokeRemoteMethodOnce(proxy, method, args,
						   iter, constraints);
	    if (result instanceof Failure) {
		failure = (Failure) result;
	    } else {
		return result;
	    }
	} while (failure.retry && iter.hasNext());

	/*
	 * If all attempts failed with communication failures, then
	 * throw the exception representing the last communication
	 * failure.
	 */
	if (logger.isLoggable(Levels.FAILED)) {
	    if (failure != null) {
		logThrow(Levels.FAILED, method, failure.exception, false);
	    }
	}
	throw failure.exception;
    }

    /**
     * Make one attempt to invoke a remote method.
     *
     * If this method returns an object that is not a Failure
     * instance, then the overall remote invocation should return that
     * object.  If this method throws an exception, then the overall
     * remote invocation should throw that exception (no further
     * retry).
     *
     * If this method returns a Failure instance, then this attempt to
     * invoke the remote method failed due a communication failure.
     * If the retry flag of the Failure instance is true, then the
     * failure is safe to retry (in light of "at most once" execution
     * semantics) and possibly worth retrying.
     **/
    private Object invokeRemoteMethodOnce(Object proxy,
					  Method method,
					  Object[] args,
					  OutboundRequestIterator iter,
					  InvocationConstraints constraints)
	throws Throwable
    {
	/*
	 * Initiate remote call request.
	 */
	OutboundRequest request;
	try {
	    request = iter.next();
	} catch (Exception e) {
	    if (e instanceof IOException) {
		e = wrapSafeIOException((IOException) e, oe);
	    }
	    return new Failure(e, true);
	}

	/*
	 * Marshal method and arguments.
	 */
	boolean ok = false;
	boolean integrity = false;
	boolean wroteMethod = false;
	Collection context;
	try {
	    /*
	     * Check the unfulfilled constraints.  If the unfulfilled
	     * requirements include Integrity.YES, then we must verify
	     * codebase integrity at this level.  If there are any
	     * non-Integrity unfulfilled requirements, we cannot
	     * satisfy them, so this request attempt must fail.
	     */
	    InvocationConstraints unfulfilled =
		request.getUnfulfilledConstraints();
	    for (Iterator i = unfulfilled.requirements().iterator();
		 i.hasNext();)
	    {
		InvocationConstraint c = (InvocationConstraint) i.next();
		if (c == Integrity.YES) {
		    integrity = true;
		} else if (!(c instanceof Integrity)) {
		    throw new UnsupportedConstraintException(
			"cannot satisfy unfulfilled constraint: " + c);
		}
		// REMIND: support ConstraintAlternatives containing Integrity?
	    }

	    /*
	     * Even if Integrity.YES wasn't a requirement, we will
	     * satisfy a preference for it.
	     */
	    if (!integrity) {
		for (Iterator i = unfulfilled.preferences().iterator();
		     i.hasNext();)
		{
		    InvocationConstraint c = (InvocationConstraint) i.next();
		    if (c == Integrity.YES) {
			integrity = true;
			break;	// no need to examine preferences further
		    }
		    // NYI: support ConstraintAlternatives containing Integrity
		}
	    }

	    OutputStream ros = request.getRequestOutputStream();

	    ros.write(0x00);			// marshalling protocol version
	    ros.write(integrity ? 0x01 : 0x00);	// integrity

	    context = new ArrayList(1);
	    Util.populateContext(context, integrity);

	    ObjectOutputStream out =
		createMarshalOutputStream(proxy, method, request, context);

	    wroteMethod = true;
	    marshalMethod(proxy, method, out, context);

	    args = (args == null) ? new Object[0] : args;
	    marshalArguments(proxy, method, args, out, context);

	    out.close();
	    ok = true;
	} catch (Exception e) {
	    if (e instanceof IOException) {
		if (wroteMethod && request.getDeliveryStatus()) {
		    e = new MarshalException("error marshalling arguments", e);
		} else {
		    e = wrapSafeIOException((IOException) e, oe);
		}
	    }
	    return new Failure(e,
			       !wroteMethod || !request.getDeliveryStatus());
	} finally {
	    if (!ok) {
		request.abort();
	    }
	}

	/*
	 * Execute call and unmarshal return value or exception.
	 */
	ok = false;
	boolean versionMismatch = false;
	Object returnValue = null;
	Throwable throwable = null;
	try {
	    throwable = oe.executeCall(request);
	    if (throwable == null) {
		InputStream ris = request.getResponseInputStream();
		
		int responseCode = ris.read();
		if (responseCode == -1) {
		    throw new EOFException("connection closed by server");
		} else if (responseCode == 0x00) {
		    versionMismatch = true;
		    throw new ProtocolException(
			"marshalling protocol version mismatch");
		}
		
		ObjectInputStream in =
		    createMarshalInputStream(proxy, method, request,
					     integrity, context);
		
		switch (responseCode) {
		case 0x01:
		    returnValue = unmarshalReturn(proxy, method, in, context);
		    if (logger.isLoggable(Level.FINE)) {
			logReturn(method, returnValue);
		    }
		    break;

		case 0x02:
		    throwable = unmarshalThrow(proxy, method, in, context);
		    break;

		default:
		    throw new ProtocolException(
			"invalid response code " + responseCode);
		}

		in.close();
	    }
	    ok = true;
	} catch (Exception e) {
	    boolean retry = false;
	    if (e instanceof IOException) {
		if (!versionMismatch && request.getDeliveryStatus()) {
		    e = new UnmarshalException(
			 "exception unmarshalling response", e);
		} else {
		    e = wrapSafeIOException((IOException) e, oe);
		    retry = !versionMismatch;
		}
	    } else if (e instanceof ClassNotFoundException) {
		e = new UnmarshalException("error unmarshalling response", e);
	    }
	    return new Failure(e,
		!versionMismatch || !request.getDeliveryStatus());
	} finally {
	    if (!ok) {
		request.abort();
	    }
	}

	/*
	 * Throw exception or return the return value.
	 */
	if (throwable != null) {
	    if (logger.isLoggable(Levels.FAILED)) {
		logThrow(Levels.FAILED, method, throwable, true);
	    }
	    throw throwable;
	} else {
	    return returnValue;
	}
    }

    /**
     * Wraps an IOException that occurred while attempting to
     * communicate a remote call in a RemoteException that will
     * indicate that the failed remote invocation is safe to retry
     * without violating "at most once" execution semantics.
     **/
    private static RemoteException wrapSafeIOException(IOException ioe,
						       ObjectEndpoint oe)
    {
	if (ioe instanceof java.net.UnknownHostException) {
	    return new java.rmi.UnknownHostException(
		"unknown host in " + oe, ioe);
	} else if (ioe instanceof java.net.ConnectException) {
	    return new java.rmi.ConnectException(
		"connection refused or timed out to " + oe, ioe);
	} else {
	    return new ConnectIOException(
		"I/O exception connecting to " + oe, ioe);
	}
    }

    /**
     * Returns the combined absolute client and server constraints for
     * the specified method, getting the constraints from the cache if
     * possible, and creating and updating the cache if necessary.
     **/
    private InvocationConstraints getConstraints(Method method) {
	if (clientConstraints == null && serverConstraints == null) {
	    return InvocationConstraints.EMPTY;
	}

	synchronized (cacheLock) {
	    if (methodCache == null) {
		methodCache = new Method[CACHE_SIZE];
		constraintCache = new InvocationConstraints[CACHE_SIZE];
		cacheIndex = CACHE_SIZE - 1;
	    } else {
		for (int i = CACHE_SIZE; --i >= 0; ) {
		    if (methodCache[i] == method) {
			return constraintCache[i].makeAbsolute();
		    }
		}
	    }

	    InvocationConstraints constraints = InvocationConstraints.combine(
		clientConstraints == null ?
		    null : clientConstraints.getConstraints(method),
		serverConstraints == null ?
		    null : serverConstraints.getConstraints(method));

	    methodCache[cacheIndex] = method;
	    constraintCache[cacheIndex] = constraints;
	    cacheIndex = (cacheIndex == 0) ? CACHE_SIZE - 1 : cacheIndex - 1;
	    return constraints.makeAbsolute();
	}
    }

    /**
     * Returns a copy of this invocation handler with the specified
     * constraints as its new client constraints.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * as follows:
     *
     * <p>This method looks for a public constructor declared by the
     * class of this object with two parameters, the first parameter
     * type being the class of this object and the second parameter
     * type being {@link MethodConstraints}.  If found, the
     * constructor is invoked with this instance and the specified
     * constraints, and the resulting object is returned.  If the
     * constructor could not be found or was not accessible, or if the
     * constructor invocation throws an exception, an
     * <code>UndeclaredThrowableException</code> is thrown.
     *
     * <p>A subclass can override this method to control how the
     * invocation handler is copied.
     *
     * @param	constraints the new client constraints, or
     *		<code>null</code>
     *
     * @return	a copy of this invocation handler with the specified
     *		constraints as its client constraints
     **/
    protected InvocationHandler setClientConstraints(
	MethodConstraints constraints)
    {
	Class c = getClass();
	try {
	    Constructor constructor =
		c.getConstructor(new Class[] { c, MethodConstraints.class });
	    return (BasicInvocationHandler)
		constructor.newInstance(new Object[] { this, constraints });
	} catch (RuntimeException e) {
	    throw e;
	} catch (Exception e) {
	    throw new UndeclaredThrowableException(
		e, "exception constructing invocation handler");
	}
    }

    /**
     * Returns a new {@link ObjectOutputStream} instance to use to write
     * objects to the request output stream obtained by invoking the {@link
     * OutboundRequest#getRequestOutputStream getRequestOutputStream} method
     * on the given <code>request</code>.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to return a new {@link MarshalOutputStream} instance
     * constructed with the output stream obtained from
     * <code>request</code> as specified above and an unmodifiable
     * view of the supplied <code>context</code> collection.
     *
     * <p>A subclass can override this method to control how the
     * marshal input stream is created or implemented.
     *
     * @param	proxy the proxy instance
     * @param	method the remote method invoked
     * @param	request the outbound request
     * @param	context the client context
     * @return	a new {@link ObjectOutputStream} instance for marshalling
     *		a call request
     * @throws	IOException if an I/O exception occurs
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected ObjectOutputStream
        createMarshalOutputStream(Object proxy,
				  Method method,
				  OutboundRequest request,
				  Collection context)
	throws IOException
    {
	if (proxy == null || method == null) {
	    throw new NullPointerException();
	}
	OutputStream out = request.getRequestOutputStream();
	Collection unmodContext = Collections.unmodifiableCollection(context);
	return new MarshalOutputStream(out, unmodContext);
    }
							  
    /**
     * Returns a new {@link ObjectInputStream} instance to use to read
     * objects from the response input stream obtained by invoking the {@link
     * OutboundRequest#getResponseInputStream getResponseInputStream} method
     * on the given <code>request</code>.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to return a new {@link MarshalInputStream} instance constructed
     * with the input stream obtained from <code>request</code> as
     * specified above for the input stream <code>in</code>, the class
     * loader of <code>proxy</code>'s class for
     * <code>defaultLoader</code> and <code>verifierLoader</code>,
     * this method's <code>integrity</code> argument for
     * <code>verifyCodebaseIntegrity</code>, and an unmodifiable view
     * of <code>context</code> for the <code>context</code>
     * collection.  The {@link
     * MarshalInputStream#useCodebaseAnnotations
     * useCodebaseAnnotations} method is invoked on the created stream
     * before it is returned.
     *
     * <p>An exception is thrown if <code>proxy</code> is not an instance
     * of a dynamic proxy class containing this invocation handler.
     *
     * <p>A subclass can override this method to control how the
     * marshal input stream is created or implemented.
     *
     * @param	proxy the proxy instance
     * @param	method the remote method invoked
     * @param	request the outbound request
     * @param	integrity whether or not to verify codebase integrity
     * @param	context the client context
     * @return	a new {@link ObjectInputStream} instance for unmarshalling
     *		a call response
     * @throws	IOException if an I/O exception occurs
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected ObjectInputStream
        createMarshalInputStream(Object proxy,
				 Method method,
				 OutboundRequest request,
				 boolean integrity,
				 Collection context)
	throws IOException
    {
	if (method == null) {
	    throw new NullPointerException();
	}
	if (Proxy.getInvocationHandler(proxy) != this) {
	    throw new IllegalArgumentException("not proxy for this");
	}
	ClassLoader proxyLoader = getProxyLoader(proxy.getClass());
	Collection unmodContext = Collections.unmodifiableCollection(context);
	MarshalInputStream in =
	    new MarshalInputStream(request.getResponseInputStream(),
				   proxyLoader, integrity, proxyLoader,
				   unmodContext);
	in.useCodebaseAnnotations();
	return in;
    }

    /**
     * Returns the class loader for the specified proxy class.
     */
    private static ClassLoader getProxyLoader(final Class proxyClass) {
	return (ClassLoader)
	    AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    return proxyClass.getClassLoader();
		}
	    });
    }

    /**
     * Marshals a representation of the given <code>method</code> to
     * the outgoing request stream, <code>out</code>.  For each remote
     * call, the <code>invoke</code> method calls this method to
     * marshal a representation of the method.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to write the JRMP method hash (defined in section 8.3 of the
     * Java RMI specification) for the given method to the output stream
     * using the {@link ObjectOutputStream#writeLong writeLong}
     * method.
     *
     * <p>A subclass can override this method to control how the remote
     * method is marshalled.
     *
     * @param	proxy the proxy instance that the method was invoked on
     * @param   method the <code>Method</code> instance corresponding
     *          to the interface method invoked on the proxy
     *          instance.  The declaring class of the
     *          <code>Method</code> object will be the interface that
     *          the method was declared in, which may be a
     *          superinterface of the proxy interface that the proxy
     *          class inherits the method through.
     * @param	out outgoing request stream for the remote call
     * @param	context the client context
     *
     * @throws	IOException if an I/O exception occurs
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected void marshalMethod(Object proxy,
				 Method method,
				 ObjectOutputStream out,
				 Collection context)
	throws IOException
    {
	if (proxy == null || method == null || context == null) {
	    throw new NullPointerException();
	}
        out.writeLong(Util.getMethodHash(method));
    }

    /**
     * Marshals the arguments for the specified remote method to the outgoing
     * request stream, <code>out</code>.  For each remote call, the
     * <code>invoke</code> method calls this method to marshal arguments.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * marshal each argument as follows:
     *
     * <p>If the corresponding declared parameter type is primitive, then the 
     * the primitive value is written to the stream (for example, if the type
     * is <code>int.class</code>, then the primitive <code>int</code> value
     * is written to the stream using the <code>writeInt</code> method).
     * Otherwise, the argument is written to the stream using the
     * <code>writeObject</code> method.
     *
     * <p>A subclass can override this method to marshal the arguments
     * in an alternative context, perform pre- or post-processing on
     * the arguments, marshal additional implicit data, or otherwise
     * control how the arguments are marshalled.
     *
     * @param	proxy the proxy instance that the method was invoked on
     * @param   method the <code>Method</code> instance corresponding
     *          to the interface method invoked on the proxy
     *          instance.  The declaring class of the
     *          <code>Method</code> object will be the interface that
     *          the method was declared in, which may be a
     *          superinterface of the proxy interface that the proxy
     *          class inherits the method through.
     * @param   args an array of objects containing the values of the
     *          arguments passed in the method invocation on the
     *          proxy instance.  If an argument's corresponding declared
     *          parameter type is primitive, then its value is
     *          represented with an instance of the corresponding primitive
     *          wrapper class, such as <code>java.lang.Integer</code> or 
     *          <code>java.lang.Boolean</code>.
     * @param	out outgoing request stream for the remote call
     * @param	context the client context
     *
     * @throws	IOException if an I/O exception occurs
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected void marshalArguments(Object proxy,
				    Method method,
				    Object[] args,
				    ObjectOutputStream out,
				    Collection context)
	throws IOException
    {
	if (proxy == null || args == null || out == null || context == null) {
	    throw new NullPointerException();
	}
	Class[] types = method.getParameterTypes();
	for (int i = 0; i < types.length; i++) {	
	    Util.marshalValue(types[i], args[i], out);
	}
    }

    /**
     * Unmarshals the return value for the specified remote method from the
     * incoming response stream, <code>in</code>.  In the case that a value is
     * returned from the invocation on the remote object, this method is
     * called to unmarshal that return value.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * as follows:
     *
     * <p>If the return type of the method is void, then no return value is
     * read from the stream and <code>null</code> is returned.  If the return
     * type is a primitive type, then the primitive value is read from the
     * stream (for example, if the type is <code>int.class</code>, then the
     * primitive <code>int</code> value is read from the stream using the
     * <code>readInt</code> method).  Otherwise, the return value is read from
     * the stream using the <code>readObject</code> method.
     *
     * <p>A subclass can override this method to unmarshal the return
     * value in an alternative context, perform post-processing on the
     * return value, unmarshal additional implicit data, or otherwise
     * control how the return value is unmarshalled.
     *
     * @param	proxy the proxy instance that the method was invoked on
     * @param   method the <code>Method</code> instance corresponding
     *          to the interface method invoked on the proxy
     *          instance.  The declaring class of the
     *          <code>Method</code> object will be the interface that
     *          the method was declared in, which may be a
     *          superinterface of the proxy interface that the proxy
     *          class inherits the method through.
     * @param	in the incoming result stream for the remote call
     * @param	context the client context
     * @return  the unmarshalled return value of the method invocation on 
     *		the proxy instance.  If the declared return value of the
     *          interface method is a primitive type, then the
     *          value returned by <code>invoke</code> will be an
     *          instance of the corresponding primitive wrapper
     *          class; otherwise, it will be a type assignable to
     *		the declared return type.
     * @throws	IOException if an I/O exception occurs
     * @throws  ClassNotFoundException if a class could not be found during
     *          unmarshalling
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected Object unmarshalReturn(Object proxy,
				     Method method,
				     ObjectInputStream in,
				     Collection context)
	throws IOException, ClassNotFoundException
    {
	if (proxy == null || in == null || context == null) {
	    throw new NullPointerException();
	}
	Class returnType = method.getReturnType();
	Object returnValue = null;
	if (returnType != void.class) {
	    returnValue = Util.unmarshalValue(returnType, in);
	}
	return returnValue;
    }

    /**
     * Unmarshals the throwable for the specified remote method from the
     * incoming response stream, <code>in</code>, and returns the result.  In
     * the case that an exception was thrown as a result of the
     * invocation on the remote object, this method is called to unmarshal
     * that throwable.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to return the throwable unmarshalled from the stream using the
     * <code>readObject</code> method.  If the unmarshalled throwable
     * is a checked exception that is not assignable to any exception
     * in the <code>throws</code> clause of the method implemented by the
     * <code>proxy</code>'s class, then: if there is no public member
     * method of <code>proxy</code>'s class with the same name and
     * parameter types as <code>method</code> an {@link
     * IllegalArgumentException} is thrown, otherwise that exception
     * is wrapped in an {@link UnexpectedException} and the wrapped
     * exception is returned.
     *
     * <p>A subclass can override this method to unmarshal the return
     * value in an alternative context, perform post-processing on the
     * return value, unmarshal additional implicit data, or otherwise
     * control how the return value is unmarshalled.
     *
     * @param	proxy the proxy instance that the method was invoked on
     * @param   method the <code>Method</code> instance corresponding
     *          to the interface method invoked on the proxy
     *          instance.  The declaring class of the
     *          <code>Method</code> object will be the interface that
     *          the method was declared in, which may be a
     *          superinterface of the proxy interface that the proxy
     *          class inherits the method through.
     * @param	context the client context
     * @param	in the incoming result stream for the remote call
     * @return	the unmarshalled exception to throw from the method
     *		invocation on the proxy instance.  The exception's type
     *		must be assignable either to any of the exception types
     *		declared in the <code>throws</code> clause of the interface
     *		method or to the unchecked exception types
     *		<code>java.lang.RuntimeException</code> or
     *		<code>java.lang.Error</code>.
     * @throws	IOException if an I/O exception occurs
     * @throws  ClassNotFoundException if a class could not be found during
     *          unmarshalling
     * @throws	NullPointerException if any argument is <code>null</code>
     **/
    protected Throwable unmarshalThrow(Object proxy,
				       Method method,
				       ObjectInputStream in,
				       Collection context)
	throws IOException, ClassNotFoundException
    {
	if (proxy == null || method == null || context == null) {
	    throw new NullPointerException();
	}
	Throwable t = (Throwable) in.readObject();
	Util.exceptionReceivedFromServer(t);
	if (!(t instanceof RuntimeException || t instanceof Error)) {
	    Class cl = proxy.getClass();
	    try {
		method = cl.getMethod(method.getName(),
				      method.getParameterTypes());
	    } catch (NoSuchMethodException e) {
		throw (IllegalArgumentException)
		    new IllegalArgumentException().initCause(e);
	    }
	    Class[] exTypes = method.getExceptionTypes();
	    Class thrownType = t.getClass();
	    for (int i = 0; i < exTypes.length; i++) {
		if (exTypes[i].isAssignableFrom(thrownType)) {
		    return t;
		}
	    }
	    UnexpectedException wrapper =
		new UnexpectedException("unexpected exception");
	    wrapper.detail = t;
	    t = wrapper;
	}
	return t;
    }
    
    /**
     * Log the start of an outbound remote call.
     **/
    private void logCall(Method method,
			 Object[] args,
			 InvocationConstraints constraints)
    {
	String msg = "outbound call {0}.{1} to {2}\n{3}";
	if (logger.isLoggable(Level.FINEST)) {
	    msg = "outbound call {0}.{1} to {2}\nargs {4}\n{3}";
	}
	Object xargs = (args == null) ?
	    Collections.EMPTY_LIST : Arrays.asList(args);
	logger.logp(Level.FINE, this.getClass().getName(), "invoke", msg,
		    new Object[] { method.getDeclaringClass().getName(),
				   method.getName(), oe, constraints, xargs});
    }

    /**
     * Log the return of an outbound remote call.
     **/
    private void logReturn(Method method, Object res) {
	String msg = "outbound call {0}.{1} returns";
	if (logger.isLoggable(Level.FINEST) &&
	    method.getReturnType() != void.class)
	{
	    msg = "outbound call {0}.{1} returns {2}";
	}
	logger.logp(Level.FINE, this.getClass().getName(), "invoke", msg,
		    new Object[]{method.getDeclaringClass().getName(),
				 method.getName(), res});
    }

    /**
     * Log the throw of an outbound remote call.
     **/
    private void logThrow(Level level,
			  Method method,
			  Throwable t,
			  boolean isRemote)
    {
	LogRecord lr = new LogRecord(level,
				     isRemote ?
				     "outbound call {0}.{1} remotely throws" :
				     "outbound call {0}.{1} locally throws");
	lr.setLoggerName(logger.getName());
	lr.setSourceClassName(this.getClass().getName());
	lr.setSourceMethodName("invoke");
	lr.setParameters(new Object[]{method.getDeclaringClass().getName(),
				      method.getName()});
	lr.setThrown(t);
	logger.log(lr);
    }

    /**
     * Returns this <code>BasicInvocationHandler</code>'s
     * <code>ObjectEndpoint</code>.
     *
     * @return the <code>ObjectEndpoint</code>
     **/
    public final ObjectEndpoint getObjectEndpoint() {
	return oe;
    }

    /**
     * Returns this <code>BasicInvocationHandler</code>'s client
     * constraints.
     *
     * @return the client constraints
     **/
    public final MethodConstraints getClientConstraints() {
	return clientConstraints;
    }

    /**
     * Returns this <code>BasicInvocationHandler</code>'s server
     * constraints.
     *
     * @return the server constraints
     **/
    public final MethodConstraints getServerConstraints() {
	return serverConstraints;
    }

    /**
     * Returns the hash code value for this invocation handler.
     *
     * @return the hash code value for this invocation handler
     **/
    public int hashCode() {
	return oe.hashCode();
    }

    /**
     * Compares the specified object with this
     * <code>BasicInvocationHandler</code> for equality.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to return <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object has the same class as this object,
     *
     * <li>the <code>ObjectEndpoint</code> in the specified object has the
     * same class and is equal to the object endpoint in this object, and
     *
     * <li>the client constraints and server constraints in the specified
     * object are equal to the ones in this object.
     *
     * </ul>
     *
     * <p>A subclass should override this method if adds instance
     * state that affects equality.
     *
     * @param	obj the object to compare with
     *
     * @return	<code>true</code> if <code>obj</code> is equivalent to
     *		this object; <code>false</code> otherwise
     **/
    public boolean equals(Object obj) {
	if (obj == this) {
	    return true;
	} else if (obj == null || getClass() != obj.getClass()) {
	    return false;
	}
	BasicInvocationHandler other = (BasicInvocationHandler) obj;
	return
	    Util.sameClassAndEquals(oe, other.oe) &&
	    Util.equals(clientConstraints, other.clientConstraints) &&
	    Util.equals(serverConstraints, other.serverConstraints);
    }

    /**
     * Returns <code>true</code> if the specified object (which is not
     * yet known to be trusted) is equivalent in trust, content, and
     * function to this known trusted object, and <code>false</code>
     * otherwise.
     *
     * <p><code>BasicInvocationHandler</code> implements this method
     * to return <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object has the same class as this object,
     *
     * <li>this object's <code>ObjectEndpoint</code> is an instance of
     * {@link TrustEquivalence} and invoking its
     * <code>checkTrustEquivalence</code> method with the specified
     * object's <code>ObjectEndpoint</code> returns <code>true</code>,
     * and
     *
     * <li>the client constraints and server constraints in the
     * specified object are equal to the ones in this object.
     *
     * </ul>
     *
     * <p>A subclass should override this method to perform any
     * additional checks that are necessary.
     **/
    public boolean checkTrustEquivalence(Object obj) {
	if (obj == this) {
	    return true;
	} else if (obj == null || getClass() != obj.getClass()) {
	    return false;
	}
	BasicInvocationHandler other = (BasicInvocationHandler) obj;
	return
	    Util.checkTrustEquivalence(oe, other.oe) &&
	    Util.equals(clientConstraints, other.clientConstraints) &&
	    Util.equals(serverConstraints, other.serverConstraints);
    }

    /**
     * Returns a string representation of this
     * <code>BasicInvocationHandler</code>.
     *
     * @return a string representation of this
     * <code>BasicInvocationHandler</code>
     **/
    public String toString() {
	return Util.getUnqualifiedName(getClass()) + "[" + oe + "]";
    }

    /**
     * Returns a string representation for a proxy that uses this invocation
     * handler.
     **/
    private String proxyToString(Object proxy) {
	Class[] interfaces = proxy.getClass().getInterfaces();
	if (interfaces.length == 0) {
	    return "Proxy[" + this + "]";
	}
	String iface = interfaces[0].getName();
	int dot = iface.lastIndexOf('.');
	if (dot >= 0) {
	    iface = iface.substring(dot + 1);
	}
	return "Proxy[" + iface + "," + this + "]";
    }

    /**
     * @throws	InvalidObjectException if the object endpoint is
     * <code>null</code>
     **/
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	in.defaultReadObject();
	if (oe == null) {
	    throw new InvalidObjectException("null object endpoint");
	}
	cacheLock = new Object();
    }

    /**
     * @throws	InvalidObjectException unconditionally
     **/
    private void readObjectNoData() throws InvalidObjectException {
	throw new InvalidObjectException("no data in stream; class: " +
					 this.getClass().getName());
    }
}
