/*
* Copyright (c) 1997, 2013, 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 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 comparison
* 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 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<ClassDefinition> remotesImplemented = // list of remote interfaces found
new Vector<ClassDefinition>();
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<String, Method> methods = new Hashtable<String, Method>();
boolean errors = false;
for (Enumeration<ClassDefinition> enumeration
= remotesImplemented.elements();
enumeration.hasMoreElements();)
{
ClassDefinition interfaceDef = 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<Method> enumeration = methods.elements();
enumeration.hasMoreElements();)
{
Method m = 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] = 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<String, Method> 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 = 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-qualified 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 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<ClassDeclaration> legalExceptions
= new Vector<ClassDeclaration>();
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<ClassDeclaration> 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;
}
}
}