public class

Main

extends Object
/*
 * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.rmi.rmic.newrmic;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.RootDoc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import sun.rmi.rmic.newrmic.jrmp.JrmpGenerator;
import sun.tools.util.CommandLine;

/**
 * The rmic front end.  This class contains the "main" method for rmic
 * command line invocation.
 *
 * A Main instance contains the stream to output error messages and
 * other diagnostics to.
 *
 * An rmic compilation batch (for example, one rmic command line
 * invocation) is executed by invoking the "compile" method of a Main
 * instance.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 *
 * NOTE: If and when there is a J2SE API for invoking SDK tools, this
 * class should be updated to support that API.
 *
 * NOTE: This class is the front end for a "new" rmic implementation,
 * which uses javadoc and the doclet API for reading class files and
 * javac for compiling generated source files.  This implementation is
 * incomplete: it lacks any CORBA-based back end implementations, and
 * thus the command line options "-idl", "-iiop", and their related
 * options are not yet supported.  The front end for the "old",
 * oldjavac-based rmic implementation is sun.rmi.rmic.Main.
 *
 * @author Peter Jones
 **/
public class Main {

    /*
     * Implementation note:
     *
     * In order to use the doclet API to read class files, much of
     * this implementation of rmic executes as a doclet within an
     * invocation of javadoc.  This class is used as the doclet class
     * for such javadoc invocations, via its static "start" and
     * "optionLength" methods.  There is one javadoc invocation per
     * rmic compilation batch.
     *
     * The only guaranteed way to pass data to a doclet through a
     * javadoc invocation is through doclet-specific options on the
     * javadoc "command line".  Rather than passing numerous pieces of
     * individual data in string form as javadoc options, we use a
     * single doclet-specific option ("-batchID") to pass a numeric
     * identifier that uniquely identifies the rmic compilation batch
     * that the javadoc invocation is for, and that identifier can
     * then be used as a key in a global table to retrieve an object
     * containing all of batch-specific data (rmic command line
     * arguments, etc.).
     */

    /** guards "batchCount" */
    private static final Object batchCountLock = new Object();

    /** number of batches run; used to generated batch IDs */
    private static long batchCount = 0;

    /** maps batch ID to batch data */
    private static final Map<Long,Batch> batchTable =
        Collections.synchronizedMap(new HashMap<Long,Batch>());

    /** stream to output error messages and other diagnostics to */
    private final PrintStream out;

    /** name of this program, to use in error messages */
    private final String program;

    /**
     * Command line entry point.
     **/
    public static void main(String[] args) {
        Main rmic = new Main(System.err, "rmic");
        System.exit(rmic.compile(args) ? 0 : 1);
    }

    /**
     * Creates a Main instance that writes output to the specified
     * stream.  The specified program name is used in error messages.
     **/
    public Main(OutputStream out, String program) {
        this.out = out instanceof PrintStream ?
            (PrintStream) out : new PrintStream(out);
        this.program = program;
    }

    /**
     * Compiles a batch of input classes, as given by the specified
     * command line arguments.  Protocol-specific generators are
     * determined by the choice options on the command line.  Returns
     * true if successful, or false if an error occurred.
     *
     * NOTE: This method is retained for transitional consistency with
     * previous implementations.
     **/
    public boolean compile(String[] args) {
        long startTime = System.currentTimeMillis();

        long batchID;
        synchronized (batchCountLock) {
            batchID = batchCount++;     // assign batch ID
        }

        // process command line
        Batch batch = parseArgs(args);
        if (batch == null) {
            return false;               // terminate if error occurred
        }

        /*
         * With the batch data retrievable in the global table, run
         * javadoc to continue the rest of the batch's compliation as
         * a doclet.
         */
        boolean status;
        try {
            batchTable.put(batchID, batch);
            status = invokeJavadoc(batch, batchID);
        } finally {
            batchTable.remove(batchID);
        }

        if (batch.verbose) {
            long deltaTime = System.currentTimeMillis() - startTime;
            output(Resources.getText("rmic.done_in",
                                     Long.toString(deltaTime)));
        }

        return status;
    }

    /**
     * Prints the specified string to the output stream of this Main
     * instance.
     **/
    public void output(String msg) {
        out.println(msg);
    }

    /**
     * Prints an error message to the output stream of this Main
     * instance.  The first argument is used as a key in rmic's
     * resource bundle, and the rest of the arguments are used as
     * arguments in the formatting of the resource string.
     **/
    public void error(String msg, String... args) {
        output(Resources.getText(msg, args));
    }

