| /* |
| * 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 |
| */ |
| 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. |
| */ |
| 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 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. |
| * |
| * @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. |
| */ |
| 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<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. |
| */ |
| 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. |
| */ |
| 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("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. |
| */ |
| 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("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. |
| */ |
| 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); |
| } |
| } |
| } |