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