    /**
     * Prints rmic's usage message to the output stream of this Main
     * instance.
     *
     * This method is public so that it can be used by the "parseArgs"
     * methods of Generator implementations.
     **/
    public void usage() {
        error("rmic.usage", program);
    }

    /**
     * Processes rmic command line arguments.  Returns a Batch object
     * representing the command line arguments if successful, or null
     * if an error occurred.  Processed elements of the args array are
     * set to null.
     **/
    private Batch parseArgs(String[] args) {
        Batch batch = new Batch();

        /*
         * Pre-process command line for @file arguments.
         */
        try {
            args = CommandLine.parse(args);
        } catch (FileNotFoundException e) {
            error("rmic.cant.read", e.getMessage());
            return null;
        } catch (IOException e) {
            e.printStackTrace(out);
            return null;
        }

        for (int i = 0; i < args.length; i++) {

            if (args[i] == null) {
                // already processed by a generator
                continue;

            } else if (args[i].equals("-Xnew")) {
                // we're already using the "new" implementation
                args[i] = null;

            } else if (args[i].equals("-show")) {
                // obselete: fail
                error("rmic.option.unsupported", args[i]);
                usage();
                return null;

            } else if (args[i].equals("-O")) {
                // obselete: warn but tolerate
                error("rmic.option.unsupported", args[i]);
                args[i] = null;

            } else if (args[i].equals("-debug")) {
                // obselete: warn but tolerate
                error("rmic.option.unsupported", args[i]);
                args[i] = null;

            } else if (args[i].equals("-depend")) {
                // obselete: warn but tolerate
                // REMIND: should this fail instead?
                error("rmic.option.unsupported", args[i]);
                args[i] = null;

            } else if (args[i].equals("-keep") ||
                       args[i].equals("-keepgenerated"))
            {
                batch.keepGenerated = true;
                args[i] = null;

            } else if (args[i].equals("-g")) {
                batch.debug = true;
                args[i] = null;

            } else if (args[i].equals("-nowarn")) {
                batch.noWarn = true;
                args[i] = null;

            } else if (args[i].equals("-nowrite")) {
                batch.noWrite = true;
                args[i] = null;

            } else if (args[i].equals("-verbose")) {
                batch.verbose = true;
                args[i] = null;

            } else if (args[i].equals("-Xnocompile")) {
                batch.noCompile = true;
                batch.keepGenerated = true;
                args[i] = null;

            } else if (args[i].equals("-bootclasspath")) {
                if ((i + 1) >= args.length) {
                    error("rmic.option.requires.argument", args[i]);
                    usage();
                    return null;
                }
                if (batch.bootClassPath != null) {
                    error("rmic.option.already.seen", args[i]);
                    usage();
                    return null;
                }
                args[i] = null;
                batch.bootClassPath = args[++i];
                assert batch.bootClassPath != null;
                args[i] = null;

            } else if (args[i].equals("-extdirs")) {
                if ((i + 1) >= args.length) {
                    error("rmic.option.requires.argument", args[i]);
                    usage();
                    return null;
                }
                if (batch.extDirs != null) {
                    error("rmic.option.already.seen", args[i]);
                    usage();
                    return null;
                }
                args[i] = null;
                batch.extDirs = args[++i];
                assert batch.extDirs != null;
                args[i] = null;

            } else if (args[i].equals("-classpath")) {
                if ((i + 1) >= args.length) {
                    error("rmic.option.requires.argument", args[i]);
                    usage();
                    return null;
                }
                if (batch.classPath != null) {
                    error("rmic.option.already.seen", args[i]);
                    usage();
                    return null;
                }
                args[i] = null;
                batch.classPath = args[++i];
                assert batch.classPath != null;
                args[i] = null;

            } else if (args[i].equals("-d")) {
                if ((i + 1) >= args.length) {
                    error("rmic.option.requires.argument", args[i]);
                    usage();
                    return null;
                }
                if (batch.destDir != null) {
                    error("rmic.option.already.seen", args[i]);
                    usage();
                    return null;
                }
                args[i] = null;
                batch.destDir = new File(args[++i]);
                assert batch.destDir != null;
                args[i] = null;
                if (!batch.destDir.exists()) {
                    error("rmic.no.such.directory", batch.destDir.getPath());
                    usage();
                    return null;
                }

            } else if (args[i].equals("-v1.1") ||
                       args[i].equals("-vcompat") ||
                       args[i].equals("-v1.2"))
            {
                Generator gen = new JrmpGenerator();
                batch.generators.add(gen);
                // JrmpGenerator only requires base BatchEnvironment class
                if (!gen.parseArgs(args, this)) {
                    return null;
                }

            } else if (args[i].equalsIgnoreCase("-iiop")) {
                error("rmic.option.unimplemented", args[i]);
                return null;

                // Generator gen = new IiopGenerator();
                // batch.generators.add(gen);
                // if (!batch.envClass.isAssignableFrom(gen.envClass())) {
                //   error("rmic.cannot.use.both",
                //         batch.envClass.getName(), gen.envClass().getName());
                //   return null;
                // }
                // batch.envClass = gen.envClass();
                // if (!gen.parseArgs(args, this)) {
                //   return null;
                // }

            } else if (args[i].equalsIgnoreCase("-idl")) {
                error("rmic.option.unimplemented", args[i]);
                return null;

                // see implementation sketch above

            } else if (args[i].equalsIgnoreCase("-xprint")) {
                error("rmic.option.unimplemented", args[i]);
                return null;

                // see implementation sketch above
            }
        }

        /*
         * At this point, all that remains non-null in the args
         * array are input class names or illegal options.
         */
        for (int i = 0; i < args.length; i++) {
            if (args[i] != null) {
                if (args[i].startsWith("-")) {
                    error("rmic.no.such.option", args[i]);
                    usage();
                    return null;
                } else {
                    batch.classes.add(args[i]);
                }
            }
        }
        if (batch.classes.isEmpty()) {
            usage();
            return null;
        }

        /*
         * If options did not specify at least one protocol-specific
         * generator, then JRMP is the default.
         */
        if (batch.generators.isEmpty()) {
            batch.generators.add(new JrmpGenerator());
        }
        return batch;
    }

