aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java')
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java710
1 files changed, 710 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java
new file mode 100644
index 000000000..f22a652df
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.Type;
+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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import sun.rmi.rmic.newrmic.BatchEnvironment;
+
+import static sun.rmi.rmic.newrmic.Constants.*;
+import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
+
+/**
+ * Encapsulates RMI-specific information about a remote implementation
+ * class (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
+ **/
+final class RemoteClass {
+
+ /** rmic environment for this object */
+ private final BatchEnvironment env;
+
+ /** the remote implementation class this object represents */
+ private final ClassDoc implClass;
+
+ /** remote interfaces implemented by this class */
+ private ClassDoc[] remoteInterfaces;
+
+ /** the remote methods of this class */
+ private Method[] remoteMethods;
+
+ /** stub/skeleton "interface hash" for this class */
+ private long interfaceHash;
+
+ /**
+ * Creates a RemoteClass instance that represents the RMI-specific
+ * information about the specified remote implementation class.
+ *
+ * If the class is not a valid 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.
+ **/
+ static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) {
+ RemoteClass remoteClass = new RemoteClass(env, implClass);
+ if (remoteClass.init()) {
+ return remoteClass;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a RemoteClass instance for the specified class. The
+ * resulting object is not yet initialized.
+ **/
+ private RemoteClass(BatchEnvironment env, ClassDoc implClass) {
+ this.env = env;
+ this.implClass = implClass;
+ }
+
+ /**
+ * Returns the ClassDoc for this remote implementation class.
+ **/
+ ClassDoc classDoc() {
+ return implClass;
+ }
+
+ /**
+ * Returns the remote interfaces implemented by this remote
+ * implementation class.
+ *
+ * A remote interface is an interface that is a subinterface of
+ * java.rmi.Remote. The remote interfaces of a class are the
+ * direct superinterfaces of the class and all 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).
+ **/
+ ClassDoc[] remoteInterfaces() {
+ return (ClassDoc[]) remoteInterfaces.clone();
+ }
+
+ /**
+ * Returns an array of RemoteClass.Method objects representing all
+ * of the remote methods of this remote implementation class (all
+ * of the member methods of the class's remote interfaces).
+ *
+ * The methods in the array are ordered according to a comparison
+ * of strings consisting of their name followed by their
+ * descriptor, so each method's index in the array corresponds to
+ * its "operation number" in the JDK 1.1 version of the JRMP
+ * stub/skeleton protocol.
+ **/
+ Method[] remoteMethods() {
+ return (Method[]) remoteMethods.clone();
+ }
+
+ /**
+ * Returns the "interface hash" used to match a stub/skeleton pair
+ * for this remote implementation class in the JDK 1.1 version of
+ * the JRMP stub/skeleton protocol.
+ **/
+ long interfaceHash() {
+ return interfaceHash;
+ }
+
+ /**
+ * Validates this remote implementation class and computes the
+ * RMI-specific information. Returns true if successful, or false
+ * if an error occurred.
+ **/
+ private boolean init() {
+ /*
+ * Verify that it is really a class, not an interface.
+ */
+ if (implClass.isInterface()) {
+ env.error("rmic.cant.make.stubs.for.interface",
+ implClass.qualifiedName());
+ return false;
+ }
+
+ /*
+ * 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.
+ */
+ List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>();
+ for (ClassDoc cl = implClass; cl != null; cl = cl.superclass()) {
+ for (ClassDoc intf : cl.interfaces()) {
+ /*
+ * Add interface to the list if it extends Remote and
+ * it is not already there.
+ */
+ if (!remotesImplemented.contains(intf) &&
+ intf.subclassOf(env.docRemote()))
+ {
+ remotesImplemented.add(intf);
+ if (env.verbose()) {
+ env.output("[found remote interface: " +
+ intf.qualifiedName() + "]");
+ }
+ }
+ }
+
+ /*
+ * Verify that the candidate remote implementation class
+ * implements at least one remote interface directly.
+ */
+ if (cl == implClass && remotesImplemented.isEmpty()) {
+ if (implClass.subclassOf(env.docRemote())) {
+ /*
+ * This error message is used if the class does
+ * implement a remote interface through one of its
+ * superclasses, but not directly.
+ */
+ env.error("rmic.must.implement.remote.directly",
+ implClass.qualifiedName());
+ } else {
+ /*
+ * This error message is used if the class does
+ * not implement a remote interface at all.
+ */
+ env.error("rmic.must.implement.remote",
+ implClass.qualifiedName());
+ }
+ return false;
+ }
+ }
+
+ /*
+ * Convert list of remote interfaces to an array
+ * (order is not important for this array).
+ */
+ remoteInterfaces =
+ remotesImplemented.toArray(
+ new ClassDoc[remotesImplemented.size()]);
+
+ /*
+ * Collect the methods from all of the remote interfaces into
+ * a table, which maps from method name-and-descriptor string
+ * to Method object.
+ */
+ Map<String,Method> methods = new HashMap<String,Method>();
+ boolean errors = false;
+ for (ClassDoc intf : remotesImplemented) {
+ if (!collectRemoteMethods(intf, methods)) {
+ /*
+ * Continue iterating despite errors in order to
+ * generate more complete error report.
+ */
+ errors = true;
+ }
+ }
+ if (errors) {
+ return false;
+ }
+
+ /*
+ * Sort table of remote methods into an array. The elements
+ * are sorted in ascending order of the string of the method's
+ * name and descriptor, so that each elements index is equal
+ * to its operation number in the JDK 1.1 version of the JRMP
+ * stub/skeleton protocol.
+ */
+ String[] orderedKeys =
+ methods.keySet().toArray(new String[methods.size()]);
+ Arrays.sort(orderedKeys);
+ remoteMethods = new Method[methods.size()];
+ for (int i = 0; i < remoteMethods.length; i++) {
+ remoteMethods[i] = methods.get(orderedKeys[i]);
+ if (env.verbose()) {
+ String msg = "[found remote method <" + i + ">: " +
+ remoteMethods[i].operationString();
+ ClassDoc[] exceptions = remoteMethods[i].exceptionTypes();
+ if (exceptions.length > 0) {
+ msg += " throws ";
+ for (int j = 0; j < exceptions.length; j++) {
+ if (j > 0) {
+ msg += ", ";
+ }
+ msg += exceptions[j].qualifiedName();
+ }
+ }
+ msg += "\n\tname and descriptor = \"" +
+ remoteMethods[i].nameAndDescriptor();
+ msg += "\n\tmethod hash = " +
+ remoteMethods[i].methodHash() + "]";
+ env.output(msg);
+ }
+ }
+
+ /*
+ * Finally, pre-compute the interface hash to be used by
+ * stubs/skeletons for this remote class in the JDK 1.1
+ * version of the JRMP stub/skeleton protocol.
+ */
+ interfaceHash = computeInterfaceHash();
+
+ return true;
+ }
+
+ /**
+ * Collects and validates all methods from the specified interface
+ * and all of its superinterfaces as remote methods. Remote
+ * methods are added to the supplied table. Returns true if
+ * successful, or false if an error occurred.
+ **/
+ private boolean collectRemoteMethods(ClassDoc intf,
+ Map<String,Method> table)
+ {
+ if (!intf.isInterface()) {
+ throw new AssertionError(
+ intf.qualifiedName() + " not an interface");
+ }
+
+ boolean errors = false;
+
+ /*
+ * Search interface's declared methods.
+ */
+ nextMethod:
+ for (MethodDoc method : intf.methods()) {
+
+ /*
+ * Verify that each method throws RemoteException (or a
+ * superclass of RemoteException).
+ */
+ boolean hasRemoteException = false;
+ for (ClassDoc ex : method.thrownExceptions()) {
+ if (env.docRemoteException().subclassOf(ex)) {
+ hasRemoteException = true;
+ break;
+ }
+ }
+
+ /*
+ * 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("rmic.must.throw.remoteexception",
+ intf.qualifiedName(),
+ method.name() + method.signature());
+ errors = true;
+ continue nextMethod;
+ }
+
+ /*
+ * 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.
+ */
+ MethodDoc implMethod = findImplMethod(method);
+ if (implMethod != null) { // should not be null
+ for (ClassDoc ex : implMethod.thrownExceptions()) {
+ if (!ex.subclassOf(env.docException())) {
+ env.error("rmic.must.only.throw.exception",
+ implMethod.name() + implMethod.signature(),
+ ex.qualifiedName());
+ errors = true;
+ continue nextMethod;
+ }
+ }
+ }
+
+ /*
+ * Create RemoteClass.Method object to represent this method
+ * found in a remote interface.
+ */
+ Method newMethod = new Method(method);
+
+ /*
+ * Store remote method's representation in the table of
+ * remote methods found, keyed by its name and descriptor.
+ *
+ * If the table already contains an entry with the same
+ * method name and descriptor, 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
+ * clause that contains (only) all of the checked
+ * exceptions that can be thrown by both the old and the
+ * new method (see bugid 4070653).
+ */
+ String key = newMethod.nameAndDescriptor();
+ Method oldMethod = table.get(key);
+ if (oldMethod != null) {
+ newMethod = newMethod.mergeWith(oldMethod);
+ }
+ table.put(key, newMethod);
+ }
+
+ /*
+ * Recursively collect methods for all superinterfaces.
+ */
+ for (ClassDoc superintf : intf.interfaces()) {
+ if (!collectRemoteMethods(superintf, table)) {
+ errors = true;
+ }
+ }
+
+ return !errors;
+ }
+
+ /**
+ * Returns the MethodDoc for the method of this remote
+ * implementation class that implements the specified remote
+ * method of a remote interface. Returns null if no matching
+ * method was found in this remote implementation class.
+ **/
+ private MethodDoc findImplMethod(MethodDoc interfaceMethod) {
+ String name = interfaceMethod.name();
+ String desc = Util.methodDescriptorOf(interfaceMethod);
+ for (MethodDoc implMethod : implClass.methods()) {
+ if (name.equals(implMethod.name()) &&
+ desc.equals(Util.methodDescriptorOf(implMethod)))
+ {
+ return implMethod;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes 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 class and a
+ * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton
+ * protocol.
+ *
+ * It is calculated using the first 64 bits of an SHA digest. The
+ * digest is of a stream consisting of the following data:
+ * (int) stub version number, always 1
+ * for each remote method, in order of operation number:
+ * (UTF-8) method name
+ * (UTF-8) method descriptor
+ * for each declared exception, in alphabetical name order:
+ * (UTF-8) name of exception class
+ * (where "UTF-8" includes a 16-bit length prefix as written by
+ * java.io.DataOutput.writeUTF).
+ **/
+ 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 (Method method : remoteMethods) {
+ MethodDoc methodDoc = method.methodDoc();
+
+ out.writeUTF(methodDoc.name());
+ out.writeUTF(Util.methodDescriptorOf(methodDoc));
+ // descriptors already use binary names
+
+ ClassDoc exceptions[] = methodDoc.thrownExceptions();
+ Arrays.sort(exceptions, new ClassDocComparator());
+ for (ClassDoc ex : exceptions) {
+ out.writeUTF(Util.binaryNameOf(ex));
+ }
+ }
+ 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 AssertionError(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+
+ return hash;
+ }
+
+ /**
+ * Compares ClassDoc instances according to the lexicographic
+ * order of their binary names.
+ **/
+ private static class ClassDocComparator implements Comparator<ClassDoc> {
+ public int compare(ClassDoc o1, ClassDoc o2) {
+ return Util.binaryNameOf(o1).compareTo(Util.binaryNameOf(o2));
+ }
+ }
+
+ /**
+ * Encapsulates RMI-specific information about a particular remote
+ * method in the remote implementation class represented by the
+ * enclosing RemoteClass.
+ **/
+ final class Method implements Cloneable {
+
+ /**
+ * MethodDoc for this remove method, from one of the remote
+ * interfaces that this method was found in.
+ *
+ * Note that this MethodDoc may be only one of multiple that
+ * correspond to this remote method object, if multiple of
+ * this class's remote interfaces contain methods with the
+ * same name and descriptor. Therefore, this MethodDoc may
+ * declare more exceptions thrown that this remote method
+ * does.
+ **/
+ private final MethodDoc methodDoc;
+
+ /** java.rmi.server.Operation string for this remote method */
+ private final String operationString;
+
+ /** name and descriptor of this remote method */
+ private final String nameAndDescriptor;
+
+ /** JRMP "method hash" for this remote method */
+ private final long methodHash;
+
+ /**
+ * Exceptions declared to be thrown by this remote method.
+ *
+ * This list may include superfluous entries, such as
+ * unchecked exceptions and subclasses of other entries.
+ **/
+ private ClassDoc[] exceptionTypes;
+
+ /**
+ * Creates a new Method instance for the specified method.
+ **/
+ Method(MethodDoc methodDoc) {
+ this.methodDoc = methodDoc;
+ exceptionTypes = methodDoc.thrownExceptions();
+ /*
+ * Sort exception types to improve consistency with
+ * previous implementations.
+ */
+ Arrays.sort(exceptionTypes, new ClassDocComparator());
+ operationString = computeOperationString();
+ nameAndDescriptor =
+ methodDoc.name() + Util.methodDescriptorOf(methodDoc);
+ methodHash = computeMethodHash();
+ }
+
+ /**
+ * Returns the MethodDoc object corresponding to this method
+ * of a remote interface.
+ **/
+ MethodDoc methodDoc() {
+ return methodDoc;
+ }
+
+ /**
+ * Returns the parameter types declared by this method.
+ **/
+ Type[] parameterTypes() {
+ Parameter[] parameters = methodDoc.parameters();
+ Type[] paramTypes = new Type[parameters.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ paramTypes[i] = parameters[i].type();
+ }
+ return paramTypes;
+ }
+
+ /**
+ * Returns the exception types declared to be thrown by this
+ * remote method.
+ *
+ * For methods with the same name and descriptor 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 by all of them.
+ **/
+ ClassDoc[] exceptionTypes() {
+ return (ClassDoc[]) exceptionTypes.clone();
+ }
+
+ /**
+ * Returns the JRMP "method hash" used to identify this remote
+ * method in the JDK 1.2 version of the stub protocol.
+ **/
+ long methodHash() {
+ return methodHash;
+ }
+
+ /**
+ * Returns the string representation of this method
+ * appropriate for the construction of a
+ * java.rmi.server.Operation object.
+ **/
+ String operationString() {
+ return operationString;
+ }
+
+ /**
+ * Returns a string consisting of this method's name followed
+ * by its descriptor.
+ **/
+ String nameAndDescriptor() {
+ return nameAndDescriptor;
+ }
+
+ /**
+ * Returns a new Method object that is a legal combination of
+ * this Method object and another one.
+ *
+ * Doing 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.
+ **/
+ Method mergeWith(Method other) {
+ if (!nameAndDescriptor().equals(other.nameAndDescriptor())) {
+ throw new AssertionError(
+ "attempt to merge method \"" +
+ other.nameAndDescriptor() + "\" with \"" +
+ nameAndDescriptor());
+ }
+
+ List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>();
+ collectCompatibleExceptions(
+ other.exceptionTypes, exceptionTypes, legalExceptions);
+ collectCompatibleExceptions(
+ exceptionTypes, other.exceptionTypes, legalExceptions);
+
+ Method merged = clone();
+ merged.exceptionTypes =
+ legalExceptions.toArray(new ClassDoc[legalExceptions.size()]);
+
+ return merged;
+ }
+
+ /**
+ * Cloning is supported by returning a shallow copy of this
+ * object.
+ **/
+ protected Method clone() {
+ try {
+ return (Method) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Adds to the supplied list all exceptions in the "froms"
+ * array that are subclasses of an exception in the "withs"
+ * array.
+ **/
+ private void collectCompatibleExceptions(ClassDoc[] froms,
+ ClassDoc[] withs,
+ List<ClassDoc> list)
+ {
+ for (ClassDoc from : froms) {
+ if (!list.contains(from)) {
+ for (ClassDoc with : withs) {
+ if (from.subclassOf(with)) {
+ list.add(from);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes the JRMP "method hash" of this remote method. The
+ * method hash is a long containing the first 64 bits of the
+ * SHA digest from the UTF-8 encoded string of the method name
+ * and descriptor.
+ **/
+ 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 = nameAndDescriptor();
+ 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 AssertionError(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+
+ return hash;
+ }
+
+ /**
+ * Computes the string representation of this method
+ * appropriate for the construction of a
+ * java.rmi.server.Operation object.
+ **/
+ private String computeOperationString() {
+ /*
+ * To be consistent with previous implementations, we use
+ * the deprecated style of placing the "[]" for the return
+ * type (if any) after the parameter list.
+ */
+ Type returnType = methodDoc.returnType();
+ String op = returnType.qualifiedTypeName() + " " +
+ methodDoc.name() + "(";
+ Parameter[] parameters = methodDoc.parameters();
+ for (int i = 0; i < parameters.length; i++) {
+ if (i > 0) {
+ op += ", ";
+ }
+ op += parameters[i].type().toString();
+ }
+ op += ")" + returnType.dimension();
+ return op;
+ }
+ }
+}