1 /*
2  * Copyright (c) 1997, 2013, 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<String, Integer> 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      */
RMIGenerator()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      */
parseArgs(String argv[], Main main)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 = versionOptions.get(arg);
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      */
generate(BatchEnvironment env, ClassDefinition cdef, File destDir)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 
generate()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      */
sourceFileForClass(Identifier className, Identifier outputClassName, File destDir, BatchEnvironment env)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      */
RMIGenerator(BatchEnvironment env, ClassDefinition cdef, File destDir, RemoteClass remoteClass, int version)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      */
writeStub(IndentingWriter p)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      */
writeStubConstructors(IndentingWriter p)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      */
writeStubMethod(IndentingWriter p, int opnum)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<ClassDefinition> 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<ClassDefinition> enumeration = catchList.elements();
619                  enumeration.hasMoreElements();)
620             {
621                 ClassDefinition def = 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      */
computeUniqueCatchList(ClassDeclaration[] exceptions)653     private Vector<ClassDefinition> computeUniqueCatchList(ClassDeclaration[] exceptions) {
654         Vector<ClassDefinition> 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 = uniqueList.elementAt(j);
686                     if (def.superClassOf(env, decl)) {
687                         /*
688                          * If a superclass of this exception is already on
689                          * the list to catch, then ignore and continue;
690                          */
691                         continue nextException;
692                     } else if (def.subClassOf(env, decl)) {
693                         /*
694                          * If a subclass of this exception is on the list
695                          * to catch, then remove it.
696                          */
697                         uniqueList.removeElementAt(j);
698                     } else {
699                         j++;    // else continue comparing
700                     }
701                 }
702                 /* This exception is unique: add it to the list to catch. */
703                 uniqueList.addElement(decl.getClassDefinition(env));
704             } catch (ClassNotFound e) {
705                 env.error(0, "class.not.found", e.name, decl.getName());
706                 /*
707                  * REMIND: We do not exit from this exceptional condition,
708                  * generating questionable code and likely letting the
709                  * compiler report a resulting error later.
710                  */
711             }
712         }
713         return uniqueList;
714     }
715 
716     /**
717      * Write the skeleton for the remote class to a stream.
718      */
writeSkeleton(IndentingWriter p)719     private void writeSkeleton(IndentingWriter p) throws IOException {
720         if (version == STUB_VERSION_1_2) {
721             throw new Error("should not generate skeleton for version");
722         }
723 
724         /*
725          * Write boiler plate comment.
726          */
727         p.pln("// Skeleton class generated by rmic, do not edit.");
728         p.pln("// Contents subject to change without notice.");
729         p.pln();
730 
731         /*
732          * If remote implementation class was in a particular package,
733          * declare the skeleton class to be in the same package.
734          */
735         if (remoteClassName.isQualified()) {
736             p.pln("package " + remoteClassName.getQualifier() + ";");
737             p.pln();
738         }
739 
740         /*
741          * Declare the skeleton class.
742          */
743         p.plnI("public final class " +
744             Names.mangleClass(skeletonClassName.getName()));
745         p.pln("implements " + idSkeleton);
746         p.pOlnI("{");
747 
748         writeOperationsArray(p);
749         p.pln();
750 
751         writeInterfaceHash(p);
752         p.pln();
753 
754         /*
755          * Define the getOperations() method.
756          */
757         p.plnI("public " + idOperation + "[] getOperations() {");
758         p.pln("return (" + idOperation + "[]) operations.clone();");
759         p.pOln("}");
760         p.pln();
761 
762         /*
763          * Define the dispatch() method.
764          */
765         p.plnI("public void dispatch(" + idRemote + " obj, " +
766             idRemoteCall + " call, int opnum, long hash)");
767         p.pln("throws java.lang.Exception");
768         p.pOlnI("{");
769 
770         if (version == STUB_VERSION_FAT) {
771             p.plnI("if (opnum < 0) {");
772             if (remoteMethods.length > 0) {
773                 for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
774                     if (opnum > 0)
775                         p.pO("} else ");
776                     p.plnI("if (hash == " +
777                         remoteMethods[opnum].getMethodHash() + "L) {");
778                     p.pln("opnum = " + opnum + ";");
779                 }
780                 p.pOlnI("} else {");
781             }
782             /*
783              * Skeleton throws UnmarshalException if it does not recognize
784              * the method hash; this is what UnicastServerRef.dispatch()
785              * would do.
786              */
787             p.pln("throw new " +
788                 idUnmarshalException + "(\"invalid method hash\");");
789             if (remoteMethods.length > 0) {
790                 p.pOln("}");
791             }
792             /*
793              * Ignore the validation of the interface hash if the
794              * operation number was negative, since it is really a
795              * method hash instead.
796              */
797             p.pOlnI("} else {");
798         }
799 
800         p.plnI("if (hash != interfaceHash)");
801         p.pln("throw new " +
802             idSkeletonMismatchException + "(\"interface hash mismatch\");");
803         p.pO();
804 
805         if (version == STUB_VERSION_FAT) {
806             p.pOln("}");                // end if/else (opnum < 0) block
807         }
808         p.pln();
809 
810         /*
811          * Cast remote object instance to our specific implementation class.
812          */
813         p.pln(remoteClassName + " server = (" + remoteClassName + ") obj;");
814 
815         /*
816          * Process call according to the operation number.
817          */
818         p.plnI("switch (opnum) {");
819         for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
820             writeSkeletonDispatchCase(p, opnum);
821         }
822         p.pOlnI("default:");
823         /*
824          * Skeleton throws UnmarshalException if it does not recognize
825          * the operation number; this is consistent with the case of an
826          * unrecognized method hash.
827          */
828         p.pln("throw new " + idUnmarshalException +
829             "(\"invalid method number\");");
830         p.pOln("}");                    // end switch statement
831 
832         p.pOln("}");                    // end dispatch() method
833 
834         p.pOln("}");                    // end skeleton class
835     }
836 
837     /**
838      * Write the case block for the skeleton's dispatch method for
839      * the remote method with the given "opnum".
840      */
writeSkeletonDispatchCase(IndentingWriter p, int opnum)841     private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
842         throws IOException
843     {
844         RemoteClass.Method method = remoteMethods[opnum];
845         Identifier methodName = method.getName();
846         Type methodType = method.getType();
847         Type paramTypes[] = methodType.getArgumentTypes();
848         String paramNames[] = nameParameters(paramTypes);
849         Type returnType = methodType.getReturnType();
850 
851         p.pOlnI("case " + opnum + ": // " +
852             methodType.typeString(methodName.toString(), true, false));
853         /*
854          * Use nested block statement inside case to provide an independent
855          * namespace for local variables used to unmarshal parameters for
856          * this remote method.
857          */
858         p.pOlnI("{");
859 
860         if (paramTypes.length > 0) {
861             /*
862              * Declare local variables to hold arguments.
863              */
864             for (int i = 0; i < paramTypes.length; i++) {
865                 p.pln(paramTypes[i] + " " + paramNames[i] + ";");
866             }
867 
868             /*
869              * Unmarshal arguments from call stream.
870              */
871             p.plnI("try {");
872             p.pln("java.io.ObjectInput in = call.getInputStream();");
873             boolean objectsRead = writeUnmarshalArguments(p, "in",
874                 paramTypes, paramNames);
875             p.pOlnI("} catch (java.io.IOException e) {");
876             p.pln("throw new " + idUnmarshalException +
877                 "(\"error unmarshalling arguments\", e);");
878             /*
879              * If any only if readObject has been invoked, we must catch
880              * ClassNotFoundException as well as IOException.
881              */
882             if (objectsRead) {
883                 p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
884                 p.pln("throw new " + idUnmarshalException +
885                     "(\"error unmarshalling arguments\", e);");
886             }
887             p.pOlnI("} finally {");
888             p.pln("call.releaseInputStream();");
889             p.pOln("}");
890         } else {
891             p.pln("call.releaseInputStream();");
892         }
893 
894         if (!returnType.isType(TC_VOID)) {
895             /*
896              * Declare variable to hold return type, if not void.
897              */
898             p.p(returnType + " $result = ");            // REMIND: why $?
899         }
900 
901         /*
902          * Invoke the method on the server object.
903          */
904         p.p("server." + methodName + "(");
905         for (int i = 0; i < paramNames.length; i++) {
906             if (i > 0)
907                 p.p(", ");
908             p.p(paramNames[i]);
909         }
910         p.pln(");");
911 
912         /*
913          * Always invoke getResultStream(true) on the call object to send
914          * the indication of a successful invocation to the caller.  If
915          * the return type is not void, keep the result stream and marshal
916          * the return value.
917          */
918         p.plnI("try {");
919         if (!returnType.isType(TC_VOID)) {
920             p.p("java.io.ObjectOutput out = ");
921         }
922         p.pln("call.getResultStream(true);");
923         if (!returnType.isType(TC_VOID)) {
924             writeMarshalArgument(p, "out", returnType, "$result");
925             p.pln(";");
926         }
927         p.pOlnI("} catch (java.io.IOException e) {");
928         p.pln("throw new " +
929             idMarshalException + "(\"error marshalling return\", e);");
930         p.pOln("}");
931 
932         p.pln("break;");                // break from switch statement
933 
934         p.pOlnI("}");                   // end nested block statement
935         p.pln();
936     }
937 
938     /**
939      * Write declaration and initializer for "operations" static array.
940      */
writeOperationsArray(IndentingWriter p)941     private void writeOperationsArray(IndentingWriter p)
942         throws IOException
943     {
944         p.plnI("private static final " + idOperation + "[] operations = {");
945         for (int i = 0; i < remoteMethods.length; i++) {
946             if (i > 0)
947                 p.pln(",");
948             p.p("new " + idOperation + "(\"" +
949                 remoteMethods[i].getOperationString() + "\")");
950         }
951         p.pln();
952         p.pOln("};");
953     }
954 
955     /**
956      * Write declaration and initializer for "interfaceHash" static field.
957      */
writeInterfaceHash(IndentingWriter p)958     private void writeInterfaceHash(IndentingWriter p)
959         throws IOException
960     {
961         p.pln("private static final long interfaceHash = " +
962             remoteClass.getInterfaceHash() + "L;");
963     }
964 
965     /**
966      * Write declaration for java.lang.reflect.Method static fields
967      * corresponding to each remote method in a stub.
968      */
writeMethodFieldDeclarations(IndentingWriter p)969     private void writeMethodFieldDeclarations(IndentingWriter p)
970         throws IOException
971     {
972         for (int i = 0; i < methodFieldNames.length; i++) {
973             p.pln("private static java.lang.reflect.Method " +
974                 methodFieldNames[i] + ";");
975         }
976     }
977 
978     /**
979      * Write code to initialize the static fields for each method
980      * using the Java Reflection API.
981      */
writeMethodFieldInitializers(IndentingWriter p)982     private void writeMethodFieldInitializers(IndentingWriter p)
983         throws IOException
984     {
985         for (int i = 0; i < methodFieldNames.length; i++) {
986             p.p(methodFieldNames[i] + " = ");
987             /*
988              * Here we look up the Method object in the arbitrary interface
989              * that we find in the RemoteClass.Method object.
990              * REMIND: Is this arbitrary choice OK?
991              * REMIND: Should this access be part of RemoteClass.Method's
992              * abstraction?
993              */
994             RemoteClass.Method method = remoteMethods[i];
995             MemberDefinition def = method.getMemberDefinition();
996             Identifier methodName = method.getName();
997             Type methodType = method.getType();
998             Type paramTypes[] = methodType.getArgumentTypes();
999 
1000             p.p(def.getClassDefinition().getName() + ".class.getMethod(\"" +
1001                 methodName + "\", new java.lang.Class[] {");
1002             for (int j = 0; j < paramTypes.length; j++) {
1003                 if (j > 0)
1004                     p.p(", ");
1005                 p.p(paramTypes[j] + ".class");
1006             }
1007             p.pln("});");
1008         }
1009     }
1010 
1011 
1012     /*
1013      * Following are a series of static utility methods useful during
1014      * the code generation process:
1015      */
1016 
1017     /**
1018      * Generate an array of names for fields that correspond to the given
1019      * array of remote methods.  Each name in the returned array is
1020      * guaranteed to be unique.
1021      *
1022      * The name of a method is included in its corresponding field name
1023      * to enhance readability of the generated code.
1024      */
nameMethodFields(RemoteClass.Method[] methods)1025     private static String[] nameMethodFields(RemoteClass.Method[] methods) {
1026         String[] names = new String[methods.length];
1027         for (int i = 0; i < names.length; i++) {
1028             names[i] = "$method_" + methods[i].getName() + "_" + i;
1029         }
1030         return names;
1031     }
1032 
1033     /**
1034      * Generate an array of names for parameters corresponding to the
1035      * given array of types for the parameters.  Each name in the returned
1036      * array is guaranteed to be unique.
1037      *
1038      * A representation of the type of a parameter is included in its
1039      * corresponding field name to enhance the readability of the generated
1040      * code.
1041      */
nameParameters(Type[] types)1042     private static String[] nameParameters(Type[] types) {
1043         String[] names = new String[types.length];
1044         for (int i = 0; i < names.length; i++) {
1045             names[i] = "$param_" +
1046                 generateNameFromType(types[i]) + "_" + (i + 1);
1047         }
1048         return names;
1049     }
1050 
1051     /**
1052      * Generate a readable string representing the given type suitable
1053      * for embedding within a Java identifier.
1054      */
generateNameFromType(Type type)1055     private static String generateNameFromType(Type type) {
1056         int typeCode = type.getTypeCode();
1057         switch (typeCode) {
1058         case TC_BOOLEAN:
1059         case TC_BYTE:
1060         case TC_CHAR:
1061         case TC_SHORT:
1062         case TC_INT:
1063         case TC_LONG:
1064         case TC_FLOAT:
1065         case TC_DOUBLE:
1066             return type.toString();
1067         case TC_ARRAY:
1068             return "arrayOf_" + generateNameFromType(type.getElementType());
1069         case TC_CLASS:
1070             return Names.mangleClass(type.getClassName().getName()).toString();
1071         default:
1072             throw new Error("unexpected type code: " + typeCode);
1073         }
1074     }
1075 
1076     /**
1077      * Write a snippet of Java code to marshal a value named "name" of
1078      * type "type" to the java.io.ObjectOutput stream named "stream".
1079      *
1080      * Primitive types are marshalled with their corresponding methods
1081      * in the java.io.DataOutput interface, and objects (including arrays)
1082      * are marshalled using the writeObject method.
1083      */
writeMarshalArgument(IndentingWriter p, String streamName, Type type, String name)1084     private static void writeMarshalArgument(IndentingWriter p,
1085                                              String streamName,
1086                                              Type type, String name)
1087         throws IOException
1088     {
1089         int typeCode = type.getTypeCode();
1090         switch (typeCode) {
1091         case TC_BOOLEAN:
1092             p.p(streamName + ".writeBoolean(" + name + ")");
1093             break;
1094         case TC_BYTE:
1095             p.p(streamName + ".writeByte(" + name + ")");
1096             break;
1097         case TC_CHAR:
1098             p.p(streamName + ".writeChar(" + name + ")");
1099             break;
1100         case TC_SHORT:
1101             p.p(streamName + ".writeShort(" + name + ")");
1102             break;
1103         case TC_INT:
1104             p.p(streamName + ".writeInt(" + name + ")");
1105             break;
1106         case TC_LONG:
1107             p.p(streamName + ".writeLong(" + name + ")");
1108             break;
1109         case TC_FLOAT:
1110             p.p(streamName + ".writeFloat(" + name + ")");
1111             break;
1112         case TC_DOUBLE:
1113             p.p(streamName + ".writeDouble(" + name + ")");
1114             break;
1115         case TC_ARRAY:
1116         case TC_CLASS:
1117             p.p(streamName + ".writeObject(" + name + ")");
1118             break;
1119         default:
1120             throw new Error("unexpected type code: " + typeCode);
1121         }
1122     }
1123 
1124     /**
1125      * Write Java statements to marshal a series of values in order as
1126      * named in the "names" array, with types as specified in the "types"
1127      * array", to the java.io.ObjectOutput stream named "stream".
1128      */
writeMarshalArguments(IndentingWriter p, String streamName, Type[] types, String[] names)1129     private static void writeMarshalArguments(IndentingWriter p,
1130                                               String streamName,
1131                                               Type[] types, String[] names)
1132         throws IOException
1133     {
1134         if (types.length != names.length) {
1135             throw new Error("parameter type and name arrays different sizes");
1136         }
1137 
1138         for (int i = 0; i < types.length; i++) {
1139             writeMarshalArgument(p, streamName, types[i], names[i]);
1140             p.pln(";");
1141         }
1142     }
1143 
1144     /**
1145      * Write a snippet of Java code to unmarshal a value of type "type"
1146      * from the java.io.ObjectInput stream named "stream" into a variable
1147      * named "name" (if "name" is null, the value in unmarshalled and
1148      * discarded).
1149      *
1150      * Primitive types are unmarshalled with their corresponding methods
1151      * in the java.io.DataInput interface, and objects (including arrays)
1152      * are unmarshalled using the readObject method.
1153      */
writeUnmarshalArgument(IndentingWriter p, String streamName, Type type, String name)1154     private static boolean writeUnmarshalArgument(IndentingWriter p,
1155                                                   String streamName,
1156                                                   Type type, String name)
1157         throws IOException
1158     {
1159         boolean readObject = false;
1160 
1161         if (name != null) {
1162             p.p(name + " = ");
1163         }
1164 
1165         int typeCode = type.getTypeCode();
1166         switch (type.getTypeCode()) {
1167         case TC_BOOLEAN:
1168             p.p(streamName + ".readBoolean()");
1169             break;
1170         case TC_BYTE:
1171             p.p(streamName + ".readByte()");
1172             break;
1173         case TC_CHAR:
1174             p.p(streamName + ".readChar()");
1175             break;
1176         case TC_SHORT:
1177             p.p(streamName + ".readShort()");
1178             break;
1179         case TC_INT:
1180             p.p(streamName + ".readInt()");
1181             break;
1182         case TC_LONG:
1183             p.p(streamName + ".readLong()");
1184             break;
1185         case TC_FLOAT:
1186             p.p(streamName + ".readFloat()");
1187             break;
1188         case TC_DOUBLE:
1189             p.p(streamName + ".readDouble()");
1190             break;
1191         case TC_ARRAY:
1192         case TC_CLASS:
1193             p.p("(" + type + ") " + streamName + ".readObject()");
1194             readObject = true;
1195             break;
1196         default:
1197             throw new Error("unexpected type code: " + typeCode);
1198         }
1199         return readObject;
1200     }
1201 
1202     /**
1203      * Write Java statements to unmarshal a series of values in order of
1204      * types as in the "types" array from the java.io.ObjectInput stream
1205      * named "stream" into variables as named in "names" (for any element
1206      * of "names" that is null, the corresponding value is unmarshalled
1207      * and discarded).
1208      */
writeUnmarshalArguments(IndentingWriter p, String streamName, Type[] types, String[] names)1209     private static boolean writeUnmarshalArguments(IndentingWriter p,
1210                                                    String streamName,
1211                                                    Type[] types,
1212                                                    String[] names)
1213         throws IOException
1214     {
1215         if (types.length != names.length) {
1216             throw new Error("parameter type and name arrays different sizes");
1217         }
1218 
1219         boolean readObject = false;
1220         for (int i = 0; i < types.length; i++) {
1221             if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
1222                 readObject = true;
1223             }
1224             p.pln(";");
1225         }
1226         return readObject;
1227     }
1228 
1229     /**
1230      * Return a snippet of Java code to wrap a value named "name" of
1231      * type "type" into an object as appropriate for use by the
1232      * Java Reflection API.
1233      *
1234      * For primitive types, an appropriate wrapper class instantiated
1235      * with the primitive value.  For object types (including arrays),
1236      * no wrapping is necessary, so the value is named directly.
1237      */
wrapArgumentCode(Type type, String name)1238     private static String wrapArgumentCode(Type type, String name) {
1239         int typeCode = type.getTypeCode();
1240         switch (typeCode) {
1241         case TC_BOOLEAN:
1242             return ("(" + name +
1243                     " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
1244         case TC_BYTE:
1245             return "new java.lang.Byte(" + name + ")";
1246         case TC_CHAR:
1247             return "new java.lang.Character(" + name + ")";
1248         case TC_SHORT:
1249             return "new java.lang.Short(" + name + ")";
1250         case TC_INT:
1251             return "new java.lang.Integer(" + name + ")";
1252         case TC_LONG:
1253             return "new java.lang.Long(" + name + ")";
1254         case TC_FLOAT:
1255             return "new java.lang.Float(" + name + ")";
1256         case TC_DOUBLE:
1257             return "new java.lang.Double(" + name + ")";
1258         case TC_ARRAY:
1259         case TC_CLASS:
1260             return name;
1261         default:
1262             throw new Error("unexpected type code: " + typeCode);
1263         }
1264     }
1265 
1266     /**
1267      * Return a snippet of Java code to unwrap a value named "name" into
1268      * a value of type "type", as appropriate for the Java Reflection API.
1269      *
1270      * For primitive types, the value is assumed to be of the corresponding
1271      * wrapper type, and a method is called on the wrapper type to retrieve
1272      * the primitive value.  For object types (include arrays), no
1273      * unwrapping is necessary; the value is simply cast to the expected
1274      * real object type.
1275      */
unwrapArgumentCode(Type type, String name)1276     private static String unwrapArgumentCode(Type type, String name) {
1277         int typeCode = type.getTypeCode();
1278         switch (typeCode) {
1279         case TC_BOOLEAN:
1280             return "((java.lang.Boolean) " + name + ").booleanValue()";
1281         case TC_BYTE:
1282             return "((java.lang.Byte) " + name + ").byteValue()";
1283         case TC_CHAR:
1284             return "((java.lang.Character) " + name + ").charValue()";
1285         case TC_SHORT:
1286             return "((java.lang.Short) " + name + ").shortValue()";
1287         case TC_INT:
1288             return "((java.lang.Integer) " + name + ").intValue()";
1289         case TC_LONG:
1290             return "((java.lang.Long) " + name + ").longValue()";
1291         case TC_FLOAT:
1292             return "((java.lang.Float) " + name + ").floatValue()";
1293         case TC_DOUBLE:
1294             return "((java.lang.Double) " + name + ").doubleValue()";
1295         case TC_ARRAY:
1296         case TC_CLASS:
1297             return "((" + type + ") " + name + ")";
1298         default:
1299             throw new Error("unexpected type code: " + typeCode);
1300         }
1301     }
1302 }
1303