    /**
     * Doclet class entry point.
     **/
    public static boolean start(RootDoc rootDoc) {

        /*
         * Find batch ID among javadoc options, and retrieve
         * corresponding batch data from global table.
         */
        long batchID = -1;
        for (String[] option : rootDoc.options()) {
            if (option[0].equals("-batchID")) {
                try {
                    batchID = Long.parseLong(option[1]);
                } catch (NumberFormatException e) {
                    throw new AssertionError(e);
                }
            }
        }
        Batch batch = batchTable.get(batchID);
        assert batch != null;

        /*
         * Construct batch environment using class agreed upon by
         * generator implementations.
         */
        BatchEnvironment env;
        try {
            Constructor<? extends BatchEnvironment> cons =
                batch.envClass.getConstructor(new Class[] { RootDoc.class });
            env = cons.newInstance(rootDoc);
        } catch (NoSuchMethodException e) {
            throw new AssertionError(e);
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        } catch (InstantiationException e) {
            throw new AssertionError(e);
        } catch (InvocationTargetException e) {
            throw new AssertionError(e);
        }

        env.setVerbose(batch.verbose);

        /*
         * Determine the destination directory (the top of the package
         * hierarchy) for the output of this batch; if no destination
         * directory was specified on the command line, then the
         * default is the current working directory.
         */
        File destDir = batch.destDir;
        if (destDir == null) {
            destDir = new File(System.getProperty("user.dir"));
        }

        /*
         * Run each input class through each generator.
         */
        for (String inputClassName : batch.classes) {
            ClassDoc inputClass = rootDoc.classNamed(inputClassName);
            try {
                for (Generator gen : batch.generators) {
                    gen.generate(env, inputClass, destDir);
                }
            } catch (NullPointerException e) {
                /*
                 * We assume that this means that some class that was
                 * needed (perhaps even a bootstrap class) was not
                 * found, and that javadoc has already reported this
                 * as an error.  There is nothing for us to do here
                 * but try to continue with the next input class.
                 *
                 * REMIND: More explicit error checking throughout
                 * would be preferable, however.
                 */
            }
        }

        /*
         * Compile any generated source files, if configured to do so.
         */
        boolean status = true;
        List<File> generatedFiles = env.generatedFiles();
        if (!batch.noCompile && !batch.noWrite && !generatedFiles.isEmpty()) {
            status = batch.enclosingMain().invokeJavac(batch, generatedFiles);
        }

        /*
         * Delete any generated source files, if configured to do so.
         */
        if (!batch.keepGenerated) {
            for (File file : generatedFiles) {
                file.delete();
            }
        }

        return status;
    }

    /**
     * Doclet class method that indicates that this doclet class
     * recognizes (only) the "-batchID" option on the javadoc command
     * line, and that the "-batchID" option comprises two arguments on
     * the javadoc command line.
     **/
    public static int optionLength(String option) {
        if (option.equals("-batchID")) {
            return 2;
        } else {
            return 0;
        }
    }

