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