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