    /**
     * Runs the javadoc tool to invoke this class as a doclet, passing
     * command line options derived from the specified batch data and
     * indicating the specified batch ID.
     *
     * NOTE: This method currently uses a J2SE-internal API to run
     * javadoc.  If and when there is a J2SE API for invoking SDK
     * tools, this method should be updated to use that API instead.
     **/
    private boolean invokeJavadoc(Batch batch, long batchID) {
        List<String> javadocArgs = new ArrayList<String>();

        // include all types, regardless of language-level access
        javadocArgs.add("-private");

        // inputs are class names, not source files
        javadocArgs.add("-Xclasses");

        // reproduce relevant options from rmic invocation
        if (batch.verbose) {
            javadocArgs.add("-verbose");
        }
        if (batch.bootClassPath != null) {
            javadocArgs.add("-bootclasspath");
            javadocArgs.add(batch.bootClassPath);
        }
        if (batch.extDirs != null) {
            javadocArgs.add("-extdirs");
            javadocArgs.add(batch.extDirs);
        }
        if (batch.classPath != null) {
            javadocArgs.add("-classpath");
            javadocArgs.add(batch.classPath);
        }

        // specify batch ID
        javadocArgs.add("-batchID");
        javadocArgs.add(Long.toString(batchID));

        /*
         * Run javadoc on union of rmic input classes and all
         * generators' bootstrap classes, so that they will all be
         * available to the doclet code.
         */
        Set<String> classNames = new HashSet<String>();
        for (Generator gen : batch.generators) {
            classNames.addAll(gen.bootstrapClassNames());
        }
        classNames.addAll(batch.classes);
        for (String s : classNames) {
            javadocArgs.add(s);
        }

        // run javadoc with our program name and output stream
        int status = com.sun.tools.javadoc.Main.execute(
            program,
            new PrintWriter(out, true),
            new PrintWriter(out, true),
            new PrintWriter(out, true),
            this.getClass().getName(),          // doclet class is this class
            javadocArgs.toArray(new String[javadocArgs.size()]));
        return status == 0;
    }

    /**
     * Runs the javac tool to compile the specified source files,
     * passing command line options derived from the specified batch
     * data.
     *
     * NOTE: This method currently uses a J2SE-internal API to run
     * javac.  If and when there is a J2SE API for invoking SDK tools,
     * this method should be updated to use that API instead.
     **/
    private boolean invokeJavac(Batch batch, List<File> files) {
        List<String> javacArgs = new ArrayList<String>();

        // rmic never wants to display javac warnings
        javacArgs.add("-nowarn");

        // reproduce relevant options from rmic invocation
        if (batch.debug) {
            javacArgs.add("-g");
        }
        if (batch.verbose) {
            javacArgs.add("-verbose");
        }
        if (batch.bootClassPath != null) {
            javacArgs.add("-bootclasspath");
            javacArgs.add(batch.bootClassPath);
        }
        if (batch.extDirs != null) {
            javacArgs.add("-extdirs");
            javacArgs.add(batch.extDirs);
        }
        if (batch.classPath != null) {
            javacArgs.add("-classpath");
            javacArgs.add(batch.classPath);
        }

        /*
         * For now, rmic still always produces class files that have a
         * class file format version compatible with JDK 1.1.
         */
        javacArgs.add("-source");
        javacArgs.add("1.3");
        javacArgs.add("-target");
        javacArgs.add("1.1");

        // add source files to compile
        for (File file : files) {
            javacArgs.add(file.getPath());
        }

        // run javac with our output stream
        int status = com.sun.tools.javac.Main.compile(
            javacArgs.toArray(new String[javacArgs.size()]),
            new PrintWriter(out, true));
        return status == 0;
    }

    /**
     * The data for an rmic compliation batch: the processed command
     * line arguments.
     **/
    private class Batch {
        boolean keepGenerated = false;  // -keep or -keepgenerated
        boolean debug = false;          // -g
        boolean noWarn = false;         // -nowarn
        boolean noWrite = false;        // -nowrite
        boolean verbose = false;        // -verbose
        boolean noCompile = false;      // -Xnocompile
        String bootClassPath = null;    // -bootclasspath
        String extDirs = null;          // -extdirs
        String classPath = null;        // -classpath
        File destDir = null;            // -d
        List<Generator> generators = new ArrayList<Generator>();
        Class<? extends BatchEnvironment> envClass = BatchEnvironment.class;
        List<String> classes = new ArrayList<String>();

        Batch() { }

        /**
         * Returns the Main instance for this batch.
         **/
        Main enclosingMain() {
            return Main.this;
        }
    }
}