1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /*****************************************************************************/ 27 /* Copyright (c) IBM Corporation 1998 */ 28 /* */ 29 /* (C) Copyright IBM Corp. 1998 */ 30 /* */ 31 /*****************************************************************************/ 32 33 package sun.rmi.rmic; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.OutputStreamWriter; 38 import java.io.IOException; 39 import java.util.Enumeration; 40 import java.util.Hashtable; 41 import java.util.Vector; 42 import sun.tools.java.Type; 43 import sun.tools.java.Identifier; 44 import sun.tools.java.ClassDefinition; 45 import sun.tools.java.ClassDeclaration; 46 import sun.tools.java.ClassNotFound; 47 import sun.tools.java.ClassFile; 48 import sun.tools.java.MemberDefinition; 49 import com.sun.corba.se.impl.util.Utility; 50 51 /** 52 * A Generator object will generate the Java source code of the stub 53 * and skeleton classes for an RMI remote implementation class, using 54 * a particular stub protocol version. 55 * 56 * WARNING: The contents of this source file are not part of any 57 * supported API. Code that depends on them does so at its own risk: 58 * they are subject to change or removal without notice. 59 * 60 * @author Peter Jones, Bryan Atsatt 61 */ 62 public class RMIGenerator implements RMIConstants, Generator { 63 64 private static final Hashtable<String, Integer> versionOptions = new Hashtable<>(); 65 static { 66 versionOptions.put("-v1.1", 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 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 = 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 }