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