1080 lines
40 KiB
Java
1080 lines
40 KiB
Java
/*
|
|
* Copyright (c) 2003, 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.newrmic.jrmp;
|
|
|
|
import com.sun.javadoc.ClassDoc;
|
|
import com.sun.javadoc.MethodDoc;
|
|
import com.sun.javadoc.Type;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import sun.rmi.rmic.newrmic.BatchEnvironment;
|
|
import sun.rmi.rmic.newrmic.IndentingWriter;
|
|
|
|
import static sun.rmi.rmic.newrmic.Constants.*;
|
|
import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
|
|
|
|
/**
|
|
* Writes the source code for the stub class and (optionally) skeleton
|
|
* class for a particular remote implementation class.
|
|
*
|
|
* 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
|
|
**/
|
|
class StubSkeletonWriter {
|
|
|
|
/** rmic environment for this object */
|
|
private final BatchEnvironment env;
|
|
|
|
/** the remote implementation class to generate code for */
|
|
private final RemoteClass remoteClass;
|
|
|
|
/** version of the JRMP stub protocol to generate code for */
|
|
private final StubVersion version;
|
|
|
|
/*
|
|
* binary names of the stub and skeleton classes to generate for
|
|
* the remote class
|
|
*/
|
|
private final String stubClassName;
|
|
private final String skeletonClassName;
|
|
|
|
/* package name and simple names of the stub and skeleton classes */
|
|
private final String packageName;
|
|
private final String stubClassSimpleName;
|
|
private final String skeletonClassSimpleName;
|
|
|
|
/** remote methods of class, indexed by operation number */
|
|
private final RemoteClass.Method[] remoteMethods;
|
|
|
|
/**
|
|
* Names to use for the java.lang.reflect.Method static fields in
|
|
* the generated stub class corresponding to each remote method.
|
|
**/
|
|
private final String[] methodFieldNames;
|
|
|
|
/**
|
|
* Creates a StubSkeletonWriter instance for the specified remote
|
|
* implementation class. The generated code will implement the
|
|
* specified JRMP stub protocol version.
|
|
**/
|
|
StubSkeletonWriter(BatchEnvironment env,
|
|
RemoteClass remoteClass,
|
|
StubVersion version)
|
|
{
|
|
this.env = env;
|
|
this.remoteClass = remoteClass;
|
|
this.version = version;
|
|
|
|
stubClassName = Util.binaryNameOf(remoteClass.classDoc()) + "_Stub";
|
|
skeletonClassName =
|
|
Util.binaryNameOf(remoteClass.classDoc()) + "_Skel";
|
|
|
|
int i = stubClassName.lastIndexOf('.');
|
|
packageName = (i != -1 ? stubClassName.substring(0, i) : "");
|
|
stubClassSimpleName = stubClassName.substring(i + 1);
|
|
skeletonClassSimpleName = skeletonClassName.substring(i + 1);
|
|
|
|
remoteMethods = remoteClass.remoteMethods();
|
|
methodFieldNames = nameMethodFields(remoteMethods);
|
|
}
|
|
|
|
/**
|
|
* Returns the binary name of the stub class to generate for the
|
|
* remote implementation class.
|
|
**/
|
|
String stubClassName() {
|
|
return stubClassName;
|
|
}
|
|
|
|
/**
|
|
* Returns the binary name of the skeleton class to generate for
|
|
* the remote implementation class.
|
|
**/
|
|
String skeletonClassName() {
|
|
return skeletonClassName;
|
|
}
|
|
|
|
/**
|
|
* Writes the stub class for the remote class to a stream.
|
|
**/
|
|
void writeStub(IndentingWriter p) throws IOException {
|
|
|
|
/*
|
|
* Write boiler plate comment.
|
|
*/
|
|
p.pln("// Stub class generated by rmic, do not edit.");
|
|
p.pln("// Contents subject to change without notice.");
|
|
p.pln();
|
|
|
|
/*
|
|
* If remote implementation class was in a particular package,
|
|
* declare the stub class to be in the same package.
|
|
*/
|
|
if (!packageName.equals("")) {
|
|
p.pln("package " + packageName + ";");
|
|
p.pln();
|
|
}
|
|
|
|
/*
|
|
* Declare the stub class; implement all remote interfaces.
|
|
*/
|
|
p.plnI("public final class " + stubClassSimpleName);
|
|
p.pln("extends " + REMOTE_STUB);
|
|
ClassDoc[] remoteInterfaces = remoteClass.remoteInterfaces();
|
|
if (remoteInterfaces.length > 0) {
|
|
p.p("implements ");
|
|
for (int i = 0; i < remoteInterfaces.length; i++) {
|
|
if (i > 0) {
|
|
p.p(", ");
|
|
}
|
|
p.p(remoteInterfaces[i].qualifiedName());
|
|
}
|
|
p.pln();
|
|
}
|
|
p.pOlnI("{");
|
|
|
|
if (version == StubVersion.V1_1 ||
|
|
version == StubVersion.VCOMPAT)
|
|
{
|
|
writeOperationsArray(p);
|
|
p.pln();
|
|
writeInterfaceHash(p);
|
|
p.pln();
|
|
}
|
|
|
|
if (version == StubVersion.VCOMPAT ||
|
|
version == StubVersion.V1_2)
|
|
{
|
|
p.pln("private static final long serialVersionUID = " +
|
|
STUB_SERIAL_VERSION_UID + ";");
|
|
p.pln();
|
|
|
|
/*
|
|
* We only need to declare and initialize the static fields of
|
|
* Method objects for each remote method if there are any remote
|
|
* methods; otherwise, skip this code entirely, to avoid generating
|
|
* a try/catch block for a checked exception that cannot occur
|
|
* (see bugid 4125181).
|
|
*/
|
|
if (methodFieldNames.length > 0) {
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.pln("private static boolean useNewInvoke;");
|
|
}
|
|
writeMethodFieldDeclarations(p);
|
|
p.pln();
|
|
|
|
/*
|
|
* Initialize java.lang.reflect.Method fields for each remote
|
|
* method in a static initializer.
|
|
*/
|
|
p.plnI("static {");
|
|
p.plnI("try {");
|
|
if (version == StubVersion.VCOMPAT) {
|
|
/*
|
|
* Fat stubs must determine whether the API required for
|
|
* the JDK 1.2 stub protocol is supported in the current
|
|
* runtime, so that it can use it if supported. This is
|
|
* determined by using the Reflection API to test if the
|
|
* new invoke method on RemoteRef exists, and setting the
|
|
* static boolean "useNewInvoke" to true if it does, or
|
|
* to false if a NoSuchMethodException is thrown.
|
|
*/
|
|
p.plnI(REMOTE_REF + ".class.getMethod(\"invoke\",");
|
|
p.plnI("new java.lang.Class[] {");
|
|
p.pln(REMOTE + ".class,");
|
|
p.pln("java.lang.reflect.Method.class,");
|
|
p.pln("java.lang.Object[].class,");
|
|
p.pln("long.class");
|
|
p.pOln("});");
|
|
p.pO();
|
|
p.pln("useNewInvoke = true;");
|
|
}
|
|
writeMethodFieldInitializers(p);
|
|
p.pOlnI("} catch (java.lang.NoSuchMethodException e) {");
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.pln("useNewInvoke = false;");
|
|
} else {
|
|
p.plnI("throw new java.lang.NoSuchMethodError(");
|
|
p.pln("\"stub class initialization failed\");");
|
|
p.pO();
|
|
}
|
|
p.pOln("}"); // end try/catch block
|
|
p.pOln("}"); // end static initializer
|
|
p.pln();
|
|
}
|
|
}
|
|
|
|
writeStubConstructors(p);
|
|
p.pln();
|
|
|
|
/*
|
|
* Write each stub method.
|
|
*/
|
|
if (remoteMethods.length > 0) {
|
|
p.pln("// methods from remote interfaces");
|
|
for (int i = 0; i < remoteMethods.length; ++i) {
|
|
p.pln();
|
|
writeStubMethod(p, i);
|
|
}
|
|
}
|
|
|
|
p.pOln("}"); // end stub class
|
|
}
|
|
|
|
/**
|
|
* Writes the constructors for the stub class.
|
|
**/
|
|
private void writeStubConstructors(IndentingWriter p)
|
|
throws IOException
|
|
{
|
|
p.pln("// constructors");
|
|
|
|
/*
|
|
* Only stubs compatible with the JDK 1.1 stub protocol need
|
|
* a no-arg constructor; later versions use reflection to find
|
|
* the constructor that directly takes a RemoteRef argument.
|
|
*/
|
|
if (version == StubVersion.V1_1 ||
|
|
version == StubVersion.VCOMPAT)
|
|
{
|
|
p.plnI("public " + stubClassSimpleName + "() {");
|
|
p.pln("super();");
|
|
p.pOln("}");
|
|
}
|
|
|
|
p.plnI("public " + stubClassSimpleName + "(" + REMOTE_REF + " ref) {");
|
|
p.pln("super(ref);");
|
|
p.pOln("}");
|
|
}
|
|
|
|
/**
|
|
* Writes the stub method for the remote method with the given
|
|
* operation number.
|
|
**/
|
|
private void writeStubMethod(IndentingWriter p, int opnum)
|
|
throws IOException
|
|
{
|
|
RemoteClass.Method method = remoteMethods[opnum];
|
|
MethodDoc methodDoc = method.methodDoc();
|
|
String methodName = methodDoc.name();
|
|
Type[] paramTypes = method.parameterTypes();
|
|
String paramNames[] = nameParameters(paramTypes);
|
|
Type returnType = methodDoc.returnType();
|
|
ClassDoc[] exceptions = method.exceptionTypes();
|
|
|
|
/*
|
|
* Declare stub method; throw exceptions declared in remote
|
|
* interface(s).
|
|
*/
|
|
p.pln("// implementation of " +
|
|
Util.getFriendlyUnqualifiedSignature(methodDoc));
|
|
p.p("public " + returnType.toString() + " " + methodName + "(");
|
|
for (int i = 0; i < paramTypes.length; i++) {
|
|
if (i > 0) {
|
|
p.p(", ");
|
|
}
|
|
p.p(paramTypes[i].toString() + " " + paramNames[i]);
|
|
}
|
|
p.plnI(")");
|
|
if (exceptions.length > 0) {
|
|
p.p("throws ");
|
|
for (int i = 0; i < exceptions.length; i++) {
|
|
if (i > 0) {
|
|
p.p(", ");
|
|
}
|
|
p.p(exceptions[i].qualifiedName());
|
|
}
|
|
p.pln();
|
|
}
|
|
p.pOlnI("{");
|
|
|
|
/*
|
|
* The RemoteRef.invoke methods throw Exception, but unless
|
|
* this stub method throws Exception as well, we must catch
|
|
* Exceptions thrown from the invocation. So we must catch
|
|
* Exception and rethrow something we can throw:
|
|
* UnexpectedException, which is a subclass of
|
|
* RemoteException. But for any subclasses of Exception that
|
|
* we can throw, like RemoteException, RuntimeException, and
|
|
* any of the exceptions declared by this stub method, we want
|
|
* them to pass through unmodified, so first we must catch any
|
|
* such exceptions and rethrow them directly.
|
|
*
|
|
* We have to be careful generating the rethrowing catch
|
|
* blocks here, because javac will flag an error if there are
|
|
* any unreachable catch blocks, i.e. if the catch of an
|
|
* exception class follows a previous catch of it or of one of
|
|
* its superclasses. The following method invocation takes
|
|
* care of these details.
|
|
*/
|
|
List<ClassDoc> catchList = computeUniqueCatchList(exceptions);
|
|
|
|
/*
|
|
* If we need to catch any particular exceptions (i.e. this method
|
|
* does not declare java.lang.Exception), put the entire stub
|
|
* method in a try block.
|
|
*/
|
|
if (catchList.size() > 0) {
|
|
p.plnI("try {");
|
|
}
|
|
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.plnI("if (useNewInvoke) {");
|
|
}
|
|
if (version == StubVersion.VCOMPAT ||
|
|
version == StubVersion.V1_2)
|
|
{
|
|
if (!Util.isVoid(returnType)) {
|
|
p.p("Object $result = "); // REMIND: why $?
|
|
}
|
|
p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", ");
|
|
if (paramTypes.length > 0) {
|
|
p.p("new java.lang.Object[] {");
|
|
for (int i = 0; i < paramTypes.length; i++) {
|
|
if (i > 0)
|
|
p.p(", ");
|
|
p.p(wrapArgumentCode(paramTypes[i], paramNames[i]));
|
|
}
|
|
p.p("}");
|
|
} else {
|
|
p.p("null");
|
|
}
|
|
p.pln(", " + method.methodHash() + "L);");
|
|
if (!Util.isVoid(returnType)) {
|
|
p.pln("return " +
|
|
unwrapArgumentCode(returnType, "$result") + ";");
|
|
}
|
|
}
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.pOlnI("} else {");
|
|
}
|
|
if (version == StubVersion.V1_1 ||
|
|
version == StubVersion.VCOMPAT)
|
|
{
|
|
p.pln(REMOTE_CALL + " call = ref.newCall((" + REMOTE_OBJECT +
|
|
") this, operations, " + opnum + ", interfaceHash);");
|
|
|
|
if (paramTypes.length > 0) {
|
|
p.plnI("try {");
|
|
p.pln("java.io.ObjectOutput out = call.getOutputStream();");
|
|
writeMarshalArguments(p, "out", paramTypes, paramNames);
|
|
p.pOlnI("} catch (java.io.IOException e) {");
|
|
p.pln("throw new " + MARSHAL_EXCEPTION +
|
|
"(\"error marshalling arguments\", e);");
|
|
p.pOln("}");
|
|
}
|
|
|
|
p.pln("ref.invoke(call);");
|
|
|
|
if (Util.isVoid(returnType)) {
|
|
p.pln("ref.done(call);");
|
|
} else {
|
|
p.pln(returnType.toString() + " $result;");
|
|
// REMIND: why $?
|
|
p.plnI("try {");
|
|
p.pln("java.io.ObjectInput in = call.getInputStream();");
|
|
boolean objectRead =
|
|
writeUnmarshalArgument(p, "in", returnType, "$result");
|
|
p.pln(";");
|
|
p.pOlnI("} catch (java.io.IOException e) {");
|
|
p.pln("throw new " + UNMARSHAL_EXCEPTION +
|
|
"(\"error unmarshalling return\", e);");
|
|
/*
|
|
* If any only if readObject has been invoked, we must catch
|
|
* ClassNotFoundException as well as IOException.
|
|
*/
|
|
if (objectRead) {
|
|
p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
|
|
p.pln("throw new " + UNMARSHAL_EXCEPTION +
|
|
"(\"error unmarshalling return\", e);");
|
|
}
|
|
p.pOlnI("} finally {");
|
|
p.pln("ref.done(call);");
|
|
p.pOln("}");
|
|
p.pln("return $result;");
|
|
}
|
|
}
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.pOln("}"); // end if/else (useNewInvoke) block
|
|
}
|
|
|
|
/*
|
|
* If we need to catch any particular exceptions, finally write
|
|
* the catch blocks for them, rethrow any other Exceptions with an
|
|
* UnexpectedException, and end the try block.
|
|
*/
|
|
if (catchList.size() > 0) {
|
|
for (ClassDoc catchClass : catchList) {
|
|
p.pOlnI("} catch (" + catchClass.qualifiedName() + " e) {");
|
|
p.pln("throw e;");
|
|
}
|
|
p.pOlnI("} catch (java.lang.Exception e) {");
|
|
p.pln("throw new " + UNEXPECTED_EXCEPTION +
|
|
"(\"undeclared checked exception\", e);");
|
|
p.pOln("}"); // end try/catch block
|
|
}
|
|
|
|
p.pOln("}"); // end stub method
|
|
}
|
|
|
|
/**
|
|
* Computes the exceptions that need to be caught and rethrown in
|
|
* a stub method before wrapping Exceptions in
|
|
* UnexpectedExceptions, given the exceptions declared in the
|
|
* throws clause of the method. Returns a list containing the
|
|
* exception to catch. Each exception is guaranteed to be unique,
|
|
* i.e. not a subclass of any of the other exceptions in the list,
|
|
* so the catch blocks for these exceptions may be generated in
|
|
* any order relative to each other.
|
|
*
|
|
* RemoteException and RuntimeException are each automatically
|
|
* placed in the returned list (unless any of their superclasses
|
|
* are already present), since those exceptions should always be
|
|
* directly rethrown by a stub method.
|
|
*
|
|
* The returned list will be empty if java.lang.Exception or one
|
|
* of its superclasses is in the throws clause of the method,
|
|
* indicating that no exceptions need to be caught.
|
|
**/
|
|
private List<ClassDoc> computeUniqueCatchList(ClassDoc[] exceptions) {
|
|
List<ClassDoc> uniqueList = new ArrayList<ClassDoc>();
|
|
|
|
uniqueList.add(env.docRuntimeException());
|
|
uniqueList.add(env.docRemoteException()); // always catch/rethrow these
|
|
|
|
/* For each exception declared by the stub method's throws clause: */
|
|
nextException:
|
|
for (ClassDoc ex : exceptions) {
|
|
if (env.docException().subclassOf(ex)) {
|
|
/*
|
|
* If java.lang.Exception (or a superclass) was declared
|
|
* in the throws clause of this stub method, then we don't
|
|
* have to bother catching anything; clear the list and
|
|
* return.
|
|
*/
|
|
uniqueList.clear();
|
|
break;
|
|
} else if (!ex.subclassOf(env.docException())) {
|
|
/*
|
|
* Ignore other Throwables that do not extend Exception,
|
|
* because they cannot be thrown by the invoke methods.
|
|
*/
|
|
continue;
|
|
}
|
|
/*
|
|
* Compare this exception against the current list of
|
|
* exceptions that need to be caught:
|
|
*/
|
|
for (Iterator<ClassDoc> i = uniqueList.iterator(); i.hasNext();) {
|
|
ClassDoc ex2 = i.next();
|
|
if (ex.subclassOf(ex2)) {
|
|
/*
|
|
* If a superclass of this exception is already on
|
|
* the list to catch, then ignore this one and continue;
|
|
*/
|
|
continue nextException;
|
|
} else if (ex2.subclassOf(ex)) {
|
|
/*
|
|
* If a subclass of this exception is on the list
|
|
* to catch, then remove it;
|
|
*/
|
|
i.remove();
|
|
}
|
|
}
|
|
/* This exception is unique: add it to the list to catch. */
|
|
uniqueList.add(ex);
|
|
}
|
|
return uniqueList;
|
|
}
|
|
|
|
/**
|
|
* Writes the skeleton for the remote class to a stream.
|
|
**/
|
|
void writeSkeleton(IndentingWriter p) throws IOException {
|
|
if (version == StubVersion.V1_2) {
|
|
throw new AssertionError(
|
|
"should not generate skeleton for version " + version);
|
|
}
|
|
|
|
/*
|
|
* Write boiler plate comment.
|
|
*/
|
|
p.pln("// Skeleton class generated by rmic, do not edit.");
|
|
p.pln("// Contents subject to change without notice.");
|
|
p.pln();
|
|
|
|
/*
|
|
* If remote implementation class was in a particular package,
|
|
* declare the skeleton class to be in the same package.
|
|
*/
|
|
if (!packageName.equals("")) {
|
|
p.pln("package " + packageName + ";");
|
|
p.pln();
|
|
}
|
|
|
|
/*
|
|
* Declare the skeleton class.
|
|
*/
|
|
p.plnI("public final class " + skeletonClassSimpleName);
|
|
p.pln("implements " + SKELETON);
|
|
p.pOlnI("{");
|
|
|
|
writeOperationsArray(p);
|
|
p.pln();
|
|
|
|
writeInterfaceHash(p);
|
|
p.pln();
|
|
|
|
/*
|
|
* Define the getOperations() method.
|
|
*/
|
|
p.plnI("public " + OPERATION + "[] getOperations() {");
|
|
p.pln("return (" + OPERATION + "[]) operations.clone();");
|
|
p.pOln("}");
|
|
p.pln();
|
|
|
|
/*
|
|
* Define the dispatch() method.
|
|
*/
|
|
p.plnI("public void dispatch(" + REMOTE + " obj, " +
|
|
REMOTE_CALL + " call, int opnum, long hash)");
|
|
p.pln("throws java.lang.Exception");
|
|
p.pOlnI("{");
|
|
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.plnI("if (opnum < 0) {");
|
|
if (remoteMethods.length > 0) {
|
|
for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
|
|
if (opnum > 0)
|
|
p.pO("} else ");
|
|
p.plnI("if (hash == " +
|
|
remoteMethods[opnum].methodHash() + "L) {");
|
|
p.pln("opnum = " + opnum + ";");
|
|
}
|
|
p.pOlnI("} else {");
|
|
}
|
|
/*
|
|
* Skeleton throws UnmarshalException if it does not recognize
|
|
* the method hash; this is what UnicastServerRef.dispatch()
|
|
* would do.
|
|
*/
|
|
p.pln("throw new " +
|
|
UNMARSHAL_EXCEPTION + "(\"invalid method hash\");");
|
|
if (remoteMethods.length > 0) {
|
|
p.pOln("}");
|
|
}
|
|
/*
|
|
* Ignore the validation of the interface hash if the
|
|
* operation number was negative, since it is really a
|
|
* method hash instead.
|
|
*/
|
|
p.pOlnI("} else {");
|
|
}
|
|
|
|
p.plnI("if (hash != interfaceHash)");
|
|
p.pln("throw new " +
|
|
SKELETON_MISMATCH_EXCEPTION + "(\"interface hash mismatch\");");
|
|
p.pO();
|
|
|
|
if (version == StubVersion.VCOMPAT) {
|
|
p.pOln("}"); // end if/else (opnum < 0) block
|
|
}
|
|
p.pln();
|
|
|
|
/*
|
|
* Cast remote object reference to the remote implementation
|
|
* class, if it's not private. We don't use the binary name
|
|
* of the class like previous implementations did because that
|
|
* would not compile with javac (since 1.4.1). If the remote
|
|
* implementation class is private, then we can't cast to it
|
|
* like previous implementations did because that also would
|
|
* not compile with javac-- so instead, we'll have to try to
|
|
* cast to the remote interface for each remote method.
|
|
*/
|
|
if (!remoteClass.classDoc().isPrivate()) {
|
|
p.pln(remoteClass.classDoc().qualifiedName() + " server = (" +
|
|
remoteClass.classDoc().qualifiedName() + ") obj;");
|
|
}
|
|
|
|
/*
|
|
* Process call according to the operation number.
|
|
*/
|
|
p.plnI("switch (opnum) {");
|
|
for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
|
|
writeSkeletonDispatchCase(p, opnum);
|
|
}
|
|
p.pOlnI("default:");
|
|
/*
|
|
* Skeleton throws UnmarshalException if it does not recognize
|
|
* the operation number; this is consistent with the case of an
|
|
* unrecognized method hash.
|
|
*/
|
|
p.pln("throw new " + UNMARSHAL_EXCEPTION +
|
|
"(\"invalid method number\");");
|
|
p.pOln("}"); // end switch statement
|
|
|
|
p.pOln("}"); // end dispatch() method
|
|
|
|
p.pOln("}"); // end skeleton class
|
|
}
|
|
|
|
/**
|
|
* Writes the case block for the skeleton's dispatch method for
|
|
* the remote method with the given "opnum".
|
|
**/
|
|
private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
|
|
throws IOException
|
|
{
|
|
RemoteClass.Method method = remoteMethods[opnum];
|
|
MethodDoc methodDoc = method.methodDoc();
|
|
String methodName = methodDoc.name();
|
|
Type paramTypes[] = method.parameterTypes();
|
|
String paramNames[] = nameParameters(paramTypes);
|
|
Type returnType = methodDoc.returnType();
|
|
|
|
p.pOlnI("case " + opnum + ": // " +
|
|
Util.getFriendlyUnqualifiedSignature(methodDoc));
|
|
/*
|
|
* Use nested block statement inside case to provide an independent
|
|
* namespace for local variables used to unmarshal parameters for
|
|
* this remote method.
|
|
*/
|
|
p.pOlnI("{");
|
|
|
|
if (paramTypes.length > 0) {
|
|
/*
|
|
* Declare local variables to hold arguments.
|
|
*/
|
|
for (int i = 0; i < paramTypes.length; i++) {
|
|
p.pln(paramTypes[i].toString() + " " + paramNames[i] + ";");
|
|
}
|
|
|
|
/*
|
|
* Unmarshal arguments from call stream.
|
|
*/
|
|
p.plnI("try {");
|
|
p.pln("java.io.ObjectInput in = call.getInputStream();");
|
|
boolean objectsRead = writeUnmarshalArguments(p, "in",
|
|
paramTypes, paramNames);
|
|
p.pOlnI("} catch (java.io.IOException e) {");
|
|
p.pln("throw new " + UNMARSHAL_EXCEPTION +
|
|
"(\"error unmarshalling arguments\", e);");
|
|
/*
|
|
* If any only if readObject has been invoked, we must catch
|
|
* ClassNotFoundException as well as IOException.
|
|
*/
|
|
if (objectsRead) {
|
|
p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
|
|
p.pln("throw new " + UNMARSHAL_EXCEPTION +
|
|
"(\"error unmarshalling arguments\", e);");
|
|
}
|
|
p.pOlnI("} finally {");
|
|
p.pln("call.releaseInputStream();");
|
|
p.pOln("}");
|
|
} else {
|
|
p.pln("call.releaseInputStream();");
|
|
}
|
|
|
|
if (!Util.isVoid(returnType)) {
|
|
/*
|
|
* Declare variable to hold return type, if not void.
|
|
*/
|
|
p.p(returnType.toString() + " $result = ");
|
|
// REMIND: why $?
|
|
}
|
|
|
|
/*
|
|
* Invoke the method on the server object. If the remote
|
|
* implementation class is private, then we don't have a
|
|
* reference cast to it, and so we try to cast to the remote
|
|
* object reference to the method's declaring interface here.
|
|
*/
|
|
String target = remoteClass.classDoc().isPrivate() ?
|
|
"((" + methodDoc.containingClass().qualifiedName() + ") obj)" :
|
|
"server";
|
|
p.p(target + "." + methodName + "(");
|
|
for (int i = 0; i < paramNames.length; i++) {
|
|
if (i > 0)
|
|
p.p(", ");
|
|
p.p(paramNames[i]);
|
|
}
|
|
p.pln(");");
|
|
|
|
/*
|
|
* Always invoke getResultStream(true) on the call object to send
|
|
* the indication of a successful invocation to the caller. If
|
|
* the return type is not void, keep the result stream and marshal
|
|
* the return value.
|
|
*/
|
|
p.plnI("try {");
|
|
if (!Util.isVoid(returnType)) {
|
|
p.p("java.io.ObjectOutput out = ");
|
|
}
|
|
p.pln("call.getResultStream(true);");
|
|
if (!Util.isVoid(returnType)) {
|
|
writeMarshalArgument(p, "out", returnType, "$result");
|
|
p.pln(";");
|
|
}
|
|
p.pOlnI("} catch (java.io.IOException e) {");
|
|
p.pln("throw new " +
|
|
MARSHAL_EXCEPTION + "(\"error marshalling return\", e);");
|
|
p.pOln("}");
|
|
|
|
p.pln("break;"); // break from switch statement
|
|
|
|
p.pOlnI("}"); // end nested block statement
|
|
p.pln();
|
|
}
|
|
|
|
/**
|
|
* Writes declaration and initializer for "operations" static array.
|
|
**/
|
|
private void writeOperationsArray(IndentingWriter p)
|
|
throws IOException
|
|
{
|
|
p.plnI("private static final " + OPERATION + "[] operations = {");
|
|
for (int i = 0; i < remoteMethods.length; i++) {
|
|
if (i > 0)
|
|
p.pln(",");
|
|
p.p("new " + OPERATION + "(\"" +
|
|
remoteMethods[i].operationString() + "\")");
|
|
}
|
|
p.pln();
|
|
p.pOln("};");
|
|
}
|
|
|
|
/**
|
|
* Writes declaration and initializer for "interfaceHash" static field.
|
|
**/
|
|
private void writeInterfaceHash(IndentingWriter p)
|
|
throws IOException
|
|
{
|
|
p.pln("private static final long interfaceHash = " +
|
|
remoteClass.interfaceHash() + "L;");
|
|
}
|
|
|
|
/**
|
|
* Writes declaration for java.lang.reflect.Method static fields
|
|
* corresponding to each remote method in a stub.
|
|
**/
|
|
private void writeMethodFieldDeclarations(IndentingWriter p)
|
|
throws IOException
|
|
{
|
|
for (String name : methodFieldNames) {
|
|
p.pln("private static java.lang.reflect.Method " + name + ";");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes code to initialize the static fields for each method
|
|
* using the Java Reflection API.
|
|
**/
|
|
private void writeMethodFieldInitializers(IndentingWriter p)
|
|
throws IOException
|
|
{
|
|
for (int i = 0; i < methodFieldNames.length; i++) {
|
|
p.p(methodFieldNames[i] + " = ");
|
|
/*
|
|
* Look up the Method object in the somewhat arbitrary
|
|
* interface that we find in the Method object.
|
|
*/
|
|
RemoteClass.Method method = remoteMethods[i];
|
|
MethodDoc methodDoc = method.methodDoc();
|
|
String methodName = methodDoc.name();
|
|
Type paramTypes[] = method.parameterTypes();
|
|
|
|
p.p(methodDoc.containingClass().qualifiedName() + ".class.getMethod(\"" +
|
|
methodName + "\", new java.lang.Class[] {");
|
|
for (int j = 0; j < paramTypes.length; j++) {
|
|
if (j > 0)
|
|
p.p(", ");
|
|
p.p(paramTypes[j].toString() + ".class");
|
|
}
|
|
p.pln("});");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Following are a series of static utility methods useful during
|
|
* the code generation process:
|
|
*/
|
|
|
|
/**
|
|
* Generates an array of names for fields correspondins to the
|
|
* given array of remote methods. Each name in the returned array
|
|
* is guaranteed to be unique.
|
|
*
|
|
* The name of a method is included in its corresponding field
|
|
* name to enhance readability of the generated code.
|
|
**/
|
|
private static String[] nameMethodFields(RemoteClass.Method[] methods) {
|
|
String[] names = new String[methods.length];
|
|
for (int i = 0; i < names.length; i++) {
|
|
names[i] = "$method_" + methods[i].methodDoc().name() + "_" + i;
|
|
}
|
|
return names;
|
|
}
|
|
|
|
/**
|
|
* Generates an array of names for parameters corresponding to the
|
|
* given array of types for the parameters. Each name in the
|
|
* returned array is guaranteed to be unique.
|
|
*
|
|
* A representation of the type of a parameter is included in its
|
|
* corresponding parameter name to enhance the readability of the
|
|
* generated code.
|
|
**/
|
|
private static String[] nameParameters(Type[] types) {
|
|
String[] names = new String[types.length];
|
|
for (int i = 0; i < names.length; i++) {
|
|
names[i] = "$param_" +
|
|
generateNameFromType(types[i]) + "_" + (i + 1);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
/**
|
|
* Generates a readable string representing the given type
|
|
* suitable for embedding within a Java identifier.
|
|
**/
|
|
private static String generateNameFromType(Type type) {
|
|
String name = type.typeName().replace('.', '$');
|
|
int dimensions = type.dimension().length() / 2;
|
|
for (int i = 0; i < dimensions; i++) {
|
|
name = "arrayOf_" + name;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Writes a snippet of Java code to marshal a value named "name"
|
|
* of type "type" to the java.io.ObjectOutput stream named
|
|
* "stream".
|
|
*
|
|
* Primitive types are marshalled with their corresponding methods
|
|
* in the java.io.DataOutput interface, and objects (including
|
|
* arrays) are marshalled using the writeObject method.
|
|
**/
|
|
private static void writeMarshalArgument(IndentingWriter p,
|
|
String streamName,
|
|
Type type, String name)
|
|
throws IOException
|
|
{
|
|
if (type.dimension().length() > 0 || type.asClassDoc() != null) {
|
|
p.p(streamName + ".writeObject(" + name + ")");
|
|
} else if (type.typeName().equals("boolean")) {
|
|
p.p(streamName + ".writeBoolean(" + name + ")");
|
|
} else if (type.typeName().equals("byte")) {
|
|
p.p(streamName + ".writeByte(" + name + ")");
|
|
} else if (type.typeName().equals("char")) {
|
|
p.p(streamName + ".writeChar(" + name + ")");
|
|
} else if (type.typeName().equals("short")) {
|
|
p.p(streamName + ".writeShort(" + name + ")");
|
|
} else if (type.typeName().equals("int")) {
|
|
p.p(streamName + ".writeInt(" + name + ")");
|
|
} else if (type.typeName().equals("long")) {
|
|
p.p(streamName + ".writeLong(" + name + ")");
|
|
} else if (type.typeName().equals("float")) {
|
|
p.p(streamName + ".writeFloat(" + name + ")");
|
|
} else if (type.typeName().equals("double")) {
|
|
p.p(streamName + ".writeDouble(" + name + ")");
|
|
} else {
|
|
throw new AssertionError(type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes Java statements to marshal a series of values in order
|
|
* as named in the "names" array, with types as specified in the
|
|
* "types" array, to the java.io.ObjectOutput stream named
|
|
* "stream".
|
|
**/
|
|
private static void writeMarshalArguments(IndentingWriter p,
|
|
String streamName,
|
|
Type[] types, String[] names)
|
|
throws IOException
|
|
{
|
|
assert types.length == names.length;
|
|
|
|
for (int i = 0; i < types.length; i++) {
|
|
writeMarshalArgument(p, streamName, types[i], names[i]);
|
|
p.pln(";");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a snippet of Java code to unmarshal a value of type
|
|
* "type" from the java.io.ObjectInput stream named "stream" into
|
|
* a variable named "name" (if "name" is null, the value is
|
|
* unmarshalled and discarded).
|
|
*
|
|
* Primitive types are unmarshalled with their corresponding
|
|
* methods in the java.io.DataInput interface, and objects
|
|
* (including arrays) are unmarshalled using the readObject
|
|
* method.
|
|
*
|
|
* Returns true if code to invoke readObject was written, and
|
|
* false otherwise.
|
|
**/
|
|
private static boolean writeUnmarshalArgument(IndentingWriter p,
|
|
String streamName,
|
|
Type type, String name)
|
|
throws IOException
|
|
{
|
|
boolean readObject = false;
|
|
|
|
if (name != null) {
|
|
p.p(name + " = ");
|
|
}
|
|
|
|
if (type.dimension().length() > 0 || type.asClassDoc() != null) {
|
|
p.p("(" + type.toString() + ") " + streamName + ".readObject()");
|
|
readObject = true;
|
|
} else if (type.typeName().equals("boolean")) {
|
|
p.p(streamName + ".readBoolean()");
|
|
} else if (type.typeName().equals("byte")) {
|
|
p.p(streamName + ".readByte()");
|
|
} else if (type.typeName().equals("char")) {
|
|
p.p(streamName + ".readChar()");
|
|
} else if (type.typeName().equals("short")) {
|
|
p.p(streamName + ".readShort()");
|
|
} else if (type.typeName().equals("int")) {
|
|
p.p(streamName + ".readInt()");
|
|
} else if (type.typeName().equals("long")) {
|
|
p.p(streamName + ".readLong()");
|
|
} else if (type.typeName().equals("float")) {
|
|
p.p(streamName + ".readFloat()");
|
|
} else if (type.typeName().equals("double")) {
|
|
p.p(streamName + ".readDouble()");
|
|
} else {
|
|
throw new AssertionError(type);
|
|
}
|
|
|
|
return readObject;
|
|
}
|
|
|
|
/**
|
|
* Writes Java statements to unmarshal a series of values in order
|
|
* of types as in the "types" array from the java.io.ObjectInput
|
|
* stream named "stream" into variables as named in "names" (for
|
|
* any element of "names" that is null, the corresponding value is
|
|
* unmarshalled and discarded).
|
|
**/
|
|
private static boolean writeUnmarshalArguments(IndentingWriter p,
|
|
String streamName,
|
|
Type[] types,
|
|
String[] names)
|
|
throws IOException
|
|
{
|
|
assert types.length == names.length;
|
|
|
|
boolean readObject = false;
|
|
for (int i = 0; i < types.length; i++) {
|
|
if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
|
|
readObject = true;
|
|
}
|
|
p.pln(";");
|
|
}
|
|
return readObject;
|
|
}
|
|
|
|
/**
|
|
* Returns a snippet of Java code to wrap a value named "name" of
|
|
* type "type" into an object as appropriate for use by the Java
|
|
* Reflection API.
|
|
*
|
|
* For primitive types, an appropriate wrapper class is
|
|
* instantiated with the primitive value. For object types
|
|
* (including arrays), no wrapping is necessary, so the value is
|
|
* named directly.
|
|
**/
|
|
private static String wrapArgumentCode(Type type, String name) {
|
|
if (type.dimension().length() > 0 || type.asClassDoc() != null) {
|
|
return name;
|
|
} else if (type.typeName().equals("boolean")) {
|
|
return ("(" + name +
|
|
" ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
|
|
} else if (type.typeName().equals("byte")) {
|
|
return "new java.lang.Byte(" + name + ")";
|
|
} else if (type.typeName().equals("char")) {
|
|
return "new java.lang.Character(" + name + ")";
|
|
} else if (type.typeName().equals("short")) {
|
|
return "new java.lang.Short(" + name + ")";
|
|
} else if (type.typeName().equals("int")) {
|
|
return "new java.lang.Integer(" + name + ")";
|
|
} else if (type.typeName().equals("long")) {
|
|
return "new java.lang.Long(" + name + ")";
|
|
} else if (type.typeName().equals("float")) {
|
|
return "new java.lang.Float(" + name + ")";
|
|
} else if (type.typeName().equals("double")) {
|
|
return "new java.lang.Double(" + name + ")";
|
|
} else {
|
|
throw new AssertionError(type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a snippet of Java code to unwrap a value named "name"
|
|
* into a value of type "type", as appropriate for the Java
|
|
* Reflection API.
|
|
*
|
|
* For primitive types, the value is assumed to be of the
|
|
* corresponding wrapper class, and a method is called on the
|
|
* wrapper to retrieve the primitive value. For object types
|
|
* (include arrays), no unwrapping is necessary; the value is
|
|
* simply cast to the expected real object type.
|
|
**/
|
|
private static String unwrapArgumentCode(Type type, String name) {
|
|
if (type.dimension().length() > 0 || type.asClassDoc() != null) {
|
|
return "((" + type.toString() + ") " + name + ")";
|
|
} else if (type.typeName().equals("boolean")) {
|
|
return "((java.lang.Boolean) " + name + ").booleanValue()";
|
|
} else if (type.typeName().equals("byte")) {
|
|
return "((java.lang.Byte) " + name + ").byteValue()";
|
|
} else if (type.typeName().equals("char")) {
|
|
return "((java.lang.Character) " + name + ").charValue()";
|
|
} else if (type.typeName().equals("short")) {
|
|
return "((java.lang.Short) " + name + ").shortValue()";
|
|
} else if (type.typeName().equals("int")) {
|
|
return "((java.lang.Integer) " + name + ").intValue()";
|
|
} else if (type.typeName().equals("long")) {
|
|
return "((java.lang.Long) " + name + ").longValue()";
|
|
} else if (type.typeName().equals("float")) {
|
|
return "((java.lang.Float) " + name + ").floatValue()";
|
|
} else if (type.typeName().equals("double")) {
|
|
return "((java.lang.Double) " + name + ").doubleValue()";
|
|
} else {
|
|
throw new AssertionError(type);
|
|
}
|
|
}
|
|
}
|