Use Tree Navigation
public class

RemoteClass.Method

extends Object
implements Cloneable
/*
 * Copyright (c) 1997, 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sun.rmi.rmic;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.security.MessageDigest;
import java.security.DigestOutputStream;
import java.security.NoSuchAlgorithmException;
import sun.tools.java.Type;
import sun.tools.java.ClassDefinition;
import sun.tools.java.ClassDeclaration;
import sun.tools.java.MemberDefinition;
import sun.tools.java.Identifier;
import sun.tools.java.ClassNotFound;

/**
 * A RemoteClass object encapsulates RMI-specific information about
 * a remote implementation class, i.e. a class that implements
 * one or more remote interfaces.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 *
 * @author      Peter Jones
 */

public class RemoteClass implements sun.rmi.rmic.RMIConstants {

   
/**
     * Create a RemoteClass object representing the remote meta-information
     * of the given class.
     *
     * Returns true if successful.  If the class is not a properly formed
     * remote implementation class or if some other error occurs, the
     * return value will be null, and errors will have been reported to
     * the supplied BatchEnvironment.
     */

   
public static RemoteClass forClass(BatchEnvironment env,
                                       
ClassDefinition implClassDef)
   
{
       
RemoteClass rc = new RemoteClass(env, implClassDef);
       
if (rc.initialize()) {
           
return rc;
       
} else {
           
return null;
       
}
   
}

   
/**
     * Return the ClassDefinition for this class.
     */

   
public ClassDefinition getClassDefinition() {
       
return implClassDef;
   
}

   
/**
     * Return the name of the class represented by this object.
     */

   
public Identifier getName() {
       
return implClassDef.getName();
   
}

   
/**
     * Return an array of ClassDefinitions representing all of the remote
     * interfaces implemented by this class.
     *
     * A remote interface is any interface that extends Remote,
     * directly or indirectly.  The remote interfaces of a class
     * are the interfaces directly listed in either the class's
     * "implements" clause, or the "implements" clause of any
     * of its superclasses, that are remote interfaces.
     *
     * The order of the array returned is arbitrary, and some elements
     * may be superfluous (i.e., superinterfaces of other interfaces
     * in the array).
     */

   
public ClassDefinition[] getRemoteInterfaces() {
       
return (ClassDefinition[]) remoteInterfaces.clone();
   
}

   
/**
     * Return an array of RemoteClass.Method objects representing all of
     * the remote methods implemented by this class, i.e. all of the
     * methods in the class's remote interfaces.
     *
     * The methods in the array are ordered according to the comparision
     * of the strings consisting of their method name followed by their
     * type signature, so each method's index in the array corresponds
     * to its "operation number" in the JDK 1.1 version of the
     * stub/skeleton protocol.
     */

   
public Method[] getRemoteMethods() {
       
return (Method[]) remoteMethods.clone();
   
}

   
/**
     * Return the "interface hash" used to match a stub/skeleton pair for
     * this class in the JDK 1.1 version of the stub/skeleton protocol.
     */

   
public long getInterfaceHash() {
       
return interfaceHash;
   
}

   
/**
     * Return string representation of this object, consisting of
     * the string "remote class " followed by the class name.
     */

   
public String toString() {
       
return "remote class " + implClassDef.getName().toString();
   
}

   
/** rmic environment for this object */
   
private BatchEnvironment env;

   
/** the remote implementation class this object corresponds to */
   
private ClassDefinition implClassDef;

   
/** remote interfaces implemented by this class */
   
private ClassDefinition[] remoteInterfaces;

   
/** all the remote methods of this class */
   
private Method[] remoteMethods;

   
/** stub/skeleton "interface hash" for this class */
   
private long interfaceHash;

   
/** cached definition for certain classes used in this environment */
   
private ClassDefinition defRemote;
   
private ClassDefinition defException;
   
private ClassDefinition defRemoteException;

   
/**
     * Create a RemoteClass instance for the given class.  The resulting
     * object is not yet initialized.
     */

   
private RemoteClass(BatchEnvironment env, ClassDefinition implClassDef) {
       
this.env = env;
       
this.implClassDef = implClassDef;
   
}

   
/**
     * Validate that the remote implementation class is properly formed
     * and fill in the data structures required by the public interface.
     */

   
private boolean initialize() {
       
/*
         * Verify that the "impl" is really a class, not an interface.
         */

       
if (implClassDef.isInterface()) {
            env
.error(0, "rmic.cant.make.stubs.for.interface",
                      implClassDef
.getName());
           
return false;
       
}

       
/*
         * Initialize cached definitions for the Remote interface and
         * the RemoteException class.
         */

       
try {
            defRemote
=
                env
.getClassDeclaration(idRemote).getClassDefinition(env);
            defException
=
                env
.getClassDeclaration(idJavaLangException).
                getClassDefinition
(env);
            defRemoteException
=
                env
.getClassDeclaration(idRemoteException).
                getClassDefinition
(env);
       
} catch (ClassNotFound e) {
            env
.error(0, "rmic.class.not.found", e.name);
           
return false;
       
}

       
/*
         * Here we find all of the remote interfaces of our remote
         * implementation class.  For each class up the superclass
         * chain, add each directly-implemented interface that
         * somehow extends Remote to a list.
         */

       
Vector remotesImplemented =     // list of remote interfaces found
           
new Vector();
       
for (ClassDefinition classDef = implClassDef;
             classDef
!= null;)
           
{
               
try {
                   
ClassDeclaration[] interfaces = classDef.getInterfaces();
                   
for (int i = 0; i < interfaces.length; i++) {
                       
ClassDefinition interfaceDef =
                            interfaces
[i].getClassDefinition(env);
                       
/*
                         * Add interface to the list if it extends Remote and
                         * it is not already there.
                         */

                       
if (!remotesImplemented.contains(interfaceDef) &&
                            defRemote
.implementedBy(env, interfaces[i]))
                           
{
                                remotesImplemented
.addElement(interfaceDef);
                               
/***** <DEBUG> */
                               
if (env.verbose()) {
                                   
System.out.println("[found remote interface: " +
                                                       interfaceDef
.getName() + "]");
                                   
/***** </DEBUG> */
                               
}
                           
}
                   
}

                   
/*
                     * Verify that the candidate remote implementation class
                     * implements at least one remote interface directly.
                     */

                   
if (classDef == implClassDef && remotesImplemented.isEmpty()) {
                       
if (defRemote.implementedBy(env,
                                                    implClassDef
.getClassDeclaration()))
                           
{
                               
/*
                                 * This error message is used if the class does
                                 * implement a remote interface through one of
                                 * its superclasses, but not directly.
                                 */

                                env
.error(0, "rmic.must.implement.remote.directly",
                                          implClassDef
.getName());
                           
} else {
                               
/*
                                 * This error message is used if the class never
                                 * implements a remote interface.
                                 */

                                env
.error(0, "rmic.must.implement.remote",
                                          implClassDef
.getName());
                           
}
                       
return false;
                   
}

                   
/*
                     * Get definition for next superclass.
                     */

                    classDef
= (classDef.getSuperClass() != null ?
                                classDef
.getSuperClass().getClassDefinition(env) :
                               
null);

               
} catch (ClassNotFound e) {
                    env
.error(0, "class.not.found", e.name, classDef.getName());
                   
return false;
               
}
           
}

       
/*
         * The "remotesImplemented" vector now contains all of the remote
         * interfaces directly implemented by the remote class or by any
         * of its superclasses.
         *
         * At this point, we could optimize the list by removing superfluous
         * entries, i.e. any interfaces that are implemented by some other
         * interface in the list anyway.
         *
         * This should be correct; would it be worthwhile?
         *
         *      for (int i = 0; i < remotesImplemented.size();) {
         *          ClassDefinition interfaceDef =
         *              (ClassDefinition) remotesImplemented.elementAt(i);
         *          boolean isOtherwiseImplemented = false;
         *          for (int j = 0; j < remotesImplemented.size; j++) {
         *              if (j != i &&
         *                  interfaceDef.implementedBy(env, (ClassDefinition)
         *                  remotesImplemented.elementAt(j).
         *                      getClassDeclaration()))
         *              {
         *                  isOtherwiseImplemented = true;
         *                  break;
         *              }
         *          }
         *          if (isOtherwiseImplemented) {
         *              remotesImplemented.removeElementAt(i);
         *          } else {
         *              ++i;
         *          }
         *      }
         */


       
/*
         * Now we collect the methods from all of the remote interfaces
         * into a hashtable.
         */

       
Hashtable methods = new Hashtable();
       
boolean errors = false;
       
for (Enumeration enumeration = remotesImplemented.elements();
             enumeration
.hasMoreElements();)
           
{
               
ClassDefinition interfaceDef =
                   
(ClassDefinition) enumeration.nextElement();
               
if (!collectRemoteMethods(interfaceDef, methods))
                    errors
= true;
           
}
       
if (errors)
           
return false;

       
/*
         * Convert vector of remote interfaces to an array
         * (order is not important for this array).
         */

        remoteInterfaces
= new ClassDefinition[remotesImplemented.size()];
        remotesImplemented
.copyInto(remoteInterfaces);

       
/*
         * Sort table of remote methods into an array.  The elements are
         * sorted in ascending order of the string of the method's name
         * and type signature, so that each elements index is equal to
         * its operation number of the JDK 1.1 version of the stub/skeleton
         * protocol.
         */

       
String[] orderedKeys = new String[methods.size()];
       
int count = 0;
       
for (Enumeration enumeration = methods.elements();
             enumeration
.hasMoreElements();)
           
{
               
Method m = (Method) enumeration.nextElement();
               
String key = m.getNameAndDescriptor();
               
int i;
               
for (i = count; i > 0; --i) {
                   
if (key.compareTo(orderedKeys[i - 1]) >= 0) {
                       
break;
                   
}
                    orderedKeys
[i] = orderedKeys[i - 1];
               
}
                orderedKeys
[i] = key;
               
++count;
           
}
        remoteMethods
= new Method[methods.size()];
       
for (int i = 0; i < remoteMethods.length; i++) {
            remoteMethods
[i] = (Method) methods.get(orderedKeys[i]);
           
/***** <DEBUG> */
           
if (env.verbose()) {
               
System.out.print("[found remote method <" + i + ">: " +
                                 remoteMethods
[i].getOperationString());
               
ClassDeclaration[] exceptions =
                    remoteMethods
[i].getExceptions();
               
if (exceptions.length > 0)
                   
System.out.print(" throws ");
               
for (int j = 0; j < exceptions.length; j++) {
                   
if (j > 0)
                       
System.out.print(", ");
                   
System.out.print(exceptions[j].getName());
               
}
               
System.out.println("]");
           
}
           
/***** </DEBUG> */
       
}

       
/**
         * Finally, pre-compute the interface hash to be used by
         * stubs/skeletons for this remote class.
         */

        interfaceHash
= computeInterfaceHash();

       
return true;
   
}

   
/**
     * Collect and validate all methods from given interface and all of
     * its superinterfaces as remote methods.  Remote methods are added
     * to the supplied hashtable.  Returns true if successful,
     * or false if an error occurred.
     */

   
private boolean collectRemoteMethods(ClassDefinition interfaceDef,
                                         
Hashtable table)
   
{
       
if (!interfaceDef.isInterface()) {
           
throw new Error(
                           
"expected interface, not class: " + interfaceDef.getName());
       
}

       
/*
         * rmic used to enforce that a remote interface could not extend
         * a non-remote interface, i.e. an interface that did not itself
         * extend from Remote.  The current version of rmic does not have
         * this restriction, so the following code is now commented out.
         *
         * Verify that this interface extends Remote, since all interfaces
         * extended by a remote interface must implement Remote.
         *
         *      try {
         *          if (!defRemote.implementedBy(env,
         *              interfaceDef.getClassDeclaration()))
         *          {
         *              env.error(0, "rmic.can.mix.remote.nonremote",
         *                  interfaceDef.getName());
         *              return false;
         *          }
         *      } catch (ClassNotFound e) {
         *          env.error(0, "class.not.found", e.name,
         *              interfaceDef.getName());
         *          return false;
         *      }
         */


       
boolean errors = false;

       
/*
         * Search interface's members for methods.
         */

    nextMember
:
       
for (MemberDefinition member = interfaceDef.getFirstMember();
             member
!= null;
             member
= member.getNextMember())
           
{
               
if (member.isMethod() &&
                   
!member.isConstructor() && !member.isInitializer())
                   
{
                       
/*
                         * Verify that each method throws RemoteException.
                         */

                       
ClassDeclaration[] exceptions = member.getExceptions(env);
                       
boolean hasRemoteException = false;
                       
for (int i = 0; i < exceptions.length; i++) {
                           
/*
                             * rmic used to enforce that a remote method had to
                             * explicitly list RemoteException in its "throws"
                             * clause; i.e., just throwing Exception was not
                             * acceptable.  The current version of rmic does not
                             * have this restriction, so the following code is
                             * now commented out.  Instead, the method is
                             * considered valid if RemoteException is a subclass
                             * of any of the methods declared exceptions.
                             *
                             *  if (exceptions[i].getName().equals(
                             *      idRemoteException))
                             *  {
                             *      hasRemoteException = true;
                             *      break;
                             *  }
                             */

                           
try {
                               
if (defRemoteException.subClassOf(
                                                                  env
, exceptions[i]))
                                   
{
                                        hasRemoteException
= true;
                                       
break;
                                   
}
                           
} catch (ClassNotFound e) {
                                env
.error(0, "class.not.found", e.name,
                                          interfaceDef
.getName());
                               
continue nextMember;
                           
}
                       
}
                       
/*
                         * If this method did not throw RemoteException as required,
                         * generate the error but continue, so that multiple such
                         * errors can be reported.
                         */

                       
if (!hasRemoteException) {
                            env
.error(0, "rmic.must.throw.remoteexception",
                                      interfaceDef
.getName(), member.toString());
                            errors
= true;
                           
continue nextMember;
                       
}

                       
/*
                         * Verify that the implementation of this method throws only
                         * java.lang.Exception or its subclasses (fix bugid 4092486).
                         * JRMP does not support remote methods throwing
                         * java.lang.Throwable or other subclasses.
                         */

                       
try {
                           
MemberDefinition implMethod = implClassDef.findMethod(
                                                                                  env
, member.getName(), member.getType());
                           
if (implMethod != null) {           // should not be null
                                exceptions
= implMethod.getExceptions(env);
                               
for (int i = 0; i < exceptions.length; i++) {
                                   
if (!defException.superClassOf(
                                                                   env
, exceptions[i]))
                                       
{
                                            env
.error(0, "rmic.must.only.throw.exception",
                                                      implMethod
.toString(),
                                                      exceptions
[i].getName());
                                            errors
= true;
                                           
continue nextMember;
                                       
}
                               
}
                           
}
                       
} catch (ClassNotFound e) {
                            env
.error(0, "class.not.found", e.name,
                                      implClassDef
.getName());
                           
continue nextMember;
                       
}

                       
/*
                         * Create RemoteClass.Method object to represent this method
                         * found in a remote interface.
                         */

                       
Method newMethod = new Method(member);
                       
/*
                         * Store remote method's representation in the table of
                         * remote methods found, keyed by its name and parameter
                         * signature.
                         *
                         * If the table already contains an entry with the same
                         * method name and parameter signature, then we must
                         * replace the old entry with a Method object that
                         * represents a legal combination of the old and the new
                         * methods; specifically, the combined method must have
                         * a throws list that contains (only) all of the checked
                         * exceptions that can be thrown by both the old or
                         * the new method (see bugid 4070653).
                         */

                       
String key = newMethod.getNameAndDescriptor();
                       
Method oldMethod = (Method) table.get(key);
                       
if (oldMethod != null) {
                            newMethod
= newMethod.mergeWith(oldMethod);
                           
if (newMethod == null) {
                                errors
= true;
                               
continue nextMember;
                           
}
                       
}
                        table
.put(key, newMethod);
                   
}
           
}

       
/*
         * Recursively collect methods for all superinterfaces.
         */

       
try {
           
ClassDeclaration[] superDefs = interfaceDef.getInterfaces();
           
for (int i = 0; i < superDefs.length; i++) {
               
ClassDefinition superDef =
                    superDefs
[i].getClassDefinition(env);
               
if (!collectRemoteMethods(superDef, table))
                    errors
= true;
           
}
       
} catch (ClassNotFound e) {
            env
.error(0, "class.not.found", e.name, interfaceDef.getName());
           
return false;
       
}

       
return !errors;
   
}

   
/**
     * Compute the "interface hash" of the stub/skeleton pair for this
     * remote implementation class.  This is the 64-bit value used to
     * enforce compatibility between a stub and a skeleton using the
     * JDK 1.1 version of the stub/skeleton protocol.
     *
     * It is calculated using the first 64 bits of a SHA digest.  The
     * digest is from a stream consisting of the following data:
     *     (int) stub version number, always 1
     *     for each remote method, in order of operation number:
     *         (UTF) method name
     *         (UTF) method type signature
     *         for each declared exception, in alphabetical name order:
     *             (UTF) name of exception class
     *
     */

   
private long computeInterfaceHash() {
       
long hash = 0;
       
ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
       
try {
           
MessageDigest md = MessageDigest.getInstance("SHA");
           
DataOutputStream out = new DataOutputStream(
                                                       
new DigestOutputStream(sink, md));

           
out.writeInt(INTERFACE_HASH_STUB_VERSION);
           
for (int i = 0; i < remoteMethods.length; i++) {
               
MemberDefinition m = remoteMethods[i].getMemberDefinition();
               
Identifier name = m.getName();
               
Type type = m.getType();

               
out.writeUTF(name.toString());
               
// type signatures already use mangled class names
               
out.writeUTF(type.getTypeSignature());

               
ClassDeclaration exceptions[] = m.getExceptions(env);
                sortClassDeclarations
(exceptions);
               
for (int j = 0; j < exceptions.length; j++) {
                   
out.writeUTF(Names.mangleClass(
                                                   exceptions
[j].getName()).toString());
               
}
           
}
           
out.flush();

           
// use only the first 64 bits of the digest for the hash
           
byte hashArray[] = md.digest();
           
for (int i = 0; i < Math.min(8, hashArray.length); i++) {
                hash
+= ((long) (hashArray[i] & 0xFF)) << (i * 8);
           
}
       
} catch (IOException e) {
           
throw new Error(
                           
"unexpected exception computing intetrface hash: " + e);
       
} catch (NoSuchAlgorithmException e) {
           
throw new Error(
                           
"unexpected exception computing intetrface hash: " + e);
       
}

       
return hash;
   
}

   
/**
     * Sort array of class declarations alphabetically by their mangled
     * fully-qualfied class name.  This is used to feed a method's exceptions
     * in a canonical order into the digest stream for the interface hash
     * computation.
     */

   
private void sortClassDeclarations(ClassDeclaration[] decl) {
       
for (int i = 1; i < decl.length; i++) {
           
ClassDeclaration curr = decl[i];
           
String name = Names.mangleClass(curr.getName()).toString();
           
int j;
           
for (j = i; j > 0; j--) {
               
if (name.compareTo(
                                   
Names.mangleClass(decl[j - 1].getName()).toString()) >= 0)
                   
{
                       
break;
                   
}
                decl
[j] = decl[j - 1];
           
}
            decl
[j] = curr;
       
}
   
}


   
/**
     * A RemoteClass.Method object encapsulates RMI-specific information
     * about a particular remote method in the remote implementation class
     * represented by the outer instance.
     */

   
public class Method implements Cloneable {

       
/**
         * Return the definition of the actual class member corresponing
         * to this method of a remote interface.
         *
         * REMIND: Can this method be removed?
         */

       
public MemberDefinition getMemberDefinition() {
           
return memberDef;
       
}

       
/**
         * Return the name of this method.
         */

       
public Identifier getName() {
           
return memberDef.getName();
       
}

       
/**
         * Return the type of this method.
         */

       
public Type getType() {
           
return memberDef.getType();
       
}

       
/**
         * Return an array of the exception classes declared to be
         * thrown by this remote method.
         *
         * For methods with the same name and type signature inherited
         * from multiple remote interfaces, the array will contain
         * the set of exceptions declared in all of the interfaces'
         * methods that can be legally thrown in each of them.
         */

       
public ClassDeclaration[] getExceptions() {
           
return (ClassDeclaration[]) exceptions.clone();
       
}

       
/**
         * Return the "method hash" used to identify this remote method
         * in the JDK 1.2 version of the stub protocol.
         */

       
public long getMethodHash() {
           
return methodHash;
       
}

       
/**
         * Return the string representation of this method.
         */

       
public String toString() {
           
return memberDef.toString();
       
}

       
/**
         * Return the string representation of this method appropriate
         * for the construction of a java.rmi.server.Operation object.
         */

       
public String getOperationString() {
           
return memberDef.toString();
       
}

       
/**
         * Return a string consisting of this method's name followed by
         * its method descriptor, using the Java VM's notation for
         * method descriptors (see section 4.3.3 of The Java Virtual
         * Machine Specification).
         */

       
public String getNameAndDescriptor() {
           
return memberDef.getName().toString() +
                memberDef
.getType().getTypeSignature();
       
}

       
/**
         * Member definition for this method, from one of the remote
         * interfaces that this method was found in.
         *
         * Note that this member definition may be only one of several
         * member defintions that correspond to this remote method object,
         * if several of this class's remote interfaces contain methods
         * with the same name and type signature.  Therefore, this member
         * definition may declare more exceptions thrown that this remote
         * method does.
         */

       
private MemberDefinition memberDef;

       
/** stub "method hash" to identify this method */
       
private long methodHash;

       
/**
         * Exceptions declared to be thrown by this remote method.
         *
         * This list can include superfluous entries, such as
         * unchecked exceptions and subclasses of other entries.
         */

       
private ClassDeclaration[] exceptions;

       
/**
         * Create a new Method object corresponding to the given
         * method definition.
         */

       
/*
         * Temporarily comment out the private modifier until
         * the VM allows outer class to access inner class's
         * private constructor
         */

       
/* private */ Method(MemberDefinition memberDef) {
           
this.memberDef = memberDef;
            exceptions
= memberDef.getExceptions(env);
            methodHash
= computeMethodHash();
       
}

       
/**
         * Cloning is supported by returning a shallow copy of this object.
         */

       
protected Object clone() {
           
try {
               
return super.clone();
           
} catch (CloneNotSupportedException e) {
               
throw new Error("clone failed");
           
}
       
}

       
/**
         * Return a new Method object that is a legal combination of
         * this method object and another one.
         *
         * This requires determining the exceptions declared by the
         * combined method, which must be (only) all of the exceptions
         * declared in both old Methods that may thrown in either of
         * them.
         */

       
private Method mergeWith(Method other) {
           
if (!getName().equals(other.getName()) ||
               
!getType().equals(other.getType()))
               
{
                   
throw new Error("attempt to merge method \"" +
                                    other
.getNameAndDescriptor() + "\" with \"" +
                                    getNameAndDescriptor
());
               
}

           
Vector legalExceptions = new Vector();
           
try {
                collectCompatibleExceptions
(
                                            other
.exceptions, exceptions, legalExceptions);
                collectCompatibleExceptions
(
                                            exceptions
, other.exceptions, legalExceptions);
           
} catch (ClassNotFound e) {
                env
.error(0, "class.not.found", e.name,
                          getClassDefinition
().getName());
               
return null;
           
}

           
Method merged = (Method) clone();
            merged
.exceptions = new ClassDeclaration[legalExceptions.size()];
            legalExceptions
.copyInto(merged.exceptions);

           
return merged;
       
}

       
/**
         * Add to the supplied list all exceptions in the "from" array
         * that are subclasses of an exception in the "with" array.
         */

       
private void collectCompatibleExceptions(ClassDeclaration[] from,
                                                 
ClassDeclaration[] with,
                                                 
Vector list)
           
throws ClassNotFound
       
{
           
for (int i = 0; i < from.length; i++) {
               
ClassDefinition exceptionDef = from[i].getClassDefinition(env);
               
if (!list.contains(from[i])) {
                   
for (int j = 0; j < with.length; j++) {
                       
if (exceptionDef.subClassOf(env, with[j])) {
                            list
.addElement(from[i]);
                           
break;
                       
}
                   
}
               
}
           
}
       
}

       
/**
         * Compute the "method hash" of this remote method.  The method
         * hash is a long containing the first 64 bits of the SHA digest
         * from the UTF encoded string of the method name and descriptor.
         *
         * REMIND: Should this method share implementation code with
         * the outer class's computeInterfaceHash() method?
         */

       
private long computeMethodHash() {
           
long hash = 0;
           
ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
           
try {
               
MessageDigest md = MessageDigest.getInstance("SHA");
               
DataOutputStream out = new DataOutputStream(
                                                           
new DigestOutputStream(sink, md));

               
String methodString = getNameAndDescriptor();
               
/***** <DEBUG> */
               
if (env.verbose()) {
                   
System.out.println("[string used for method hash: \"" +
                                       methodString
+ "\"]");
               
}
               
/***** </DEBUG> */
               
out.writeUTF(methodString);

               
// use only the first 64 bits of the digest for the hash
               
out.flush();
               
byte hashArray[] = md.digest();
               
for (int i = 0; i < Math.min(8, hashArray.length); i++) {
                    hash
+= ((long) (hashArray[i] & 0xFF)) << (i * 8);
               
}
           
} catch (IOException e) {
               
throw new Error(
                               
"unexpected exception computing intetrface hash: " + e);
           
} catch (NoSuchAlgorithmException e) {
               
throw new Error(
                               
"unexpected exception computing intetrface hash: " + e);
           
}

           
return hash;
       
}
   
}
}