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