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 }