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