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
33package sun.rmi.rmic;
34
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.OutputStreamWriter;
38import java.io.IOException;
39import java.util.Enumeration;
40import java.util.Hashtable;
41import java.util.Vector;
42import sun.tools.java.Type;
43import sun.tools.java.Identifier;
44import sun.tools.java.ClassDefinition;
45import sun.tools.java.ClassDeclaration;
46import sun.tools.java.ClassNotFound;
47import sun.tools.java.ClassFile;
48import sun.tools.java.MemberDefinition;
49import 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 */
62public class RMIGenerator implements RMIConstants, Generator {
63
64    private static final Hashtable<String, Integer> versionOptions = new Hashtable<>();
65    static {
66        versionOptions.put("-v1.1", STUB_VERSION_1_1);
67        versionOptions.put("-vcompat", STUB_VERSION_FAT);
68        versionOptions.put("-v1.2", 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 main 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 = 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     */
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(ClassFile.newClassFile(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(ClassFile.newClassFile(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<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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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