diff options
Diffstat (limited to 'src/share/classes/sun/rmi/rmic/RemoteClass.java')
-rw-r--r-- | src/share/classes/sun/rmi/rmic/RemoteClass.java | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/rmic/RemoteClass.java b/src/share/classes/sun/rmi/rmic/RemoteClass.java new file mode 100644 index 000000000..0caa07fe0 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/RemoteClass.java @@ -0,0 +1,875 @@ +/* + * Copyright 1997-2004 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; + +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; + } + } +} |