diff options
Diffstat (limited to 'src/share/classes/sun/rmi/rmic/RMIGenerator.java')
-rw-r--r-- | src/share/classes/sun/rmi/rmic/RMIGenerator.java | 1303 |
1 files changed, 1303 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/rmic/RMIGenerator.java b/src/share/classes/sun/rmi/rmic/RMIGenerator.java new file mode 100644 index 000000000..41e94ff36 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/RMIGenerator.java @@ -0,0 +1,1303 @@ +/* + * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/*****************************************************************************/ +/* 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 + */ +public class RMIGenerator implements RMIConstants, Generator { + + private static final Hashtable versionOptions = new Hashtable(); + static { + versionOptions.put("-v1.1", new Integer(STUB_VERSION_1_1)); + versionOptions.put("-vcompat", new Integer(STUB_VERSION_FAT)); + versionOptions.put("-v1.2", new Integer(STUB_VERSION_1_2)); + } + + /** + * 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. + * @param argv The command line arguments. Ignore null + * and unknown arguments. Set each consumed argument to null. + * @param error 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 = ((Integer) versionOptions.get(arg)).intValue(); + 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. + * + * @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(new ClassFile(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(new ClassFile(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. + */ + 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 */ + private BatchEnvironment env; + + /** the remote class that this instance is generating code for */ + private RemoteClass remoteClass; + + /** version of the stub protocol to use in code generation */ + private int version; + + /** 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. + */ + 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. + */ + private String[] methodFieldNames; + + /** 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. + */ + 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. + */ + 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. + */ + 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". + */ + 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 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 enumeration = catchList.elements(); + enumeration.hasMoreElements();) + { + ClassDefinition def = (ClassDefinition) 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. + */ + private Vector computeUniqueCatchList(ClassDeclaration[] exceptions) { + Vector 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 = + (ClassDefinition) 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. + */ + 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". + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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". + */ + private static void writeMarshalArguments(IndentingWriter p, + String streamName, + Type[] types, String[] names) + throws IOException + { + if (types.length != names.length) { + throw new Error("paramter 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. + */ + 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). + */ + private static boolean writeUnmarshalArguments(IndentingWriter p, + String streamName, + Type[] types, + String[] names) + throws IOException + { + if (types.length != names.length) { + throw new Error("paramter 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. + */ + 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. + */ + 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); + } + } +} |