/*
* 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.
*/
/*****************************************************************************/
/* Copyright (c) IBM Corporation 1998 */
/* */
/* (C) Copyright IBM Corp. 1998 */
/* */
/*****************************************************************************/
package sun.rmi.rmic;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import sun.tools.java.Type;
import sun.tools.java.Identifier;
import sun.tools.java.ClassDefinition;
import sun.tools.java.ClassDeclaration;
import sun.tools.java.ClassNotFound;
import sun.tools.java.ClassFile;
import sun.tools.java.MemberDefinition;
import com.sun.corba.se.impl.util.Utility;
A Generator object will generate the Java source code of the stub
and skeleton classes for an RMI remote implementation class, using
a particular stub protocol version.
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, Bryan Atsatt
/**
* A Generator object will generate the Java source code of the stub
* and skeleton classes for an RMI remote implementation class, using
* a particular stub protocol version.
*
* 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, Bryan Atsatt
*/
public class RMIGenerator implements RMIConstants, Generator {
private static final Hashtable<String, Integer> versionOptions = new Hashtable<>();
static {
versionOptions.put("-v1.1", STUB_VERSION_1_1);
versionOptions.put("-vcompat", STUB_VERSION_FAT);
versionOptions.put("-v1.2", STUB_VERSION_1_2);
}
Default constructor for Main to use.
/**
* Default constructor for Main to use.
*/
public RMIGenerator() {
version = STUB_VERSION_1_2; // default is -v1.2 (see 4638155)
}
Examine and consume command line arguments.
Params: - argv – The command line arguments. Ignore null
and unknown arguments. Set each consumed argument to null.
- main – Report any errors using the main.error() methods.
Returns: true if no errors, false otherwise.
/**
* Examine and consume command line arguments.
* @param argv The command line arguments. Ignore null
* and unknown arguments. Set each consumed argument to null.
* @param main Report any errors using the main.error() methods.
* @return true if no errors, false otherwise.
*/
public boolean parseArgs(String argv[], Main main) {
String explicitVersion = null;
for (int i = 0; i < argv.length; i++) {
if (argv[i] != null) {
String arg = argv[i].toLowerCase();
if (versionOptions.containsKey(arg)) {
if (explicitVersion != null &&
!explicitVersion.equals(arg))
{
main.error("rmic.cannot.use.both",
explicitVersion, arg);
return false;
}
explicitVersion = arg;
version = versionOptions.get(arg);
argv[i] = null;
}
}
}
return true;
}
Generate the source files for the stub and/or skeleton classes
needed by RMI for the given remote implementation class.
Params: - env – compiler environment
- cdef – definition of remote implementation class
to generate stubs and/or skeletons for
- destDir – directory for the root of the package hierarchy
for generated files
/**
* Generate the source files for the stub and/or skeleton classes
* needed by RMI for the given remote implementation class.
*
* @param env compiler environment
* @param cdef definition of remote implementation class
* to generate stubs and/or skeletons for
* @param destDir directory for the root of the package hierarchy
* for generated files
*/
public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir) {
RemoteClass remoteClass = RemoteClass.forClass(env, cdef);
if (remoteClass == null) // exit if an error occurred
return;
RMIGenerator gen;
try {
gen = new RMIGenerator(env, cdef, destDir, remoteClass, version);
} catch (ClassNotFound e) {
env.error(0, "rmic.class.not.found", e.name);
return;
}
gen.generate();
}
private void generate() {
env.addGeneratedFile(stubFile);
try {
IndentingWriter out = new IndentingWriter(
new OutputStreamWriter(new FileOutputStream(stubFile)));
writeStub(out);
out.close();
if (env.verbose()) {
env.output(Main.getText("rmic.wrote", stubFile.getPath()));
}
env.parseFile(ClassFile.newClassFile(stubFile));
} catch (IOException e) {
env.error(0, "cant.write", stubFile.toString());
return;
}
if (version == STUB_VERSION_1_1 ||
version == STUB_VERSION_FAT)
{
env.addGeneratedFile(skeletonFile);
try {
IndentingWriter out = new IndentingWriter(
new OutputStreamWriter(
new FileOutputStream(skeletonFile)));
writeSkeleton(out);
out.close();
if (env.verbose()) {
env.output(Main.getText("rmic.wrote",
skeletonFile.getPath()));
}
env.parseFile(ClassFile.newClassFile(skeletonFile));
} catch (IOException e) {
env.error(0, "cant.write", stubFile.toString());
return;
}
} else {
/*
* For bugid 4135136: if skeleton files are not being generated
* for this compilation run, delete old skeleton source or class
* files for this remote implementation class that were
* (presumably) left over from previous runs, to avoid user
* confusion from extraneous or inconsistent generated files.
*/
File outputDir = Util.getOutputDirectoryFor(remoteClassName,destDir,env);
File skeletonClassFile = new File(outputDir,skeletonClassName.getName().toString() + ".class");
skeletonFile.delete(); // ignore failures (no big deal)
skeletonClassFile.delete();
}
}
Return the File object that should be used as the source file
for the given Java class, using the supplied destination
directory for the top of the package hierarchy.
/**
* Return the File object that should be used as the source file
* for the given Java class, using the supplied destination
* directory for the top of the package hierarchy.
*/
protected static File sourceFileForClass(Identifier className,
Identifier outputClassName,
File destDir,
BatchEnvironment env)
{
File packageDir = Util.getOutputDirectoryFor(className,destDir,env);
String outputName = Names.mangleClass(outputClassName).getName().toString();
// Is there any existing _Tie equivalent leftover from a
// previous invocation of rmic -iiop? Only do this once per
// class by looking for skeleton generation...
if (outputName.endsWith("_Skel")) {
String classNameStr = className.getName().toString();
File temp = new File(packageDir, Utility.tieName(classNameStr) + ".class");
if (temp.exists()) {
// Found a tie. Is IIOP generation also being done?
if (!env.getMain().iiopGeneration) {
// No, so write a warning...
env.error(0,"warn.rmic.tie.found",
classNameStr,
temp.getAbsolutePath());
}
}
}
String outputFileName = outputName + ".java";
return new File(packageDir, outputFileName);
}
rmic environment for this object /** rmic environment for this object */
private BatchEnvironment env;
the remote class that this instance is generating code for /** the remote class that this instance is generating code for */
private RemoteClass remoteClass;
version of the stub protocol to use in code generation /** version of the stub protocol to use in code generation */
private int version;
remote methods for remote class, indexed by operation number /** remote methods for remote class, indexed by operation number */
private RemoteClass.Method[] remoteMethods;
Names for the remote class and the stub and skeleton classes
to be generated for it.
/**
* Names for the remote class and the stub and skeleton classes
* to be generated for it.
*/
private Identifier remoteClassName;
private Identifier stubClassName;
private Identifier skeletonClassName;
private ClassDefinition cdef;
private File destDir;
private File stubFile;
private File skeletonFile;
Names to use for the java.lang.reflect.Method static fields
corresponding to each remote method.
/**
* Names to use for the java.lang.reflect.Method static fields
* corresponding to each remote method.
*/
private String[] methodFieldNames;
cached definition for certain exception classes in this environment /** cached definition for certain exception classes in this environment */
private ClassDefinition defException;
private ClassDefinition defRemoteException;
private ClassDefinition defRuntimeException;
Create a new stub/skeleton Generator object for the given
remote implementation class to generate code according to
the given stub protocol version.
/**
* Create a new stub/skeleton Generator object for the given
* remote implementation class to generate code according to
* the given stub protocol version.
*/
private RMIGenerator(BatchEnvironment env, ClassDefinition cdef,
File destDir, RemoteClass remoteClass, int version)
throws ClassNotFound
{
this.destDir = destDir;
this.cdef = cdef;
this.env = env;
this.remoteClass = remoteClass;
this.version = version;
remoteMethods = remoteClass.getRemoteMethods();
remoteClassName = remoteClass.getName();
stubClassName = Names.stubFor(remoteClassName);
skeletonClassName = Names.skeletonFor(remoteClassName);
methodFieldNames = nameMethodFields(remoteMethods);
stubFile = sourceFileForClass(remoteClassName,stubClassName, destDir , env);
skeletonFile = sourceFileForClass(remoteClassName,skeletonClassName, destDir, env);
/*
* Initialize cached definitions for exception classes used
* in the generation process.
*/
defException =
env.getClassDeclaration(idJavaLangException).
getClassDefinition(env);
defRemoteException =
env.getClassDeclaration(idRemoteException).
getClassDefinition(env);
defRuntimeException =
env.getClassDeclaration(idJavaLangRuntimeException).
getClassDefinition(env);
}
Write the stub for the remote class to a stream.
/**
* Write the stub for the remote class to a stream.
*/
private 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 (remoteClassName.isQualified()) {
p.pln("package " + remoteClassName.getQualifier() + ";");
p.pln();
}
/*
* Declare the stub class; implement all remote interfaces.
*/
p.plnI("public final class " +
Names.mangleClass(stubClassName.getName()));
p.pln("extends " + idRemoteStub);
ClassDefinition[] remoteInterfaces = remoteClass.getRemoteInterfaces();
if (remoteInterfaces.length > 0) {
p.p("implements ");
for (int i = 0; i < remoteInterfaces.length; i++) {
if (i > 0)
p.p(", ");
p.p(remoteInterfaces[i].getName().toString());
}
p.pln();
}
p.pOlnI("{");
if (version == STUB_VERSION_1_1 ||
version == STUB_VERSION_FAT)
{
writeOperationsArray(p);
p.pln();
writeInterfaceHash(p);
p.pln();
}
if (version == STUB_VERSION_FAT ||
version == STUB_VERSION_1_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 == STUB_VERSION_FAT) {
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 == STUB_VERSION_FAT) {
/*
* 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(idRemoteRef + ".class.getMethod(\"invoke\",");
p.plnI("new java.lang.Class[] {");
p.pln(idRemote + ".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 == STUB_VERSION_FAT) {
p.pln("useNewInvoke = false;");
} else {
/*
* REMIND: By throwing an Error here, the application will
* get the NoSuchMethodError directly when the stub class
* is initialized. If we throw a RuntimeException
* instead, the application would get an
* ExceptionInInitializerError. Would that be more
* appropriate, and if so, which RuntimeException should
* be thrown?
*/
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
}
Write the constructors for the stub class.
/**
* Write 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 == STUB_VERSION_1_1 ||
version == STUB_VERSION_FAT)
{
p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
"() {");
p.pln("super();");
p.pOln("}");
}
p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
"(" + idRemoteRef + " ref) {");
p.pln("super(ref);");
p.pOln("}");
}
Write the stub method for the remote method with the given "opnum".
/**
* Write the stub method for the remote method with the given "opnum".
*/
private void writeStubMethod(IndentingWriter p, int opnum)
throws IOException
{
RemoteClass.Method method = remoteMethods[opnum];
Identifier methodName = method.getName();
Type methodType = method.getType();
Type paramTypes[] = methodType.getArgumentTypes();
String paramNames[] = nameParameters(paramTypes);
Type returnType = methodType.getReturnType();
ClassDeclaration[] exceptions = method.getExceptions();
/*
* Declare stub method; throw exceptions declared in remote
* interface(s).
*/
p.pln("// implementation of " +
methodType.typeString(methodName.toString(), true, false));
p.p("public " + returnType + " " + methodName + "(");
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0)
p.p(", ");
p.p(paramTypes[i] + " " + 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].getName().toString());
}
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 unharmed, so first we must catch any such
* exceptions and rethrow it 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.
*/
Vector<ClassDefinition> 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 == STUB_VERSION_FAT) {
p.plnI("if (useNewInvoke) {");
}
if (version == STUB_VERSION_FAT ||
version == STUB_VERSION_1_2)
{
if (!returnType.isType(TC_VOID)) {
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.getMethodHash() + "L);");
if (!returnType.isType(TC_VOID)) {
p.pln("return " +
unwrapArgumentCode(returnType, "$result") + ";");
}
}
if (version == STUB_VERSION_FAT) {
p.pOlnI("} else {");
}
if (version == STUB_VERSION_1_1 ||
version == STUB_VERSION_FAT)
{
p.pln(idRemoteCall + " call = ref.newCall((" + idRemoteObject +
") 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 " + idMarshalException +
"(\"error marshalling arguments\", e);");
p.pOln("}");
}
p.pln("ref.invoke(call);");
if (returnType.isType(TC_VOID)) {
p.pln("ref.done(call);");
} else {
p.pln(returnType + " $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 " + idUnmarshalException +
"(\"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 " + idUnmarshalException +
"(\"error unmarshalling return\", e);");
}
p.pOlnI("} finally {");
p.pln("ref.done(call);");
p.pOln("}");
p.pln("return $result;");
}
}
if (version == STUB_VERSION_FAT) {
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 (Enumeration<ClassDefinition> enumeration = catchList.elements();
enumeration.hasMoreElements();)
{
ClassDefinition def = enumeration.nextElement();
p.pOlnI("} catch (" + def.getName() + " e) {");
p.pln("throw e;");
}
p.pOlnI("} catch (java.lang.Exception e) {");
p.pln("throw new " + idUnexpectedException +
"(\"undeclared checked exception\", e);");
p.pOln("}"); // end try/catch block
}
p.pOln("}"); // end stub method
}
Compute the exceptions which 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 Vector containing ClassDefinition objects for each
exception to catch. Each exception is guaranteed to be unique,
i.e. not a subclass of any of the other exceptions in the Vector,
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 Vector (if none of their superclasses are already
present), since those exceptions should always be directly rethrown
by a stub method.
The returned Vector 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.
/**
* Compute the exceptions which 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 Vector containing ClassDefinition objects for each
* exception to catch. Each exception is guaranteed to be unique,
* i.e. not a subclass of any of the other exceptions in the Vector,
* 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 Vector (if none of their superclasses are already
* present), since those exceptions should always be directly rethrown
* by a stub method.
*
* The returned Vector 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 Vector<ClassDefinition> computeUniqueCatchList(ClassDeclaration[] exceptions) {
Vector<ClassDefinition> uniqueList = new Vector<>(); // unique exceptions to catch
uniqueList.addElement(defRuntimeException);
uniqueList.addElement(defRemoteException);
/* For each exception declared by the stub method's throws clause: */
nextException:
for (int i = 0; i < exceptions.length; i++) {
ClassDeclaration decl = exceptions[i];
try {
if (defException.subClassOf(env, decl)) {
/*
* (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 (!defException.superClassOf(env, decl)) {
/*
* Ignore other Throwables that do not extend Exception,
* since they do not need to be caught anyway.
*/
continue;
}
/*
* Compare this exception against the current list of
* exceptions that need to be caught:
*/
for (int j = 0; j < uniqueList.size();) {
ClassDefinition def = uniqueList.elementAt(j);
if (def.superClassOf(env, decl)) {
/*
* If a superclass of this exception is already on
* the list to catch, then ignore and continue;
*/
continue nextException;
} else if (def.subClassOf(env, decl)) {
/*
* If a subclass of this exception is on the list
* to catch, then remove it.
*/
uniqueList.removeElementAt(j);
} else {
j++; // else continue comparing
}
}
/* This exception is unique: add it to the list to catch. */
uniqueList.addElement(decl.getClassDefinition(env));
} catch (ClassNotFound e) {
env.error(0, "class.not.found", e.name, decl.getName());
/*
* REMIND: We do not exit from this exceptional condition,
* generating questionable code and likely letting the
* compiler report a resulting error later.
*/
}
}
return uniqueList;
}
Write the skeleton for the remote class to a stream.
/**
* Write the skeleton for the remote class to a stream.
*/
private void writeSkeleton(IndentingWriter p) throws IOException {
if (version == STUB_VERSION_1_2) {
throw new Error("should not generate skeleton for 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 (remoteClassName.isQualified()) {
p.pln("package " + remoteClassName.getQualifier() + ";");
p.pln();
}
/*
* Declare the skeleton class.
*/
p.plnI("public final class " +
Names.mangleClass(skeletonClassName.getName()));
p.pln("implements " + idSkeleton);
p.pOlnI("{");
writeOperationsArray(p);
p.pln();
writeInterfaceHash(p);
p.pln();
/*
* Define the getOperations() method.
*/
p.plnI("public " + idOperation + "[] getOperations() {");
p.pln("return (" + idOperation + "[]) operations.clone();");
p.pOln("}");
p.pln();
/*
* Define the dispatch() method.
*/
p.plnI("public void dispatch(" + idRemote + " obj, " +
idRemoteCall + " call, int opnum, long hash)");
p.pln("throws java.lang.Exception");
p.pOlnI("{");
if (version == STUB_VERSION_FAT) {
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].getMethodHash() + "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 " +
idUnmarshalException + "(\"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 " +
idSkeletonMismatchException + "(\"interface hash mismatch\");");
p.pO();
if (version == STUB_VERSION_FAT) {
p.pOln("}"); // end if/else (opnum < 0) block
}
p.pln();
/*
* Cast remote object instance to our specific implementation class.
*/
p.pln(remoteClassName + " server = (" + remoteClassName + ") 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 " + idUnmarshalException +
"(\"invalid method number\");");
p.pOln("}"); // end switch statement
p.pOln("}"); // end dispatch() method
p.pOln("}"); // end skeleton class
}
Write the case block for the skeleton's dispatch method for
the remote method with the given "opnum".
/**
* Write 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];
Identifier methodName = method.getName();
Type methodType = method.getType();
Type paramTypes[] = methodType.getArgumentTypes();
String paramNames[] = nameParameters(paramTypes);
Type returnType = methodType.getReturnType();
p.pOlnI("case " + opnum + ": // " +
methodType.typeString(methodName.toString(), true, false));
/*
* 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] + " " + 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 " + idUnmarshalException +
"(\"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 " + idUnmarshalException +
"(\"error unmarshalling arguments\", e);");
}
p.pOlnI("} finally {");
p.pln("call.releaseInputStream();");
p.pOln("}");
} else {
p.pln("call.releaseInputStream();");
}
if (!returnType.isType(TC_VOID)) {
/*
* Declare variable to hold return type, if not void.
*/
p.p(returnType + " $result = "); // REMIND: why $?
}
/*
* Invoke the method on the server object.
*/
p.p("server." + 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 (!returnType.isType(TC_VOID)) {
p.p("java.io.ObjectOutput out = ");
}
p.pln("call.getResultStream(true);");
if (!returnType.isType(TC_VOID)) {
writeMarshalArgument(p, "out", returnType, "$result");
p.pln(";");
}
p.pOlnI("} catch (java.io.IOException e) {");
p.pln("throw new " +
idMarshalException + "(\"error marshalling return\", e);");
p.pOln("}");
p.pln("break;"); // break from switch statement
p.pOlnI("}"); // end nested block statement
p.pln();
}
Write declaration and initializer for "operations" static array.
/**
* Write declaration and initializer for "operations" static array.
*/
private void writeOperationsArray(IndentingWriter p)
throws IOException
{
p.plnI("private static final " + idOperation + "[] operations = {");
for (int i = 0; i < remoteMethods.length; i++) {
if (i > 0)
p.pln(",");
p.p("new " + idOperation + "(\"" +
remoteMethods[i].getOperationString() + "\")");
}
p.pln();
p.pOln("};");
}
Write declaration and initializer for "interfaceHash" static field.
/**
* Write declaration and initializer for "interfaceHash" static field.
*/
private void writeInterfaceHash(IndentingWriter p)
throws IOException
{
p.pln("private static final long interfaceHash = " +
remoteClass.getInterfaceHash() + "L;");
}
Write declaration for java.lang.reflect.Method static fields
corresponding to each remote method in a stub.
/**
* Write declaration for java.lang.reflect.Method static fields
* corresponding to each remote method in a stub.
*/
private void writeMethodFieldDeclarations(IndentingWriter p)
throws IOException
{
for (int i = 0; i < methodFieldNames.length; i++) {
p.pln("private static java.lang.reflect.Method " +
methodFieldNames[i] + ";");
}
}
Write code to initialize the static fields for each method
using the Java Reflection API.
/**
* Write 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] + " = ");
/*
* Here we look up the Method object in the arbitrary interface
* that we find in the RemoteClass.Method object.
* REMIND: Is this arbitrary choice OK?
* REMIND: Should this access be part of RemoteClass.Method's
* abstraction?
*/
RemoteClass.Method method = remoteMethods[i];
MemberDefinition def = method.getMemberDefinition();
Identifier methodName = method.getName();
Type methodType = method.getType();
Type paramTypes[] = methodType.getArgumentTypes();
p.p(def.getClassDefinition().getName() + ".class.getMethod(\"" +
methodName + "\", new java.lang.Class[] {");
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0)
p.p(", ");
p.p(paramTypes[j] + ".class");
}
p.pln("});");
}
}
/*
* Following are a series of static utility methods useful during
* the code generation process:
*/
Generate an array of names for fields that correspond 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.
/**
* Generate an array of names for fields that correspond 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].getName() + "_" + i;
}
return names;
}
Generate 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 field name to enhance the readability of the generated
code.
/**
* Generate 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 field 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;
}
Generate a readable string representing the given type suitable
for embedding within a Java identifier.
/**
* Generate a readable string representing the given type suitable
* for embedding within a Java identifier.
*/
private static String generateNameFromType(Type type) {
int typeCode = type.getTypeCode();
switch (typeCode) {
case TC_BOOLEAN:
case TC_BYTE:
case TC_CHAR:
case TC_SHORT:
case TC_INT:
case TC_LONG:
case TC_FLOAT:
case TC_DOUBLE:
return type.toString();
case TC_ARRAY:
return "arrayOf_" + generateNameFromType(type.getElementType());
case TC_CLASS:
return Names.mangleClass(type.getClassName().getName()).toString();
default:
throw new Error("unexpected type code: " + typeCode);
}
}
Write 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.
/**
* Write 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
{
int typeCode = type.getTypeCode();
switch (typeCode) {
case TC_BOOLEAN:
p.p(streamName + ".writeBoolean(" + name + ")");
break;
case TC_BYTE:
p.p(streamName + ".writeByte(" + name + ")");
break;
case TC_CHAR:
p.p(streamName + ".writeChar(" + name + ")");
break;
case TC_SHORT:
p.p(streamName + ".writeShort(" + name + ")");
break;
case TC_INT:
p.p(streamName + ".writeInt(" + name + ")");
break;
case TC_LONG:
p.p(streamName + ".writeLong(" + name + ")");
break;
case TC_FLOAT:
p.p(streamName + ".writeFloat(" + name + ")");
break;
case TC_DOUBLE:
p.p(streamName + ".writeDouble(" + name + ")");
break;
case TC_ARRAY:
case TC_CLASS:
p.p(streamName + ".writeObject(" + name + ")");
break;
default:
throw new Error("unexpected type code: " + typeCode);
}
}
Write 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".
/**
* Write 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
{
if (types.length != names.length) {
throw new Error("parameter type and name arrays different sizes");
}
for (int i = 0; i < types.length; i++) {
writeMarshalArgument(p, streamName, types[i], names[i]);
p.pln(";");
}
}
Write 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 in 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.
/**
* Write 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 in 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.
*/
private static boolean writeUnmarshalArgument(IndentingWriter p,
String streamName,
Type type, String name)
throws IOException
{
boolean readObject = false;
if (name != null) {
p.p(name + " = ");
}
int typeCode = type.getTypeCode();
switch (type.getTypeCode()) {
case TC_BOOLEAN:
p.p(streamName + ".readBoolean()");
break;
case TC_BYTE:
p.p(streamName + ".readByte()");
break;
case TC_CHAR:
p.p(streamName + ".readChar()");
break;
case TC_SHORT:
p.p(streamName + ".readShort()");
break;
case TC_INT:
p.p(streamName + ".readInt()");
break;
case TC_LONG:
p.p(streamName + ".readLong()");
break;
case TC_FLOAT:
p.p(streamName + ".readFloat()");
break;
case TC_DOUBLE:
p.p(streamName + ".readDouble()");
break;
case TC_ARRAY:
case TC_CLASS:
p.p("(" + type + ") " + streamName + ".readObject()");
readObject = true;
break;
default:
throw new Error("unexpected type code: " + typeCode);
}
return readObject;
}
Write 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).
/**
* Write 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
{
if (types.length != names.length) {
throw new Error("parameter type and name arrays different sizes");
}
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;
}
Return 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 instantiated
with the primitive value. For object types (including arrays),
no wrapping is necessary, so the value is named directly.
/**
* Return 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 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) {
int typeCode = type.getTypeCode();
switch (typeCode) {
case TC_BOOLEAN:
return ("(" + name +
" ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
case TC_BYTE:
return "new java.lang.Byte(" + name + ")";
case TC_CHAR:
return "new java.lang.Character(" + name + ")";
case TC_SHORT:
return "new java.lang.Short(" + name + ")";
case TC_INT:
return "new java.lang.Integer(" + name + ")";
case TC_LONG:
return "new java.lang.Long(" + name + ")";
case TC_FLOAT:
return "new java.lang.Float(" + name + ")";
case TC_DOUBLE:
return "new java.lang.Double(" + name + ")";
case TC_ARRAY:
case TC_CLASS:
return name;
default:
throw new Error("unexpected type code: " + typeCode);
}
}
Return 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 type, and a method is called on the wrapper type 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.
/**
* Return 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 type, and a method is called on the wrapper type 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) {
int typeCode = type.getTypeCode();
switch (typeCode) {
case TC_BOOLEAN:
return "((java.lang.Boolean) " + name + ").booleanValue()";
case TC_BYTE:
return "((java.lang.Byte) " + name + ").byteValue()";
case TC_CHAR:
return "((java.lang.Character) " + name + ").charValue()";
case TC_SHORT:
return "((java.lang.Short) " + name + ").shortValue()";
case TC_INT:
return "((java.lang.Integer) " + name + ").intValue()";
case TC_LONG:
return "((java.lang.Long) " + name + ").longValue()";
case TC_FLOAT:
return "((java.lang.Float) " + name + ").floatValue()";
case TC_DOUBLE:
return "((java.lang.Double) " + name + ").doubleValue()";
case TC_ARRAY:
case TC_CLASS:
return "((" + type + ") " + name + ")";
default:
throw new Error("unexpected type code: " + typeCode);
}
}
}