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