1 /*
   2  * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*****************************************************************************/
  27 /*                    Copyright (c) IBM Corporation 1998                     */
  28 /*                                                                           */
  29 /* (C) Copyright IBM Corp. 1998                                              */
  30 /*                                                                           */
  31 /*****************************************************************************/
  32 
  33 package sun.rmi.rmic;
  34 
  35 import java.io.File;
  36 import java.io.FileOutputStream;
  37 import java.io.OutputStreamWriter;
  38 import java.io.IOException;
  39 import java.util.Enumeration;
  40 import java.util.Hashtable;
  41 import java.util.Vector;
  42 import sun.tools.java.Type;
  43 import sun.tools.java.Identifier;
  44 import sun.tools.java.ClassDefinition;
  45 import sun.tools.java.ClassDeclaration;
  46 import sun.tools.java.ClassNotFound;
  47 import sun.tools.java.ClassFile;
  48 import sun.tools.java.MemberDefinition;
  49 import com.sun.corba.se.impl.util.Utility;
  50 
  51 /**
  52  * A Generator object will generate the Java source code of the stub
  53  * and skeleton classes for an RMI remote implementation class, using
  54  * a particular stub protocol version.
  55  *
  56  * WARNING: The contents of this source file are not part of any
  57  * supported API.  Code that depends on them does so at its own risk:
  58  * they are subject to change or removal without notice.
  59  *
  60  * @author      Peter Jones,  Bryan Atsatt
  61  */
  62 public class RMIGenerator implements RMIConstants, Generator {
  63 
  64     private static final Hashtable versionOptions = new Hashtable();
  65     static {
  66         versionOptions.put("-v1.1", new Integer(STUB_VERSION_1_1));
  67         versionOptions.put("-vcompat", new Integer(STUB_VERSION_FAT));
  68         versionOptions.put("-v1.2", new Integer(STUB_VERSION_1_2));
  69     }
  70 
  71     /**
  72      * Default constructor for Main to use.
  73      */
  74     public RMIGenerator() {
  75         version = STUB_VERSION_1_2;     // default is -v1.2 (see 4638155)
  76     }
  77 
  78     /**
  79      * Examine and consume command line arguments.
  80      * @param argv The command line arguments. Ignore null
  81      * and unknown arguments. Set each consumed argument to null.
  82      * @param error Report any errors using the main.error() methods.
  83      * @return true if no errors, false otherwise.
  84      */
  85     public boolean parseArgs(String argv[], Main main) {
  86         String explicitVersion = null;
  87         for (int i = 0; i < argv.length; i++) {
  88             if (argv[i] != null) {
  89                 String arg = argv[i].toLowerCase();
  90                 if (versionOptions.containsKey(arg)) {
  91                     if (explicitVersion != null &&
  92                         !explicitVersion.equals(arg))
  93                     {
  94                         main.error("rmic.cannot.use.both",
  95                                    explicitVersion, arg);
  96                         return false;
  97                     }
  98                     explicitVersion = arg;
  99                     version = ((Integer) versionOptions.get(arg)).intValue();
 100                     argv[i] = null;
 101                 }
 102             }
 103         }
 104         return true;
 105     }
 106 
 107     /**
 108      * Generate the source files for the stub and/or skeleton classes
 109      * needed by RMI for the given remote implementation class.
 110      *
 111      * @param env       compiler environment
 112      * @param cdef      definition of remote implementation class
 113      *                  to generate stubs and/or skeletons for
 114      * @param destDir   directory for the root of the package hierarchy
 115      *                  for generated files
 116      */
 117     public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir) {
 118         RemoteClass remoteClass = RemoteClass.forClass(env, cdef);
 119         if (remoteClass == null)        // exit if an error occurred
 120             return;
 121 
 122         RMIGenerator gen;
 123         try {
 124             gen = new RMIGenerator(env, cdef, destDir, remoteClass, version);
 125         } catch (ClassNotFound e) {
 126             env.error(0, "rmic.class.not.found", e.name);
 127             return;
 128         }
 129         gen.generate();
 130     }
 131 
 132     private void generate() {
 133         env.addGeneratedFile(stubFile);
 134 
 135         try {
 136             IndentingWriter out = new IndentingWriter(
 137                 new OutputStreamWriter(new FileOutputStream(stubFile)));
 138             writeStub(out);
 139             out.close();
 140             if (env.verbose()) {
 141                 env.output(Main.getText("rmic.wrote", stubFile.getPath()));
 142             }
 143             env.parseFile(new ClassFile(stubFile));
 144         } catch (IOException e) {
 145             env.error(0, "cant.write", stubFile.toString());
 146             return;
 147         }
 148 
 149         if (version == STUB_VERSION_1_1 ||
 150             version == STUB_VERSION_FAT)
 151         {
 152             env.addGeneratedFile(skeletonFile);
 153 
 154             try {
 155                 IndentingWriter out = new IndentingWriter(
 156                     new OutputStreamWriter(
 157                         new FileOutputStream(skeletonFile)));
 158                 writeSkeleton(out);
 159                 out.close();
 160                 if (env.verbose()) {
 161                     env.output(Main.getText("rmic.wrote",
 162                         skeletonFile.getPath()));
 163                 }
 164                 env.parseFile(new ClassFile(skeletonFile));
 165             } catch (IOException e) {
 166                 env.error(0, "cant.write", stubFile.toString());
 167                 return;
 168             }
 169         } else {
 170             /*
 171              * For bugid 4135136: if skeleton files are not being generated
 172              * for this compilation run, delete old skeleton source or class
 173              * files for this remote implementation class that were
 174              * (presumably) left over from previous runs, to avoid user
 175              * confusion from extraneous or inconsistent generated files.
 176              */
 177 
 178             File outputDir = Util.getOutputDirectoryFor(remoteClassName,destDir,env);
 179             File skeletonClassFile = new File(outputDir,skeletonClassName.getName().toString() + ".class");
 180 
 181             skeletonFile.delete();      // ignore failures (no big deal)
 182             skeletonClassFile.delete();
 183         }
 184     }
 185 
 186     /**
 187      * Return the File object that should be used as the source file
 188      * for the given Java class, using the supplied destination
 189      * directory for the top of the package hierarchy.
 190      */
 191     protected static File sourceFileForClass(Identifier className,
 192                                              Identifier outputClassName,
 193                                              File destDir,
 194                                              BatchEnvironment env)
 195     {
 196         File packageDir = Util.getOutputDirectoryFor(className,destDir,env);
 197         String outputName = Names.mangleClass(outputClassName).getName().toString();
 198 
 199         // Is there any existing _Tie equivalent leftover from a
 200         // previous invocation of rmic -iiop? Only do this once per
 201         // class by looking for skeleton generation...
 202 
 203         if (outputName.endsWith("_Skel")) {
 204             String classNameStr = className.getName().toString();
 205             File temp = new File(packageDir, Utility.tieName(classNameStr) + ".class");
 206             if (temp.exists()) {
 207 
 208                 // Found a tie. Is IIOP generation also being done?
 209 
 210                 if (!env.getMain().iiopGeneration) {
 211 
 212                     // No, so write a warning...
 213 
 214                     env.error(0,"warn.rmic.tie.found",
 215                               classNameStr,
 216                               temp.getAbsolutePath());
 217                 }
 218             }
 219         }
 220 
 221         String outputFileName = outputName + ".java";
 222         return new File(packageDir, outputFileName);
 223     }
 224 
 225 
 226     /** rmic environment for this object */
 227     private BatchEnvironment env;
 228 
 229     /** the remote class that this instance is generating code for */
 230     private RemoteClass remoteClass;
 231 
 232     /** version of the stub protocol to use in code generation */
 233     private int version;
 234 
 235     /** remote methods for remote class, indexed by operation number */
 236     private RemoteClass.Method[] remoteMethods;
 237 
 238     /**
 239      * Names for the remote class and the stub and skeleton classes
 240      * to be generated for it.
 241      */
 242     private Identifier remoteClassName;
 243     private Identifier stubClassName;
 244     private Identifier skeletonClassName;
 245 
 246     private ClassDefinition cdef;
 247     private File destDir;
 248     private File stubFile;
 249     private File skeletonFile;
 250 
 251     /**
 252      * Names to use for the java.lang.reflect.Method static fields
 253      * corresponding to each remote method.
 254      */
 255     private String[] methodFieldNames;
 256 
 257     /** cached definition for certain exception classes in this environment */
 258     private ClassDefinition defException;
 259     private ClassDefinition defRemoteException;
 260     private ClassDefinition defRuntimeException;
 261 
 262     /**
 263      * Create a new stub/skeleton Generator object for the given
 264      * remote implementation class to generate code according to
 265      * the given stub protocol version.
 266      */
 267     private RMIGenerator(BatchEnvironment env, ClassDefinition cdef,
 268                            File destDir, RemoteClass remoteClass, int version)
 269         throws ClassNotFound
 270     {
 271         this.destDir     = destDir;
 272         this.cdef        = cdef;
 273         this.env         = env;
 274         this.remoteClass = remoteClass;
 275         this.version     = version;
 276 
 277         remoteMethods = remoteClass.getRemoteMethods();
 278 
 279         remoteClassName = remoteClass.getName();
 280         stubClassName = Names.stubFor(remoteClassName);
 281         skeletonClassName = Names.skeletonFor(remoteClassName);
 282 
 283         methodFieldNames = nameMethodFields(remoteMethods);
 284 
 285         stubFile = sourceFileForClass(remoteClassName,stubClassName, destDir , env);
 286         skeletonFile = sourceFileForClass(remoteClassName,skeletonClassName, destDir, env);
 287 
 288         /*
 289          * Initialize cached definitions for exception classes used
 290          * in the generation process.
 291          */
 292         defException =
 293             env.getClassDeclaration(idJavaLangException).
 294                 getClassDefinition(env);
 295         defRemoteException =
 296             env.getClassDeclaration(idRemoteException).
 297                 getClassDefinition(env);
 298         defRuntimeException =
 299             env.getClassDeclaration(idJavaLangRuntimeException).
 300                 getClassDefinition(env);
 301     }
 302 
 303     /**
 304      * Write the stub for the remote class to a stream.
 305      */
 306     private void writeStub(IndentingWriter p) throws IOException {
 307 
 308         /*
 309          * Write boiler plate comment.
 310          */
 311         p.pln("// Stub class generated by rmic, do not edit.");
 312         p.pln("// Contents subject to change without notice.");
 313         p.pln();
 314 
 315         /*
 316          * If remote implementation class was in a particular package,
 317          * declare the stub class to be in the same package.
 318          */
 319         if (remoteClassName.isQualified()) {
 320             p.pln("package " + remoteClassName.getQualifier() + ";");
 321             p.pln();
 322         }
 323 
 324         /*
 325          * Declare the stub class; implement all remote interfaces.
 326          */
 327         p.plnI("public final class " +
 328             Names.mangleClass(stubClassName.getName()));
 329         p.pln("extends " + idRemoteStub);
 330         ClassDefinition[] remoteInterfaces = remoteClass.getRemoteInterfaces();
 331         if (remoteInterfaces.length > 0) {
 332             p.p("implements ");
 333             for (int i = 0; i < remoteInterfaces.length; i++) {
 334                 if (i > 0)
 335                     p.p(", ");
 336                 p.p(remoteInterfaces[i].getName().toString());
 337             }
 338             p.pln();
 339         }
 340         p.pOlnI("{");
 341 
 342         if (version == STUB_VERSION_1_1 ||
 343             version == STUB_VERSION_FAT)
 344         {
 345             writeOperationsArray(p);
 346             p.pln();
 347             writeInterfaceHash(p);
 348             p.pln();
 349         }
 350 
 351         if (version == STUB_VERSION_FAT ||
 352             version == STUB_VERSION_1_2)
 353         {
 354             p.pln("private static final long serialVersionUID = " +
 355                 STUB_SERIAL_VERSION_UID + ";");
 356             p.pln();
 357 
 358             /*
 359              * We only need to declare and initialize the static fields of
 360              * Method objects for each remote method if there are any remote
 361              * methods; otherwise, skip this code entirely, to avoid generating
 362              * a try/catch block for a checked exception that cannot occur
 363              * (see bugid 4125181).
 364              */
 365             if (methodFieldNames.length > 0) {
 366                 if (version == STUB_VERSION_FAT) {
 367                     p.pln("private static boolean useNewInvoke;");
 368                 }
 369                 writeMethodFieldDeclarations(p);
 370                 p.pln();
 371 
 372                 /*
 373                  * Initialize java.lang.reflect.Method fields for each remote
 374                  * method in a static initializer.
 375                  */
 376                 p.plnI("static {");
 377                 p.plnI("try {");
 378                 if (version == STUB_VERSION_FAT) {
 379                     /*
 380                      * Fat stubs must determine whether the API required for
 381                      * the JDK 1.2 stub protocol is supported in the current
 382                      * runtime, so that it can use it if supported.  This is
 383                      * determined by using the Reflection API to test if the
 384                      * new invoke method on RemoteRef exists, and setting the
 385                      * static boolean "useNewInvoke" to true if it does, or
 386                      * to false if a NoSuchMethodException is thrown.
 387                      */
 388                     p.plnI(idRemoteRef + ".class.getMethod(\"invoke\",");
 389                     p.plnI("new java.lang.Class[] {");
 390                     p.pln(idRemote + ".class,");
 391                     p.pln("java.lang.reflect.Method.class,");
 392                     p.pln("java.lang.Object[].class,");
 393                     p.pln("long.class");
 394                     p.pOln("});");
 395                     p.pO();
 396                     p.pln("useNewInvoke = true;");
 397                 }
 398                 writeMethodFieldInitializers(p);
 399                 p.pOlnI("} catch (java.lang.NoSuchMethodException e) {");
 400                 if (version == STUB_VERSION_FAT) {
 401                     p.pln("useNewInvoke = false;");
 402                 } else {
 403                     /*
 404                      * REMIND: By throwing an Error here, the application will
 405                      * get the NoSuchMethodError directly when the stub class
 406                      * is initialized.  If we throw a RuntimeException
 407                      * instead, the application would get an
 408                      * ExceptionInInitializerError.  Would that be more
 409                      * appropriate, and if so, which RuntimeException should
 410                      * be thrown?
 411                      */
 412                     p.plnI("throw new java.lang.NoSuchMethodError(");
 413                     p.pln("\"stub class initialization failed\");");
 414                     p.pO();
 415                 }
 416                 p.pOln("}");            // end try/catch block
 417                 p.pOln("}");            // end static initializer
 418                 p.pln();
 419             }
 420         }
 421 
 422         writeStubConstructors(p);
 423         p.pln();
 424 
 425         /*
 426          * Write each stub method.
 427          */
 428         if (remoteMethods.length > 0) {
 429             p.pln("// methods from remote interfaces");
 430             for (int i = 0; i < remoteMethods.length; ++i) {
 431                 p.pln();
 432                 writeStubMethod(p, i);
 433             }
 434         }
 435 
 436         p.pOln("}");                    // end stub class
 437     }
 438 
 439     /**
 440      * Write the constructors for the stub class.
 441      */
 442     private void writeStubConstructors(IndentingWriter p)
 443         throws IOException
 444     {
 445         p.pln("// constructors");
 446 
 447         /*
 448          * Only stubs compatible with the JDK 1.1 stub protocol need
 449          * a no-arg constructor; later versions use reflection to find
 450          * the constructor that directly takes a RemoteRef argument.
 451          */
 452         if (version == STUB_VERSION_1_1 ||
 453             version == STUB_VERSION_FAT)
 454         {
 455             p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
 456                 "() {");
 457             p.pln("super();");
 458             p.pOln("}");
 459         }
 460 
 461         p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
 462             "(" + idRemoteRef + " ref) {");
 463         p.pln("super(ref);");
 464         p.pOln("}");
 465     }
 466 
 467     /**
 468      * Write the stub method for the remote method with the given "opnum".
 469      */
 470     private void writeStubMethod(IndentingWriter p, int opnum)
 471         throws IOException
 472     {
 473         RemoteClass.Method method = remoteMethods[opnum];
 474         Identifier methodName = method.getName();
 475         Type methodType = method.getType();
 476         Type paramTypes[] = methodType.getArgumentTypes();
 477         String paramNames[] = nameParameters(paramTypes);
 478         Type returnType = methodType.getReturnType();
 479         ClassDeclaration[] exceptions = method.getExceptions();
 480 
 481         /*
 482          * Declare stub method; throw exceptions declared in remote
 483          * interface(s).
 484          */
 485         p.pln("// implementation of " +
 486             methodType.typeString(methodName.toString(), true, false));
 487         p.p("public " + returnType + " " + methodName + "(");
 488         for (int i = 0; i < paramTypes.length; i++) {
 489             if (i > 0)
 490                 p.p(", ");
 491             p.p(paramTypes[i] + " " + paramNames[i]);
 492         }
 493         p.plnI(")");
 494         if (exceptions.length > 0) {
 495             p.p("throws ");
 496             for (int i = 0; i < exceptions.length; i++) {
 497                 if (i > 0)
 498                     p.p(", ");
 499                 p.p(exceptions[i].getName().toString());
 500             }
 501             p.pln();
 502         }
 503         p.pOlnI("{");
 504 
 505         /*
 506          * The RemoteRef.invoke methods throw Exception, but unless this
 507          * stub method throws Exception as well, we must catch Exceptions
 508          * thrown from the invocation.  So we must catch Exception and
 509          * rethrow something we can throw: UnexpectedException, which is a
 510          * subclass of RemoteException.  But for any subclasses of Exception
 511          * that we can throw, like RemoteException, RuntimeException, and
 512          * any of the exceptions declared by this stub method, we want them
 513          * to pass through unharmed, so first we must catch any such
 514          * exceptions and rethrow it directly.
 515          *
 516          * We have to be careful generating the rethrowing catch blocks
 517          * here, because javac will flag an error if there are any
 518          * unreachable catch blocks, i.e. if the catch of an exception class
 519          * follows a previous catch of it or of one of its superclasses.
 520          * The following method invocation takes care of these details.
 521          */
 522         Vector catchList = computeUniqueCatchList(exceptions);
 523 
 524         /*
 525          * If we need to catch any particular exceptions (i.e. this method
 526          * does not declare java.lang.Exception), put the entire stub
 527          * method in a try block.
 528          */
 529         if (catchList.size() > 0) {
 530             p.plnI("try {");
 531         }
 532 
 533         if (version == STUB_VERSION_FAT) {
 534             p.plnI("if (useNewInvoke) {");
 535         }
 536         if (version == STUB_VERSION_FAT ||
 537             version == STUB_VERSION_1_2)
 538         {
 539             if (!returnType.isType(TC_VOID)) {
 540                 p.p("Object $result = ");               // REMIND: why $?
 541             }
 542             p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", ");
 543             if (paramTypes.length > 0) {
 544                 p.p("new java.lang.Object[] {");
 545                 for (int i = 0; i < paramTypes.length; i++) {
 546                     if (i > 0)
 547                         p.p(", ");
 548                     p.p(wrapArgumentCode(paramTypes[i], paramNames[i]));
 549                 }
 550                 p.p("}");
 551             } else {
 552                 p.p("null");
 553             }
 554             p.pln(", " + method.getMethodHash() + "L);");
 555             if (!returnType.isType(TC_VOID)) {
 556                 p.pln("return " +
 557                     unwrapArgumentCode(returnType, "$result") + ";");
 558             }
 559         }
 560         if (version == STUB_VERSION_FAT) {
 561             p.pOlnI("} else {");
 562         }
 563         if (version == STUB_VERSION_1_1 ||
 564             version == STUB_VERSION_FAT)
 565         {
 566             p.pln(idRemoteCall + " call = ref.newCall((" + idRemoteObject +
 567                 ") this, operations, " + opnum + ", interfaceHash);");
 568 
 569             if (paramTypes.length > 0) {
 570                 p.plnI("try {");
 571                 p.pln("java.io.ObjectOutput out = call.getOutputStream();");
 572                 writeMarshalArguments(p, "out", paramTypes, paramNames);
 573                 p.pOlnI("} catch (java.io.IOException e) {");
 574                 p.pln("throw new " + idMarshalException +
 575                     "(\"error marshalling arguments\", e);");
 576                 p.pOln("}");
 577             }
 578 
 579             p.pln("ref.invoke(call);");
 580 
 581             if (returnType.isType(TC_VOID)) {
 582                 p.pln("ref.done(call);");
 583             } else {
 584                 p.pln(returnType + " $result;");        // REMIND: why $?
 585                 p.plnI("try {");
 586                 p.pln("java.io.ObjectInput in = call.getInputStream();");
 587                 boolean objectRead =
 588                     writeUnmarshalArgument(p, "in", returnType, "$result");
 589                 p.pln(";");
 590                 p.pOlnI("} catch (java.io.IOException e) {");
 591                 p.pln("throw new " + idUnmarshalException +
 592                     "(\"error unmarshalling return\", e);");
 593                 /*
 594                  * If any only if readObject has been invoked, we must catch
 595                  * ClassNotFoundException as well as IOException.
 596                  */
 597                 if (objectRead) {
 598                     p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
 599                     p.pln("throw new " + idUnmarshalException +
 600                         "(\"error unmarshalling return\", e);");
 601                 }
 602                 p.pOlnI("} finally {");
 603                 p.pln("ref.done(call);");
 604                 p.pOln("}");
 605                 p.pln("return $result;");
 606             }
 607         }
 608         if (version == STUB_VERSION_FAT) {
 609             p.pOln("}");                // end if/else (useNewInvoke) block
 610         }
 611 
 612         /*
 613          * If we need to catch any particular exceptions, finally write
 614          * the catch blocks for them, rethrow any other Exceptions with an
 615          * UnexpectedException, and end the try block.
 616          */
 617         if (catchList.size() > 0) {
 618             for (Enumeration enumeration = catchList.elements();
 619                  enumeration.hasMoreElements();)
 620             {
 621                 ClassDefinition def = (ClassDefinition) enumeration.nextElement();
 622                 p.pOlnI("} catch (" + def.getName() + " e) {");
 623                 p.pln("throw e;");
 624             }
 625             p.pOlnI("} catch (java.lang.Exception e) {");
 626             p.pln("throw new " + idUnexpectedException +
 627                 "(\"undeclared checked exception\", e);");
 628             p.pOln("}");                // end try/catch block
 629         }
 630 
 631         p.pOln("}");                    // end stub method
 632     }
 633 
 634     /**
 635      * Compute the exceptions which need to be caught and rethrown in a
 636      * stub method before wrapping Exceptions in UnexpectedExceptions,
 637      * given the exceptions declared in the throws clause of the method.
 638      * Returns a Vector containing ClassDefinition objects for each
 639      * exception to catch.  Each exception is guaranteed to be unique,
 640      * i.e. not a subclass of any of the other exceptions in the Vector,
 641      * so the catch blocks for these exceptions may be generated in any
 642      * order relative to each other.
 643      *
 644      * RemoteException and RuntimeException are each automatically placed
 645      * in the returned Vector (if none of their superclasses are already
 646      * present), since those exceptions should always be directly rethrown
 647      * by a stub method.
 648      *
 649      * The returned Vector will be empty if java.lang.Exception or one
 650      * of its superclasses is in the throws clause of the method, indicating
 651      * that no exceptions need to be caught.
 652      */
 653     private Vector computeUniqueCatchList(ClassDeclaration[] exceptions) {
 654         Vector uniqueList = new Vector();       // unique exceptions to catch
 655 
 656         uniqueList.addElement(defRuntimeException);
 657         uniqueList.addElement(defRemoteException);
 658 
 659         /* For each exception declared by the stub method's throws clause: */
 660     nextException:
 661         for (int i = 0; i < exceptions.length; i++) {
 662             ClassDeclaration decl = exceptions[i];
 663             try {
 664                 if (defException.subClassOf(env, decl)) {
 665                     /*
 666                      * (If java.lang.Exception (or a superclass) was declared
 667                      * in the throws clause of this stub method, then we don't
 668                      * have to bother catching anything; clear the list and
 669                      * return.)
 670                      */
 671                     uniqueList.clear();
 672                     break;
 673                 } else if (!defException.superClassOf(env, decl)) {
 674                     /*
 675                      * Ignore other Throwables that do not extend Exception,
 676                      * since they do not need to be caught anyway.
 677                      */
 678                     continue;
 679                 }
 680                 /*
 681                  * Compare this exception against the current list of
 682                  * exceptions that need to be caught:
 683                  */
 684                 for (int j = 0; j < uniqueList.size();) {
 685                     ClassDefinition def =
 686                         (ClassDefinition) uniqueList.elementAt(j);
 687                     if (def.superClassOf(env, decl)) {
 688                         /*
 689                          * If a superclass of this exception is already on
 690                          * the list to catch, then ignore and continue;
 691                          */
 692                         continue nextException;
 693                     } else if (def.subClassOf(env, decl)) {
 694                         /*
 695                          * If a subclass of this exception is on the list
 696                          * to catch, then remove it.
 697                          */
 698                         uniqueList.removeElementAt(j);
 699                     } else {
 700                         j++;    // else continue comparing
 701                     }
 702                 }
 703                 /* This exception is unique: add it to the list to catch. */
 704                 uniqueList.addElement(decl.getClassDefinition(env));
 705             } catch (ClassNotFound e) {
 706                 env.error(0, "class.not.found", e.name, decl.getName());
 707                 /*
 708                  * REMIND: We do not exit from this exceptional condition,
 709                  * generating questionable code and likely letting the
 710                  * compiler report a resulting error later.
 711                  */
 712             }
 713         }
 714         return uniqueList;
 715     }
 716 
 717     /**
 718      * Write the skeleton for the remote class to a stream.
 719      */
 720     private void writeSkeleton(IndentingWriter p) throws IOException {
 721         if (version == STUB_VERSION_1_2) {
 722             throw new Error("should not generate skeleton for version");
 723         }
 724 
 725         /*
 726          * Write boiler plate comment.
 727          */
 728         p.pln("// Skeleton class generated by rmic, do not edit.");
 729         p.pln("// Contents subject to change without notice.");
 730         p.pln();
 731 
 732         /*
 733          * If remote implementation class was in a particular package,
 734          * declare the skeleton class to be in the same package.
 735          */
 736         if (remoteClassName.isQualified()) {
 737             p.pln("package " + remoteClassName.getQualifier() + ";");
 738             p.pln();
 739         }
 740 
 741         /*
 742          * Declare the skeleton class.
 743          */
 744         p.plnI("public final class " +
 745             Names.mangleClass(skeletonClassName.getName()));
 746         p.pln("implements " + idSkeleton);
 747         p.pOlnI("{");
 748 
 749         writeOperationsArray(p);
 750         p.pln();
 751 
 752         writeInterfaceHash(p);
 753         p.pln();
 754 
 755         /*
 756          * Define the getOperations() method.
 757          */
 758         p.plnI("public " + idOperation + "[] getOperations() {");
 759         p.pln("return (" + idOperation + "[]) operations.clone();");
 760         p.pOln("}");
 761         p.pln();
 762 
 763         /*
 764          * Define the dispatch() method.
 765          */
 766         p.plnI("public void dispatch(" + idRemote + " obj, " +
 767             idRemoteCall + " call, int opnum, long hash)");
 768         p.pln("throws java.lang.Exception");
 769         p.pOlnI("{");
 770 
 771         if (version == STUB_VERSION_FAT) {
 772             p.plnI("if (opnum < 0) {");
 773             if (remoteMethods.length > 0) {
 774                 for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
 775                     if (opnum > 0)
 776                         p.pO("} else ");
 777                     p.plnI("if (hash == " +
 778                         remoteMethods[opnum].getMethodHash() + "L) {");
 779                     p.pln("opnum = " + opnum + ";");
 780                 }
 781                 p.pOlnI("} else {");
 782             }
 783             /*
 784              * Skeleton throws UnmarshalException if it does not recognize
 785              * the method hash; this is what UnicastServerRef.dispatch()
 786              * would do.
 787              */
 788             p.pln("throw new " +
 789                 idUnmarshalException + "(\"invalid method hash\");");
 790             if (remoteMethods.length > 0) {
 791                 p.pOln("}");
 792             }
 793             /*
 794              * Ignore the validation of the interface hash if the
 795              * operation number was negative, since it is really a
 796              * method hash instead.
 797              */
 798             p.pOlnI("} else {");
 799         }
 800 
 801         p.plnI("if (hash != interfaceHash)");
 802         p.pln("throw new " +
 803             idSkeletonMismatchException + "(\"interface hash mismatch\");");
 804         p.pO();
 805 
 806         if (version == STUB_VERSION_FAT) {
 807             p.pOln("}");                // end if/else (opnum < 0) block
 808         }
 809         p.pln();
 810 
 811         /*
 812          * Cast remote object instance to our specific implementation class.
 813          */
 814         p.pln(remoteClassName + " server = (" + remoteClassName + ") obj;");
 815 
 816         /*
 817          * Process call according to the operation number.
 818          */
 819         p.plnI("switch (opnum) {");
 820         for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
 821             writeSkeletonDispatchCase(p, opnum);
 822         }
 823         p.pOlnI("default:");
 824         /*
 825          * Skeleton throws UnmarshalException if it does not recognize
 826          * the operation number; this is consistent with the case of an
 827          * unrecognized method hash.
 828          */
 829         p.pln("throw new " + idUnmarshalException +
 830             "(\"invalid method number\");");
 831         p.pOln("}");                    // end switch statement
 832 
 833         p.pOln("}");                    // end dispatch() method
 834 
 835         p.pOln("}");                    // end skeleton class
 836     }
 837 
 838     /**
 839      * Write the case block for the skeleton's dispatch method for
 840      * the remote method with the given "opnum".
 841      */
 842     private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
 843         throws IOException
 844     {
 845         RemoteClass.Method method = remoteMethods[opnum];
 846         Identifier methodName = method.getName();
 847         Type methodType = method.getType();
 848         Type paramTypes[] = methodType.getArgumentTypes();
 849         String paramNames[] = nameParameters(paramTypes);
 850         Type returnType = methodType.getReturnType();
 851 
 852         p.pOlnI("case " + opnum + ": // " +
 853             methodType.typeString(methodName.toString(), true, false));
 854         /*
 855          * Use nested block statement inside case to provide an independent
 856          * namespace for local variables used to unmarshal parameters for
 857          * this remote method.
 858          */
 859         p.pOlnI("{");
 860 
 861         if (paramTypes.length > 0) {
 862             /*
 863              * Declare local variables to hold arguments.
 864              */
 865             for (int i = 0; i < paramTypes.length; i++) {
 866                 p.pln(paramTypes[i] + " " + paramNames[i] + ";");
 867             }
 868 
 869             /*
 870              * Unmarshal arguments from call stream.
 871              */
 872             p.plnI("try {");
 873             p.pln("java.io.ObjectInput in = call.getInputStream();");
 874             boolean objectsRead = writeUnmarshalArguments(p, "in",
 875                 paramTypes, paramNames);
 876             p.pOlnI("} catch (java.io.IOException e) {");
 877             p.pln("throw new " + idUnmarshalException +
 878                 "(\"error unmarshalling arguments\", e);");
 879             /*
 880              * If any only if readObject has been invoked, we must catch
 881              * ClassNotFoundException as well as IOException.
 882              */
 883             if (objectsRead) {
 884                 p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
 885                 p.pln("throw new " + idUnmarshalException +
 886                     "(\"error unmarshalling arguments\", e);");
 887             }
 888             p.pOlnI("} finally {");
 889             p.pln("call.releaseInputStream();");
 890             p.pOln("}");
 891         } else {
 892             p.pln("call.releaseInputStream();");
 893         }
 894 
 895         if (!returnType.isType(TC_VOID)) {
 896             /*
 897              * Declare variable to hold return type, if not void.
 898              */
 899             p.p(returnType + " $result = ");            // REMIND: why $?
 900         }
 901 
 902         /*
 903          * Invoke the method on the server object.
 904          */
 905         p.p("server." + methodName + "(");
 906         for (int i = 0; i < paramNames.length; i++) {
 907             if (i > 0)
 908                 p.p(", ");
 909             p.p(paramNames[i]);
 910         }
 911         p.pln(");");
 912 
 913         /*
 914          * Always invoke getResultStream(true) on the call object to send
 915          * the indication of a successful invocation to the caller.  If
 916          * the return type is not void, keep the result stream and marshal
 917          * the return value.
 918          */
 919         p.plnI("try {");
 920         if (!returnType.isType(TC_VOID)) {
 921             p.p("java.io.ObjectOutput out = ");
 922         }
 923         p.pln("call.getResultStream(true);");
 924         if (!returnType.isType(TC_VOID)) {
 925             writeMarshalArgument(p, "out", returnType, "$result");
 926             p.pln(";");
 927         }
 928         p.pOlnI("} catch (java.io.IOException e) {");
 929         p.pln("throw new " +
 930             idMarshalException + "(\"error marshalling return\", e);");
 931         p.pOln("}");
 932 
 933         p.pln("break;");                // break from switch statement
 934 
 935         p.pOlnI("}");                   // end nested block statement
 936         p.pln();
 937     }
 938 
 939     /**
 940      * Write declaration and initializer for "operations" static array.
 941      */
 942     private void writeOperationsArray(IndentingWriter p)
 943         throws IOException
 944     {
 945         p.plnI("private static final " + idOperation + "[] operations = {");
 946         for (int i = 0; i < remoteMethods.length; i++) {
 947             if (i > 0)
 948                 p.pln(",");
 949             p.p("new " + idOperation + "(\"" +
 950                 remoteMethods[i].getOperationString() + "\")");
 951         }
 952         p.pln();
 953         p.pOln("};");
 954     }
 955 
 956     /**
 957      * Write declaration and initializer for "interfaceHash" static field.
 958      */
 959     private void writeInterfaceHash(IndentingWriter p)
 960         throws IOException
 961     {
 962         p.pln("private static final long interfaceHash = " +
 963             remoteClass.getInterfaceHash() + "L;");
 964     }
 965 
 966     /**
 967      * Write declaration for java.lang.reflect.Method static fields
 968      * corresponding to each remote method in a stub.
 969      */
 970     private void writeMethodFieldDeclarations(IndentingWriter p)
 971         throws IOException
 972     {
 973         for (int i = 0; i < methodFieldNames.length; i++) {
 974             p.pln("private static java.lang.reflect.Method " +
 975                 methodFieldNames[i] + ";");
 976         }
 977     }
 978 
 979     /**
 980      * Write code to initialize the static fields for each method
 981      * using the Java Reflection API.
 982      */
 983     private void writeMethodFieldInitializers(IndentingWriter p)
 984         throws IOException
 985     {
 986         for (int i = 0; i < methodFieldNames.length; i++) {
 987             p.p(methodFieldNames[i] + " = ");
 988             /*
 989              * Here we look up the Method object in the arbitrary interface
 990              * that we find in the RemoteClass.Method object.
 991              * REMIND: Is this arbitrary choice OK?
 992              * REMIND: Should this access be part of RemoteClass.Method's
 993              * abstraction?
 994              */
 995             RemoteClass.Method method = remoteMethods[i];
 996             MemberDefinition def = method.getMemberDefinition();
 997             Identifier methodName = method.getName();
 998             Type methodType = method.getType();
 999             Type paramTypes[] = methodType.getArgumentTypes();
1000 
1001             p.p(def.getClassDefinition().getName() + ".class.getMethod(\"" +
1002                 methodName + "\", new java.lang.Class[] {");
1003             for (int j = 0; j < paramTypes.length; j++) {
1004                 if (j > 0)
1005                     p.p(", ");
1006                 p.p(paramTypes[j] + ".class");
1007             }
1008             p.pln("});");
1009         }
1010     }
1011 
1012 
1013     /*
1014      * Following are a series of static utility methods useful during
1015      * the code generation process:
1016      */
1017 
1018     /**
1019      * Generate an array of names for fields that correspond to the given
1020      * array of remote methods.  Each name in the returned array is
1021      * guaranteed to be unique.
1022      *
1023      * The name of a method is included in its corresponding field name
1024      * to enhance readability of the generated code.
1025      */
1026     private static String[] nameMethodFields(RemoteClass.Method[] methods) {
1027         String[] names = new String[methods.length];
1028         for (int i = 0; i < names.length; i++) {
1029             names[i] = "$method_" + methods[i].getName() + "_" + i;
1030         }
1031         return names;
1032     }
1033 
1034     /**
1035      * Generate an array of names for parameters corresponding to the
1036      * given array of types for the parameters.  Each name in the returned
1037      * array is guaranteed to be unique.
1038      *
1039      * A representation of the type of a parameter is included in its
1040      * corresponding field name to enhance the readability of the generated
1041      * code.
1042      */
1043     private static String[] nameParameters(Type[] types) {
1044         String[] names = new String[types.length];
1045         for (int i = 0; i < names.length; i++) {
1046             names[i] = "$param_" +
1047                 generateNameFromType(types[i]) + "_" + (i + 1);
1048         }
1049         return names;
1050     }
1051 
1052     /**
1053      * Generate a readable string representing the given type suitable
1054      * for embedding within a Java identifier.
1055      */
1056     private static String generateNameFromType(Type type) {
1057         int typeCode = type.getTypeCode();
1058         switch (typeCode) {
1059         case TC_BOOLEAN:
1060         case TC_BYTE:
1061         case TC_CHAR:
1062         case TC_SHORT:
1063         case TC_INT:
1064         case TC_LONG:
1065         case TC_FLOAT:
1066         case TC_DOUBLE:
1067             return type.toString();
1068         case TC_ARRAY:
1069             return "arrayOf_" + generateNameFromType(type.getElementType());
1070         case TC_CLASS:
1071             return Names.mangleClass(type.getClassName().getName()).toString();
1072         default:
1073             throw new Error("unexpected type code: " + typeCode);
1074         }
1075     }
1076 
1077     /**
1078      * Write a snippet of Java code to marshal a value named "name" of
1079      * type "type" to the java.io.ObjectOutput stream named "stream".
1080      *
1081      * Primitive types are marshalled with their corresponding methods
1082      * in the java.io.DataOutput interface, and objects (including arrays)
1083      * are marshalled using the writeObject method.
1084      */
1085     private static void writeMarshalArgument(IndentingWriter p,
1086                                              String streamName,
1087                                              Type type, String name)
1088         throws IOException
1089     {
1090         int typeCode = type.getTypeCode();
1091         switch (typeCode) {
1092         case TC_BOOLEAN:
1093             p.p(streamName + ".writeBoolean(" + name + ")");
1094             break;
1095         case TC_BYTE:
1096             p.p(streamName + ".writeByte(" + name + ")");
1097             break;
1098         case TC_CHAR:
1099             p.p(streamName + ".writeChar(" + name + ")");
1100             break;
1101         case TC_SHORT:
1102             p.p(streamName + ".writeShort(" + name + ")");
1103             break;
1104         case TC_INT:
1105             p.p(streamName + ".writeInt(" + name + ")");
1106             break;
1107         case TC_LONG:
1108             p.p(streamName + ".writeLong(" + name + ")");
1109             break;
1110         case TC_FLOAT:
1111             p.p(streamName + ".writeFloat(" + name + ")");
1112             break;
1113         case TC_DOUBLE:
1114             p.p(streamName + ".writeDouble(" + name + ")");
1115             break;
1116         case TC_ARRAY:
1117         case TC_CLASS:
1118             p.p(streamName + ".writeObject(" + name + ")");
1119             break;
1120         default:
1121             throw new Error("unexpected type code: " + typeCode);
1122         }
1123     }
1124 
1125     /**
1126      * Write Java statements to marshal a series of values in order as
1127      * named in the "names" array, with types as specified in the "types"
1128      * array", to the java.io.ObjectOutput stream named "stream".
1129      */
1130     private static void writeMarshalArguments(IndentingWriter p,
1131                                               String streamName,
1132                                               Type[] types, String[] names)
1133         throws IOException
1134     {
1135         if (types.length != names.length) {
1136             throw new Error("paramter type and name arrays different sizes");
1137         }
1138 
1139         for (int i = 0; i < types.length; i++) {
1140             writeMarshalArgument(p, streamName, types[i], names[i]);
1141             p.pln(";");
1142         }
1143     }
1144 
1145     /**
1146      * Write a snippet of Java code to unmarshal a value of type "type"
1147      * from the java.io.ObjectInput stream named "stream" into a variable
1148      * named "name" (if "name" is null, the value in unmarshalled and
1149      * discarded).
1150      *
1151      * Primitive types are unmarshalled with their corresponding methods
1152      * in the java.io.DataInput interface, and objects (including arrays)
1153      * are unmarshalled using the readObject method.
1154      */
1155     private static boolean writeUnmarshalArgument(IndentingWriter p,
1156                                                   String streamName,
1157                                                   Type type, String name)
1158         throws IOException
1159     {
1160         boolean readObject = false;
1161 
1162         if (name != null) {
1163             p.p(name + " = ");
1164         }
1165 
1166         int typeCode = type.getTypeCode();
1167         switch (type.getTypeCode()) {
1168         case TC_BOOLEAN:
1169             p.p(streamName + ".readBoolean()");
1170             break;
1171         case TC_BYTE:
1172             p.p(streamName + ".readByte()");
1173             break;
1174         case TC_CHAR:
1175             p.p(streamName + ".readChar()");
1176             break;
1177         case TC_SHORT:
1178             p.p(streamName + ".readShort()");
1179             break;
1180         case TC_INT:
1181             p.p(streamName + ".readInt()");
1182             break;
1183         case TC_LONG:
1184             p.p(streamName + ".readLong()");
1185             break;
1186         case TC_FLOAT:
1187             p.p(streamName + ".readFloat()");
1188             break;
1189         case TC_DOUBLE:
1190             p.p(streamName + ".readDouble()");
1191             break;
1192         case TC_ARRAY:
1193         case TC_CLASS:
1194             p.p("(" + type + ") " + streamName + ".readObject()");
1195             readObject = true;
1196             break;
1197         default:
1198             throw new Error("unexpected type code: " + typeCode);
1199         }
1200         return readObject;
1201     }
1202 
1203     /**
1204      * Write Java statements to unmarshal a series of values in order of
1205      * types as in the "types" array from the java.io.ObjectInput stream
1206      * named "stream" into variables as named in "names" (for any element
1207      * of "names" that is null, the corresponding value is unmarshalled
1208      * and discarded).
1209      */
1210     private static boolean writeUnmarshalArguments(IndentingWriter p,
1211                                                    String streamName,
1212                                                    Type[] types,
1213                                                    String[] names)
1214         throws IOException
1215     {
1216         if (types.length != names.length) {
1217             throw new Error("paramter type and name arrays different sizes");
1218         }
1219 
1220         boolean readObject = false;
1221         for (int i = 0; i < types.length; i++) {
1222             if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
1223                 readObject = true;
1224             }
1225             p.pln(";");
1226         }
1227         return readObject;
1228     }
1229 
1230     /**
1231      * Return a snippet of Java code to wrap a value named "name" of
1232      * type "type" into an object as appropriate for use by the
1233      * Java Reflection API.
1234      *
1235      * For primitive types, an appropriate wrapper class instantiated
1236      * with the primitive value.  For object types (including arrays),
1237      * no wrapping is necessary, so the value is named directly.
1238      */
1239     private static String wrapArgumentCode(Type type, String name) {
1240         int typeCode = type.getTypeCode();
1241         switch (typeCode) {
1242         case TC_BOOLEAN:
1243             return ("(" + name +
1244                     " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
1245         case TC_BYTE:
1246             return "new java.lang.Byte(" + name + ")";
1247         case TC_CHAR:
1248             return "new java.lang.Character(" + name + ")";
1249         case TC_SHORT:
1250             return "new java.lang.Short(" + name + ")";
1251         case TC_INT:
1252             return "new java.lang.Integer(" + name + ")";
1253         case TC_LONG:
1254             return "new java.lang.Long(" + name + ")";
1255         case TC_FLOAT:
1256             return "new java.lang.Float(" + name + ")";
1257         case TC_DOUBLE:
1258             return "new java.lang.Double(" + name + ")";
1259         case TC_ARRAY:
1260         case TC_CLASS:
1261             return name;
1262         default:
1263             throw new Error("unexpected type code: " + typeCode);
1264         }
1265     }
1266 
1267     /**
1268      * Return a snippet of Java code to unwrap a value named "name" into
1269      * a value of type "type", as appropriate for the Java Reflection API.
1270      *
1271      * For primitive types, the value is assumed to be of the corresponding
1272      * wrapper type, and a method is called on the wrapper type to retrieve
1273      * the primitive value.  For object types (include arrays), no
1274      * unwrapping is necessary; the value is simply cast to the expected
1275      * real object type.
1276      */
1277     private static String unwrapArgumentCode(Type type, String name) {
1278         int typeCode = type.getTypeCode();
1279         switch (typeCode) {
1280         case TC_BOOLEAN:
1281             return "((java.lang.Boolean) " + name + ").booleanValue()";
1282         case TC_BYTE:
1283             return "((java.lang.Byte) " + name + ").byteValue()";
1284         case TC_CHAR:
1285             return "((java.lang.Character) " + name + ").charValue()";
1286         case TC_SHORT:
1287             return "((java.lang.Short) " + name + ").shortValue()";
1288         case TC_INT:
1289             return "((java.lang.Integer) " + name + ").intValue()";
1290         case TC_LONG:
1291             return "((java.lang.Long) " + name + ").longValue()";
1292         case TC_FLOAT:
1293             return "((java.lang.Float) " + name + ").floatValue()";
1294         case TC_DOUBLE:
1295             return "((java.lang.Double) " + name + ").doubleValue()";
1296         case TC_ARRAY:
1297         case TC_CLASS:
1298             return "((" + type + ") " + name + ")";
1299         default:
1300             throw new Error("unexpected type code: " + typeCode);
1301         }
1302     }
1303 }