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      */
  97     public Main(OutputStream out, String program) {
  98         this.out = out;
  99         this.program = program;
 100     }
 101 
 102     /**
 103      * Output a message.
 104      */
 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      */
 116     public void error(String msg) {
 117         output(getText(msg));
 118     }
 119 
 120     public void error(String msg, String arg1) {
 121         output(getText(msg, arg1));
 122     }
 123 
 124     public void error(String msg, String arg1, String arg2) {
 125         output(getText(msg, arg1, arg2));
 126     }
 127 
 128     /**
 129      * Usage
 130      */
 131     public void usage() {
 132         error("rmic.usage", program);
 133     }
 134 
 135     /**
 136      * Run the compiler
 137      */
 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      */
 164     public File getDestinationDir() {
 165         return destDir;
 166     }
 167 
 168     /**
 169      * Parse the arguments for compile.
 170      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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"})
 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      */
 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      */
 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 
 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 
 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 
 829     public static String getText(String key, int num) {
 830         return getText(key, Integer.toString(num), null, null);
 831     }
 832 
 833     public static String getText(String key, String arg0) {
 834         return getText(key, arg0, null, null);
 835     }
 836 
 837     public static String getText(String key, String arg0, String arg1) {
 838         return getText(key, arg0, arg1, null);
 839     }
 840 
 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 }