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