1 /* 2 * Copyright (c) 1996, 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 * Licensed Materials - Property of IBM 28 * RMI-IIOP v1.0 29 * Copyright IBM Corp. 1998 1999 All Rights Reserved 30 * 31 */ 32 33 package sun.rmi.rmic; 34 35 import java.util.Vector; 36 import java.util.Enumeration; 37 import java.util.ResourceBundle; 38 import java.util.StringTokenizer; 39 import java.util.MissingResourceException; 40 41 import java.io.OutputStream; 42 import java.io.PrintStream; 43 import java.io.IOException; 44 import java.io.File; 45 import java.io.FileNotFoundException; 46 import java.io.FileOutputStream; 47 import java.io.ByteArrayOutputStream; 48 49 import sun.tools.java.ClassFile; 50 import sun.tools.java.ClassDefinition; 51 import sun.tools.java.ClassDeclaration; 52 import sun.tools.java.ClassNotFound; 53 import sun.tools.java.Identifier; 54 import sun.tools.java.ClassPath; 55 56 import sun.tools.javac.SourceClass; 57 import sun.tools.util.CommandLine; 58 import java.lang.reflect.Constructor; 59 import java.util.Properties; 60 61 /** 62 * Main "rmic" program. 63 * 64 * WARNING: The contents of this source file are not part of any 65 * supported API. Code that depends on them does so at its own risk: 66 * they are subject to change or removal without notice. 67 */ 68 public class Main implements sun.rmi.rmic.Constants { 69 String sourcePathArg; 70 String sysClassPathArg; 71 String extDirsArg; 72 String classPathString; 73 File destDir; 74 int flags; 75 long tm; 76 Vector<String> classes; 77 boolean nowrite; 78 boolean nocompile; 79 boolean keepGenerated; 80 boolean status; 81 String[] generatorArgs; 82 Vector<Generator> generators; 83 Class<? extends BatchEnvironment> environmentClass = 84 BatchEnvironment.class; 85 boolean iiopGeneration = false; 86 87 /** 88 * Name of the program. 89 */ 90 String program; 91 92 /** 93 * The stream where error message are printed. 94 */ 95 OutputStream out; 96 97 /** 98 * Constructor. 99 */ 100 public Main(OutputStream out, String program) { 101 this.out = out; 102 this.program = program; 103 } 104 105 /** 106 * Output a message. 107 */ 108 public void output(String msg) { 109 PrintStream out = 110 this.out instanceof PrintStream ? (PrintStream)this.out 111 : new PrintStream(this.out, true); 112 out.println(msg); 113 } 114 115 /** 116 * Top level error message. This method is called when the 117 * environment could not be set up yet. 118 */ 119 public void error(String msg) { 120 output(getText(msg)); 121 } 122 123 public void error(String msg, String arg1) { 124 output(getText(msg, arg1)); 125 } 126 127 public void error(String msg, String arg1, String arg2) { 128 output(getText(msg, arg1, arg2)); 129 } 130 131 /** 132 * Usage 133 */ 134 public void usage() { 135 error("rmic.usage", program); 136 } 137 138 /** 139 * Run the compiler 140 */ 141 public synchronized boolean compile(String argv[]) { 142 143 /* 144 * Handle internal option to use the new (and incomplete) rmic 145 * implementation. This option is handled here, rather than 146 * in parseArgs, so that none of the arguments will be nulled 147 * before delegating to the new implementation. 148 */ 149 for (int i = 0; i < argv.length; i++) { 150 if (argv[i].equals("-Xnew")) { 151 return (new sun.rmi.rmic.newrmic.Main(out, 152 program)).compile(argv); 153 } 154 } 155 156 if (!parseArgs(argv)) { 157 return false; 158 } 159 160 if (classes.size() == 0) { 161 usage(); 162 return false; 163 } 164 165 if ((flags & F_WARNINGS) != 0) { 166 for (Generator g : generators) { 167 if (g instanceof RMIGenerator) { 168 error("rmic.jrmp.stubs.deprecated", program); 169 break; 170 } 171 } 172 } 173 174 return doCompile(); 175 } 176 177 /** 178 * Get the destination directory. 179 */ 180 public File getDestinationDir() { 181 return destDir; 182 } 183 184 /** 185 * Parse the arguments for compile. 186 */ 187 public boolean parseArgs(String argv[]) { 188 sourcePathArg = null; 189 sysClassPathArg = null; 190 extDirsArg = null; 191 192 classPathString = null; 193 destDir = null; 194 flags = F_WARNINGS; 195 tm = System.currentTimeMillis(); 196 classes = new Vector<>(); 197 nowrite = false; 198 nocompile = false; 199 keepGenerated = false; 200 generatorArgs = getArray("generator.args",true); 201 if (generatorArgs == null) { 202 return false; 203 } 204 generators = new Vector<>(); 205 206 // Pre-process command line for @file arguments 207 try { 208 argv = CommandLine.parse(argv); 209 } catch (FileNotFoundException e) { 210 error("rmic.cant.read", e.getMessage()); 211 return false; 212 } catch (IOException e) { 213 e.printStackTrace(out instanceof PrintStream ? 214 (PrintStream) out : 215 new PrintStream(out, true)); 216 return false; 217 } 218 219 // Parse arguments 220 for (int i = 0 ; i < argv.length ; i++) { 221 if (argv[i] != null) { 222 if (argv[i].equals("-g")) { 223 flags &= ~F_OPT; 224 flags |= F_DEBUG_LINES | F_DEBUG_VARS; 225 argv[i] = null; 226 } else if (argv[i].equals("-O")) { 227 flags &= ~F_DEBUG_LINES; 228 flags &= ~F_DEBUG_VARS; 229 flags |= F_OPT | F_DEPENDENCIES; 230 argv[i] = null; 231 } else if (argv[i].equals("-nowarn")) { 232 flags &= ~F_WARNINGS; 233 argv[i] = null; 234 } else if (argv[i].equals("-debug")) { 235 flags |= F_DUMP; 236 argv[i] = null; 237 } else if (argv[i].equals("-depend")) { 238 flags |= F_DEPENDENCIES; 239 argv[i] = null; 240 } else if (argv[i].equals("-verbose")) { 241 flags |= F_VERBOSE; 242 argv[i] = null; 243 } else if (argv[i].equals("-nowrite")) { 244 nowrite = true; 245 argv[i] = null; 246 } else if (argv[i].equals("-Xnocompile")) { 247 nocompile = true; 248 keepGenerated = true; 249 argv[i] = null; 250 } else if (argv[i].equals("-keep") || 251 argv[i].equals("-keepgenerated")) { 252 keepGenerated = true; 253 argv[i] = null; 254 } else if (argv[i].equals("-show")) { 255 error("rmic.option.unsupported", "-show"); 256 usage(); 257 return false; 258 } else if (argv[i].equals("-classpath")) { 259 if ((i + 1) < argv.length) { 260 if (classPathString != null) { 261 error("rmic.option.already.seen", "-classpath"); 262 usage(); 263 return false; 264 } 265 argv[i] = null; 266 classPathString = argv[++i]; 267 argv[i] = null; 268 } else { 269 error("rmic.option.requires.argument", "-classpath"); 270 usage(); 271 return false; 272 } 273 } else if (argv[i].equals("-sourcepath")) { 274 if ((i + 1) < argv.length) { 275 if (sourcePathArg != null) { 276 error("rmic.option.already.seen", "-sourcepath"); 277 usage(); 278 return false; 279 } 280 argv[i] = null; 281 sourcePathArg = argv[++i]; 282 argv[i] = null; 283 } else { 284 error("rmic.option.requires.argument", "-sourcepath"); 285 usage(); 286 return false; 287 } 288 } else if (argv[i].equals("-bootclasspath")) { 289 if ((i + 1) < argv.length) { 290 if (sysClassPathArg != null) { 291 error("rmic.option.already.seen", "-bootclasspath"); 292 usage(); 293 return false; 294 } 295 argv[i] = null; 296 sysClassPathArg = argv[++i]; 297 argv[i] = null; 298 } else { 299 error("rmic.option.requires.argument", "-bootclasspath"); 300 usage(); 301 return false; 302 } 303 } else if (argv[i].equals("-extdirs")) { 304 if ((i + 1) < argv.length) { 305 if (extDirsArg != null) { 306 error("rmic.option.already.seen", "-extdirs"); 307 usage(); 308 return false; 309 } 310 argv[i] = null; 311 extDirsArg = argv[++i]; 312 argv[i] = null; 313 } else { 314 error("rmic.option.requires.argument", "-extdirs"); 315 usage(); 316 return false; 317 } 318 } else if (argv[i].equals("-d")) { 319 if ((i + 1) < argv.length) { 320 if (destDir != null) { 321 error("rmic.option.already.seen", "-d"); 322 usage(); 323 return false; 324 } 325 argv[i] = null; 326 destDir = new File(argv[++i]); 327 argv[i] = null; 328 if (!destDir.exists()) { 329 error("rmic.no.such.directory", destDir.getPath()); 330 usage(); 331 return false; 332 } 333 } else { 334 error("rmic.option.requires.argument", "-d"); 335 usage(); 336 return false; 337 } 338 } else { 339 if (!checkGeneratorArg(argv,i)) { 340 usage(); 341 return false; 342 } 343 } 344 } 345 } 346 347 348 // Now that all generators have had a chance at the args, 349 // scan what's left for classes and illegal args... 350 351 for (int i = 0; i < argv.length; i++) { 352 if (argv[i] != null) { 353 if (argv[i].startsWith("-")) { 354 error("rmic.no.such.option", argv[i]); 355 usage(); 356 return false; 357 } else { 358 classes.addElement(argv[i]); 359 } 360 } 361 } 362 363 364 // If the generators vector is empty, add the default generator... 365 366 if (generators.size() == 0) { 367 addGenerator("default"); 368 } 369 370 return true; 371 } 372 373 /** 374 * If this argument is for a generator, instantiate it, call 375 * parseArgs(...) and add generator to generators vector. 376 * Returns false on error. 377 */ 378 protected boolean checkGeneratorArg(String[] argv, int currentIndex) { 379 boolean result = true; 380 if (argv[currentIndex].startsWith("-")) { 381 String arg = argv[currentIndex].substring(1).toLowerCase(); // Remove '-' 382 for (int i = 0; i < generatorArgs.length; i++) { 383 if (arg.equalsIgnoreCase(generatorArgs[i])) { 384 // Got a match, add Generator and call parseArgs... 385 Generator gen = addGenerator(arg); 386 if (gen == null) { 387 return false; 388 } 389 result = gen.parseArgs(argv,this); 390 break; 391 } 392 } 393 } 394 return result; 395 } 396 397 /** 398 * Instantiate and add a generator to the generators array. 399 */ 400 protected Generator addGenerator(String arg) { 401 402 Generator gen; 403 404 // Create an instance of the generator and add it to 405 // the array... 406 407 String className = getString("generator.class." + arg); 408 if (className == null) { 409 error("rmic.missing.property",arg); 410 return null; 411 } 412 413 try { 414 gen = (Generator) Class.forName(className).newInstance(); 415 } catch (Exception e) { 416 error("rmic.cannot.instantiate",className); 417 return null; 418 } 419 420 generators.addElement(gen); 421 422 // Get the environment required by this generator... 423 424 Class<?> envClass = BatchEnvironment.class; 425 String env = getString("generator.env." + arg); 426 if (env != null) { 427 try { 428 envClass = Class.forName(env); 429 430 // Is the new class a subclass of the current one? 431 432 if (environmentClass.isAssignableFrom(envClass)) { 433 434 // Yes, so switch to the new one... 435 436 environmentClass = envClass.asSubclass(BatchEnvironment.class); 437 438 } else { 439 440 // No. Is the current class a subclass of the 441 // new one? 442 443 if (!envClass.isAssignableFrom(environmentClass)) { 444 445 // No, so it's a conflict... 446 447 error("rmic.cannot.use.both",environmentClass.getName(),envClass.getName()); 448 return null; 449 } 450 } 451 } catch (ClassNotFoundException e) { 452 error("rmic.class.not.found",env); 453 return null; 454 } 455 } 456 457 // If this is the iiop stub generator, cache 458 // that fact for the jrmp generator... 459 460 if (arg.equals("iiop")) { 461 iiopGeneration = true; 462 } 463 return gen; 464 } 465 466 /** 467 * Grab a resource string and parse it into an array of strings. Assumes 468 * comma separated list. 469 * @param name The resource name. 470 * @param mustExist If true, throws error if resource does not exist. If 471 * false and resource does not exist, returns zero element array. 472 */ 473 protected String[] getArray(String name, boolean mustExist) { 474 String[] result = null; 475 String value = getString(name); 476 if (value == null) { 477 if (mustExist) { 478 error("rmic.resource.not.found",name); 479 return null; 480 } else { 481 return new String[0]; 482 } 483 } 484 485 StringTokenizer parser = new StringTokenizer(value,", \t\n\r", false); 486 int count = parser.countTokens(); 487 result = new String[count]; 488 for (int i = 0; i < count; i++) { 489 result[i] = parser.nextToken(); 490 } 491 492 return result; 493 } 494 495 /** 496 * Get the correct type of BatchEnvironment 497 */ 498 public BatchEnvironment getEnv() { 499 500 ClassPath classPath = 501 BatchEnvironment.createClassPath(classPathString, 502 sysClassPathArg, 503 extDirsArg); 504 BatchEnvironment result = null; 505 try { 506 Class<?>[] ctorArgTypes = {OutputStream.class,ClassPath.class,Main.class}; 507 Object[] ctorArgs = {out,classPath,this}; 508 Constructor<? extends BatchEnvironment> constructor = 509 environmentClass.getConstructor(ctorArgTypes); 510 result = constructor.newInstance(ctorArgs); 511 result.reset(); 512 } 513 catch (Exception e) { 514 error("rmic.cannot.instantiate",environmentClass.getName()); 515 } 516 return result; 517 } 518 519 520 /** 521 * Do the compile with the switches and files already supplied 522 */ 523 public boolean doCompile() { 524 // Create batch environment 525 BatchEnvironment env = getEnv(); 526 env.flags |= flags; 527 528 // Set the classfile version numbers 529 // Compat and 1.1 stubs must retain the old version number. 530 env.majorVersion = 45; 531 env.minorVersion = 3; 532 533 // Preload the "out of memory" error string just in case we run 534 // out of memory during the compile. 535 String noMemoryErrorString = getText("rmic.no.memory"); 536 String stackOverflowErrorString = getText("rmic.stack.overflow"); 537 538 try { 539 /** Load the classes on the command line 540 * Replace the entries in classes with the ClassDefinition for the class 541 */ 542 for (int i = classes.size()-1; i >= 0; i-- ) { 543 Identifier implClassName = 544 Identifier.lookup(classes.elementAt(i)); 545 546 /* 547 * Fix bugid 4049354: support using '.' as an inner class 548 * qualifier on the command line (previously, only mangled 549 * inner class names were understood, like "pkg.Outer$Inner"). 550 * 551 * The following method, also used by "javap", resolves the 552 * given unmangled inner class name to the appropriate 553 * internal identifier. For example, it translates 554 * "pkg.Outer.Inner" to "pkg.Outer. Inner". 555 */ 556 implClassName = env.resolvePackageQualifiedName(implClassName); 557 /* 558 * But if we use such an internal inner class name identifier 559 * to load the class definition, the Java compiler will notice 560 * if the impl class is a "private" inner class and then deny 561 * skeletons (needed unless "-v1.2" is used) the ability to 562 * cast to it. To work around this problem, we mangle inner 563 * class name identifiers to their binary "outer" class name: 564 * "pkg.Outer. Inner" becomes "pkg.Outer$Inner". 565 */ 566 implClassName = Names.mangleClass(implClassName); 567 568 ClassDeclaration decl = env.getClassDeclaration(implClassName); 569 try { 570 ClassDefinition def = decl.getClassDefinition(env); 571 for (int j = 0; j < generators.size(); j++) { 572 Generator gen = generators.elementAt(j); 573 gen.generate(env, def, destDir); 574 } 575 } catch (ClassNotFound ex) { 576 env.error(0, "rmic.class.not.found", implClassName); 577 } 578 579 } 580 581 // compile all classes that need compilation 582 if (!nocompile) { 583 compileAllClasses(env); 584 } 585 } catch (OutOfMemoryError ee) { 586 // The compiler has run out of memory. Use the error string 587 // which we preloaded. 588 env.output(noMemoryErrorString); 589 return false; 590 } catch (StackOverflowError ee) { 591 env.output(stackOverflowErrorString); 592 return false; 593 } catch (Error ee) { 594 // We allow the compiler to take an exception silently if a program 595 // error has previously been detected. Presumably, this makes the 596 // compiler more robust in the face of bad error recovery. 597 if (env.nerrors == 0 || env.dump()) { 598 env.error(0, "fatal.error"); 599 ee.printStackTrace(out instanceof PrintStream ? 600 (PrintStream) out : 601 new PrintStream(out, true)); 602 } 603 } catch (Exception ee) { 604 if (env.nerrors == 0 || env.dump()) { 605 env.error(0, "fatal.exception"); 606 ee.printStackTrace(out instanceof PrintStream ? 607 (PrintStream) out : 608 new PrintStream(out, true)); 609 } 610 } 611 612 env.flushErrors(); 613 614 boolean status = true; 615 if (env.nerrors > 0) { 616 String msg = ""; 617 if (env.nerrors > 1) { 618 msg = getText("rmic.errors", env.nerrors); 619 } else { 620 msg = getText("rmic.1error"); 621 } 622 if (env.nwarnings > 0) { 623 if (env.nwarnings > 1) { 624 msg += ", " + getText("rmic.warnings", env.nwarnings); 625 } else { 626 msg += ", " + getText("rmic.1warning"); 627 } 628 } 629 output(msg); 630 status = false; 631 } else { 632 if (env.nwarnings > 0) { 633 if (env.nwarnings > 1) { 634 output(getText("rmic.warnings", env.nwarnings)); 635 } else { 636 output(getText("rmic.1warning")); 637 } 638 } 639 } 640 641 // last step is to delete generated source files 642 if (!keepGenerated) { 643 env.deleteGeneratedFiles(); 644 } 645 646 // We're done 647 if (env.verbose()) { 648 tm = System.currentTimeMillis() - tm; 649 output(getText("rmic.done_in", Long.toString(tm))); 650 } 651 652 // Shutdown the environment object and release our resources. 653 // Note that while this is unneccessary when rmic is invoked 654 // the command line, there are environments in which rmic 655 // from is invoked within a server process, so resource 656 // reclamation is important... 657 658 env.shutdown(); 659 660 sourcePathArg = null; 661 sysClassPathArg = null; 662 extDirsArg = null; 663 classPathString = null; 664 destDir = null; 665 classes = null; 666 generatorArgs = null; 667 generators = null; 668 environmentClass = null; 669 program = null; 670 out = null; 671 672 return status; 673 } 674 675 /* 676 * Compile all classes that need to be compiled. 677 */ 678 public void compileAllClasses (BatchEnvironment env) 679 throws ClassNotFound, 680 IOException, 681 InterruptedException { 682 ByteArrayOutputStream buf = new ByteArrayOutputStream(4096); 683 boolean done; 684 685 do { 686 done = true; 687 for (Enumeration<?> e = env.getClasses() ; e.hasMoreElements() ; ) { 688 ClassDeclaration c = (ClassDeclaration)e.nextElement(); 689 done = compileClass(c,buf,env); 690 } 691 } while (!done); 692 } 693 694 /* 695 * Compile a single class. 696 * Fallthrough is intentional 697 */ 698 @SuppressWarnings("fallthrough") 699 public boolean compileClass (ClassDeclaration c, 700 ByteArrayOutputStream buf, 701 BatchEnvironment env) 702 throws ClassNotFound, 703 IOException, 704 InterruptedException { 705 boolean done = true; 706 env.flushErrors(); 707 SourceClass src; 708 709 switch (c.getStatus()) { 710 case CS_UNDEFINED: 711 { 712 if (!env.dependencies()) { 713 break; 714 } 715 // fall through 716 } 717 718 case CS_SOURCE: 719 { 720 done = false; 721 env.loadDefinition(c); 722 if (c.getStatus() != CS_PARSED) { 723 break; 724 } 725 // fall through 726 } 727 728 case CS_PARSED: 729 { 730 if (c.getClassDefinition().isInsideLocal()) { 731 break; 732 } 733 // If we get to here, then compilation is going 734 // to occur. If the -Xnocompile switch is set 735 // then fail. Note that this check is required 736 // here because this method is called from 737 // generators, not just from within this class... 738 739 if (nocompile) { 740 throw new IOException("Compilation required, but -Xnocompile option in effect"); 741 } 742 743 done = false; 744 745 src = (SourceClass)c.getClassDefinition(env); 746 src.check(env); 747 c.setDefinition(src, CS_CHECKED); 748 // fall through 749 } 750 751 case CS_CHECKED: 752 { 753 src = (SourceClass)c.getClassDefinition(env); 754 // bail out if there were any errors 755 if (src.getError()) { 756 c.setDefinition(src, CS_COMPILED); 757 break; 758 } 759 done = false; 760 buf.reset(); 761 src.compile(buf); 762 c.setDefinition(src, CS_COMPILED); 763 src.cleanup(env); 764 765 if (src.getError() || nowrite) { 766 break; 767 } 768 769 String pkgName = c.getName().getQualifier().toString().replace('.', File.separatorChar); 770 String className = c.getName().getFlatName().toString().replace('.', SIGC_INNERCLASS) + ".class"; 771 772 File file; 773 if (destDir != null) { 774 if (pkgName.length() > 0) { 775 file = new File(destDir, pkgName); 776 if (!file.exists()) { 777 file.mkdirs(); 778 } 779 file = new File(file, className); 780 } else { 781 file = new File(destDir, className); 782 } 783 } else { 784 ClassFile classfile = (ClassFile)src.getSource(); 785 if (classfile.isZipped()) { 786 env.error(0, "cant.write", classfile.getPath()); 787 break; 788 } 789 file = new File(classfile.getPath()); 790 file = new File(file.getParent(), className); 791 } 792 793 // Create the file 794 try { 795 FileOutputStream out = new FileOutputStream(file.getPath()); 796 buf.writeTo(out); 797 out.close(); 798 if (env.verbose()) { 799 output(getText("rmic.wrote", file.getPath())); 800 } 801 } catch (IOException ee) { 802 env.error(0, "cant.write", file.getPath()); 803 } 804 } 805 } 806 return done; 807 } 808 809 /** 810 * Main program 811 */ 812 public static void main(String argv[]) { 813 Main compiler = new Main(System.out, "rmic"); 814 System.exit(compiler.compile(argv) ? 0 : 1); 815 } 816 817 /** 818 * Return the string value of a named resource in the rmic.properties 819 * resource bundle. If the resource is not found, null is returned. 820 */ 821 public static String getString(String key) { 822 if (!resourcesInitialized) { 823 initResources(); 824 } 825 826 // To enable extensions, search the 'resourcesExt' 827 // bundle first, followed by the 'resources' bundle... 828 829 if (resourcesExt != null) { 830 try { 831 return resourcesExt.getString(key); 832 } catch (MissingResourceException e) {} 833 } 834 835 try { 836 return resources.getString(key); 837 } catch (MissingResourceException ignore) { 838 } 839 return null; 840 } 841 842 private static boolean resourcesInitialized = false; 843 private static ResourceBundle resources; 844 private static ResourceBundle resourcesExt = null; 845 846 private static void initResources() { 847 try { 848 resources = 849 ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic"); 850 resourcesInitialized = true; 851 try { 852 resourcesExt = 853 ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext"); 854 } catch (MissingResourceException e) {} 855 } catch (MissingResourceException e) { 856 throw new Error("fatal: missing resource bundle: " + 857 e.getClassName()); 858 } 859 } 860 861 public static String getText(String key) { 862 String message = getString(key); 863 if (message == null) { 864 message = "no text found: \"" + key + "\""; 865 } 866 return message; 867 } 868 869 public static String getText(String key, int num) { 870 return getText(key, Integer.toString(num), null, null); 871 } 872 873 public static String getText(String key, String arg0) { 874 return getText(key, arg0, null, null); 875 } 876 877 public static String getText(String key, String arg0, String arg1) { 878 return getText(key, arg0, arg1, null); 879 } 880 881 public static String getText(String key, 882 String arg0, String arg1, String arg2) 883 { 884 String format = getString(key); 885 if (format == null) { 886 format = "no text found: key = \"" + key + "\", " + 887 "arguments = \"{0}\", \"{1}\", \"{2}\""; 888 } 889 890 String[] args = new String[3]; 891 args[0] = (arg0 != null ? arg0 : "null"); 892 args[1] = (arg1 != null ? arg1 : "null"); 893 args[2] = (arg2 != null ? arg2 : "null"); 894 895 return java.text.MessageFormat.format(format, (Object[]) args); 896 } 897 }