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