diff options
Diffstat (limited to 'src/share/classes/sun/rmi/rmic/newrmic/Main.java')
-rw-r--r-- | src/share/classes/sun/rmi/rmic/newrmic/Main.java | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Main.java b/src/share/classes/sun/rmi/rmic/newrmic/Main.java new file mode 100644 index 000000000..e7dc7c899 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/Main.java @@ -0,0 +1,689 @@ +/* + * Copyright 2003-2005 Sun Microsystems, Inc. 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.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; + } + } +} |