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