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