/*
* Copyright (c) 1996, 2007, 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.
*/
/*
* Licensed Materials - Property of IBM
* RMI-IIOP v1.0
* Copyright IBM Corp. 1998 1999 All Rights Reserved
*
*/
package sun.rmi.rmic;
import java.util.Vector;
import java.util.Enumeration;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.MissingResourceException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.IOException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ByteArrayOutputStream;
import sun.tools.java.ClassFile;
import sun.tools.java.ClassDefinition;
import sun.tools.java.ClassDeclaration;
import sun.tools.java.ClassNotFound;
import sun.tools.java.Identifier;
import sun.tools.java.ClassPath;
import sun.tools.javac.SourceClass;
import sun.tools.util.CommandLine;
import java.lang.reflect.Constructor;
import java.util.Properties;
/**
* Main "rmic" program.
*
* 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.
*/
public class Main implements sun.rmi.rmic.Constants {
String sourcePathArg;
String sysClassPathArg;
String extDirsArg;
String classPathString;
File destDir;
int flags;
long tm;
Vector<String> classes;
boolean nowrite;
boolean nocompile;
boolean keepGenerated;
boolean status;
String[] generatorArgs;
Vector<Generator> generators;
Class<? extends BatchEnvironment> environmentClass =
BatchEnvironment.class;
boolean iiopGeneration = false;
/**
* Name of the program.
*/
String program;
/**
* The stream where error message are printed.
*/
OutputStream out;
/**
* Constructor.
*/
public Main(OutputStream out, String program) {
this.out = out;
this.program = program;
}
/**
* Output a message.
*/
public void output(String msg) {
PrintStream out =
this.out instanceof PrintStream ? (PrintStream)this.out
: new PrintStream(this.out, true);
out.println(msg);
}
/**
* Top level error message. This method is called when the
* environment could not be set up yet.
*/
public void error(String msg) {
output(getText(msg));
}
public void error(String msg, String arg1) {
output(getText(msg, arg1));
}
public void error(String msg, String arg1, String arg2) {
output(getText(msg, arg1, arg2));
}
/**
* Usage
*/
public void usage() {
error("rmic.usage", program);
}
/**
* Run the compiler
*/
public synchronized boolean compile(String argv[]) {
/*
* Handle internal option to use the new (and incomplete) rmic
* implementation. This option is handled here, rather than
* in parseArgs, so that none of the arguments will be nulled
* before delegating to the new implementation.
*/
for (int i = 0; i < argv.length; i++) {
if (argv[i].equals("-Xnew")) {
return (new sun.rmi.rmic.newrmic.Main(out,
program)).compile(argv);
}
}
if (!parseArgs(argv)) {
return false;
}
if (classes.size() == 0) {
usage();
return false;
}
return doCompile();
}
/**
* Get the destination directory.
*/
public File getDestinationDir() {
return destDir;
}
/**
* Parse the arguments for compile.
*/
public boolean parseArgs(String argv[]) {
sourcePathArg = null;
sysClassPathArg = null;
extDirsArg = null;
classPathString = null;
destDir = null;
flags = F_WARNINGS;
tm = System.currentTimeMillis();
classes = new Vector<>();
nowrite = false;
nocompile = false;
keepGenerated = false;
generatorArgs = getArray("generator.args",true);
if (generatorArgs == null) {
return false;
}
generators = new Vector<>();
// Pre-process command line for @file arguments
try {
argv = CommandLine.parse(argv);
} catch (FileNotFoundException e) {
error("rmic.cant.read", e.getMessage());
return false;
} catch (IOException e) {
e.printStackTrace(out instanceof PrintStream ?
(PrintStream) out :
new PrintStream(out, true));
return false;
}
// Parse arguments
for (int i = 0 ; i < argv.length ; i++) {
if (argv[i] != null) {
if (argv[i].equals("-g")) {
flags &= ~F_OPT;
flags |= F_DEBUG_LINES | F_DEBUG_VARS;
argv[i] = null;
} else if (argv[i].equals("-O")) {
flags &= ~F_DEBUG_LINES;
flags &= ~F_DEBUG_VARS;
flags |= F_OPT | F_DEPENDENCIES;
argv[i] = null;
} else if (argv[i].equals("-nowarn")) {
flags &= ~F_WARNINGS;
argv[i] = null;
} else if (argv[i].equals("-debug")) {
flags |= F_DUMP;
argv[i] = null;
} else if (argv[i].equals("-depend")) {
flags |= F_DEPENDENCIES;
argv[i] = null;
} else if (argv[i].equals("-verbose")) {
flags |= F_VERBOSE;
argv[i] = null;
} else if (argv[i].equals("-nowrite")) {
nowrite = true;
argv[i] = null;
} else if (argv[i].equals("-Xnocompile")) {
nocompile = true;
keepGenerated = true;
argv[i] = null;
} else if (argv[i].equals("-keep") ||
argv[i].equals("-keepgenerated")) {
keepGenerated = true;
argv[i] = null;
} else if (argv[i].equals("-show")) {
error("rmic.option.unsupported", "-show");
usage();
return false;
} else if (argv[i].equals("-classpath")) {
if ((i + 1) < argv.length) {
if (classPathString != null) {
error("rmic.option.already.seen", "-classpath");
usage();
return false;
}
argv[i] = null;
classPathString = argv[++i];
argv[i] = null;
} else {
error("rmic.option.requires.argument", "-classpath");
usage();
return false;
}
} else if (argv[i].equals("-sourcepath")) {
if ((i + 1) < argv.length) {
if (sourcePathArg != null) {
error("rmic.option.already.seen", "-sourcepath");
usage();
return false;
}
argv[i] = null;
sourcePathArg = argv[++i];
argv[i] = null;
} else {
error("rmic.option.requires.argument", "-sourcepath");
usage();
return false;
}
} else if (argv[i].equals("-bootclasspath")) {
if ((i + 1) < argv.length) {
if (sysClassPathArg != null) {
error("rmic.option.already.seen", "-bootclasspath");
usage();
return false;
}
argv[i] = null;
sysClassPathArg = argv[++i];
argv[i] = null;
} else {
error("rmic.option.requires.argument", "-bootclasspath");
usage();
return false;
}
} else if (argv[i].equals("-extdirs")) {
if ((i + 1) < argv.length) {
if (extDirsArg != null) {
error("rmic.option.already.seen", "-extdirs");
usage();
return false;
}
argv[i] = null;
extDirsArg = argv[++i];
argv[i] = null;
} else {
error("rmic.option.requires.argument", "-extdirs");
usage();
return false;
}
} else if (argv[i].equals("-d")) {
if ((i + 1) < argv.length) {
if (destDir != null) {
error("rmic.option.already.seen", "-d");
usage();
return false;
}
argv[i] = null;
destDir = new File(argv[++i]);
argv[i] = null;
if (!destDir.exists()) {
error("rmic.no.such.directory", destDir.getPath());
usage();
return false;
}
} else {
error("rmic.option.requires.argument", "-d");
usage();
return false;
}
} else {
if (!checkGeneratorArg(argv,i)) {
usage();
return false;
}
}
}
}
// Now that all generators have had a chance at the args,
// scan what's left for classes and illegal args...
for (int i = 0; i < argv.length; i++) {
if (argv[i] != null) {
if (argv[i].startsWith("-")) {
error("rmic.no.such.option", argv[i]);
usage();
return false;
} else {
classes.addElement(argv[i]);
}
}
}
// If the generators vector is empty, add the default generator...
if (generators.size() == 0) {
addGenerator("default");
}
return true;
}
/**
* If this argument is for a generator, instantiate it, call
* parseArgs(...) and add generator to generators vector.
* Returns false on error.
*/
protected boolean checkGeneratorArg(String[] argv, int currentIndex) {
boolean result = true;
if (argv[currentIndex].startsWith("-")) {
String arg = argv[currentIndex].substring(1).toLowerCase(); // Remove '-'
for (int i = 0; i < generatorArgs.length; i++) {
if (arg.equalsIgnoreCase(generatorArgs[i])) {
// Got a match, add Generator and call parseArgs...
Generator gen = addGenerator(arg);
if (gen == null) {
return false;
}
result = gen.parseArgs(argv,this);
break;
}
}
}
return result;
}
/**
* Instantiate and add a generator to the generators array.
*/
protected Generator addGenerator(String arg) {
Generator gen;
// Create an instance of the generator and add it to
// the array...
String className = getString("generator.class." + arg);
if (className == null) {
error("rmic.missing.property",arg);
return null;
}
try {
gen = (Generator) Class.forName(className).newInstance();
} catch (Exception e) {
error("rmic.cannot.instantiate",className);
return null;
}
generators.addElement(gen);
// Get the environment required by this generator...
Class<?> envClass = BatchEnvironment.class;
String env = getString("generator.env." + arg);
if (env != null) {
try {
envClass = Class.forName(env);
// Is the new class a subclass of the current one?
if (environmentClass.isAssignableFrom(envClass)) {
// Yes, so switch to the new one...
environmentClass = envClass.asSubclass(BatchEnvironment.class);
} else {
// No. Is the current class a subclass of the
// new one?
if (!envClass.isAssignableFrom(environmentClass)) {
// No, so it's a conflict...
error("rmic.cannot.use.both",environmentClass.getName(),envClass.getName());
return null;
}
}
} catch (ClassNotFoundException e) {
error("rmic.class.not.found",env);
return null;
}
}
// If this is the iiop stub generator, cache
// that fact for the jrmp generator...
if (arg.equals("iiop")) {
iiopGeneration = true;
}
return gen;
}
/**
* Grab a resource string and parse it into an array of strings. Assumes
* comma separated list.
* @param name The resource name.
* @param mustExist If true, throws error if resource does not exist. If
* false and resource does not exist, returns zero element array.
*/
protected String[] getArray(String name, boolean mustExist) {
String[] result = null;
String value = getString(name);
if (value == null) {
if (mustExist) {
error("rmic.resource.not.found",name);
return null;
} else {
return new String[0];
}
}
StringTokenizer parser = new StringTokenizer(value,", \t\n\r", false);
int count = parser.countTokens();
result = new String[count];
for (int i = 0; i < count; i++) {
result[i] = parser.nextToken();
}
return result;
}
/**
* Get the correct type of BatchEnvironment
*/
public BatchEnvironment getEnv() {
ClassPath classPath =
BatchEnvironment.createClassPath(classPathString,
sysClassPathArg,
extDirsArg);
BatchEnvironment result = null;
try {
Class[] ctorArgTypes = {OutputStream.class,ClassPath.class,Main.class};
Object[] ctorArgs = {out,classPath,this};
Constructor<? extends BatchEnvironment> constructor =
environmentClass.getConstructor(ctorArgTypes);
result = constructor.newInstance(ctorArgs);
result.reset();
}
catch (Exception e) {
error("rmic.cannot.instantiate",environmentClass.getName());
}
return result;
}
/**
* Do the compile with the switches and files already supplied
*/
public boolean doCompile() {
// Create batch environment
BatchEnvironment env = getEnv();
env.flags |= flags;
// Set the classfile version numbers
// Compat and 1.1 stubs must retain the old version number.
env.majorVersion = 45;
env.minorVersion = 3;
// Preload the "out of memory" error string just in case we run
// out of memory during the compile.
String noMemoryErrorString = getText("rmic.no.memory");
String stackOverflowErrorString = getText("rmic.stack.overflow");
try {
/** Load the classes on the command line
* Replace the entries in classes with the ClassDefinition for the class
*/
for (int i = classes.size()-1; i >= 0; i-- ) {
Identifier implClassName =
Identifier.lookup(classes.elementAt(i));
/*
* Fix bugid 4049354: support using '.' as an inner class
* qualifier on the command line (previously, only mangled
* inner class names were understood, like "pkg.Outer$Inner").
*
* The following method, also used by "javap", resolves the
* given unmangled inner class name to the appropriate
* internal identifier. For example, it translates
* "pkg.Outer.Inner" to "pkg.Outer. Inner".
*/
implClassName = env.resolvePackageQualifiedName(implClassName);
/*
* But if we use such an internal inner class name identifier
* to load the class definition, the Java compiler will notice
* if the impl class is a "private" inner class and then deny
* skeletons (needed unless "-v1.2" is used) the ability to
* cast to it. To work around this problem, we mangle inner
* class name identifiers to their binary "outer" class name:
* "pkg.Outer. Inner" becomes "pkg.Outer$Inner".
*/
implClassName = Names.mangleClass(implClassName);
ClassDeclaration decl = env.getClassDeclaration(implClassName);
try {
ClassDefinition def = decl.getClassDefinition(env);
for (int j = 0; j < generators.size(); j++) {
Generator gen = generators.elementAt(j);
gen.generate(env, def, destDir);
}
} catch (ClassNotFound ex) {
env.error(0, "rmic.class.not.found", implClassName);
}
}
// compile all classes that need compilation
if (!nocompile) {
compileAllClasses(env);
}
} catch (OutOfMemoryError ee) {
// The compiler has run out of memory. Use the error string
// which we preloaded.
env.output(noMemoryErrorString);
return false;
} catch (StackOverflowError ee) {
env.output(stackOverflowErrorString);
return false;
} catch (Error ee) {
// We allow the compiler to take an exception silently if a program
// error has previously been detected. Presumably, this makes the
// compiler more robust in the face of bad error recovery.
if (env.nerrors == 0 || env.dump()) {
env.error(0, "fatal.error");
ee.printStackTrace(out instanceof PrintStream ?
(PrintStream) out :
new PrintStream(out, true));
}
} catch (Exception ee) {
if (env.nerrors == 0 || env.dump()) {
env.error(0, "fatal.exception");
ee.printStackTrace(out instanceof PrintStream ?
(PrintStream) out :
new PrintStream(out, true));
}
}
env.flushErrors();
boolean status = true;
if (env.nerrors > 0) {
String msg = "";
if (env.nerrors > 1) {
msg = getText("rmic.errors", env.nerrors);
} else {
msg = getText("rmic.1error");
}
if (env.nwarnings > 0) {
if (env.nwarnings > 1) {
msg += ", " + getText("rmic.warnings", env.nwarnings);
} else {
msg += ", " + getText("rmic.1warning");
}
}
output(msg);
status = false;
} else {
if (env.nwarnings > 0) {
if (env.nwarnings > 1) {
output(getText("rmic.warnings", env.nwarnings));
} else {
output(getText("rmic.1warning"));
}
}
}
// last step is to delete generated source files
if (!keepGenerated) {
env.deleteGeneratedFiles();
}
// We're done
if (env.verbose()) {
tm = System.currentTimeMillis() - tm;
output(getText("rmic.done_in", Long.toString(tm)));
}
// Shutdown the environment object and release our resources.
// Note that while this is unneccessary when rmic is invoked
// the command line, there are environments in which rmic
// from is invoked within a server process, so resource
// reclamation is important...
env.shutdown();
sourcePathArg = null;
sysClassPathArg = null;
extDirsArg = null;
classPathString = null;
destDir = null;
classes = null;
generatorArgs = null;
generators = null;
environmentClass = null;
program = null;
out = null;
return status;
}
/*
* Compile all classes that need to be compiled.
*/
public void compileAllClasses (BatchEnvironment env)
throws ClassNotFound,
IOException,
InterruptedException {
ByteArrayOutputStream buf = new ByteArrayOutputStream(4096);
boolean done;
do {
done = true;
for (Enumeration<?> e = env.getClasses() ; e.hasMoreElements() ; ) {
ClassDeclaration c = (ClassDeclaration)e.nextElement();
done = compileClass(c,buf,env);
}
} while (!done);
}
/*
* Compile a single class.
* Fallthrough is intentional
*/
@SuppressWarnings("fallthrough")
public boolean compileClass (ClassDeclaration c,
ByteArrayOutputStream buf,
BatchEnvironment env)
throws ClassNotFound,
IOException,
InterruptedException {
boolean done = true;
env.flushErrors();
SourceClass src;
switch (c.getStatus()) {
case CS_UNDEFINED:
{
if (!env.dependencies()) {
break;
}
// fall through
}
case CS_SOURCE:
{
done = false;
env.loadDefinition(c);
if (c.getStatus() != CS_PARSED) {
break;
}
// fall through
}
case CS_PARSED:
{
if (c.getClassDefinition().isInsideLocal()) {
break;
}
// If we get to here, then compilation is going
// to occur. If the -Xnocompile switch is set
// then fail. Note that this check is required
// here because this method is called from
// generators, not just from within this class...
if (nocompile) {
throw new IOException("Compilation required, but -Xnocompile option in effect");
}
done = false;
src = (SourceClass)c.getClassDefinition(env);
src.check(env);
c.setDefinition(src, CS_CHECKED);
// fall through
}
case CS_CHECKED:
{
src = (SourceClass)c.getClassDefinition(env);
// bail out if there were any errors
if (src.getError()) {
c.setDefinition(src, CS_COMPILED);
break;
}
done = false;
buf.reset();
src.compile(buf);
c.setDefinition(src, CS_COMPILED);
src.cleanup(env);
if (src.getError() || nowrite) {
break;
}
String pkgName = c.getName().getQualifier().toString().replace('.', File.separatorChar);
String className = c.getName().getFlatName().toString().replace('.', SIGC_INNERCLASS) + ".class";
File file;
if (destDir != null) {
if (pkgName.length() > 0) {
file = new File(destDir, pkgName);
if (!file.exists()) {
file.mkdirs();
}
file = new File(file, className);
} else {
file = new File(destDir, className);
}
} else {
ClassFile classfile = (ClassFile)src.getSource();
if (classfile.isZipped()) {
env.error(0, "cant.write", classfile.getPath());
break;
}
file = new File(classfile.getPath());
file = new File(file.getParent(), className);
}
// Create the file
try {
FileOutputStream out = new FileOutputStream(file.getPath());
buf.writeTo(out);
out.close();
if (env.verbose()) {
output(getText("rmic.wrote", file.getPath()));
}
} catch (IOException ee) {
env.error(0, "cant.write", file.getPath());
}
}
}
return done;
}
/**
* Main program
*/
public static void main(String argv[]) {
Main compiler = new Main(System.out, "rmic");
System.exit(compiler.compile(argv) ? 0 : 1);
}
/**
* Return the string value of a named resource in the rmic.properties
* resource bundle. If the resource is not found, null is returned.
*/
public static String getString(String key) {
if (!resourcesInitialized) {
initResources();
}
// To enable extensions, search the 'resourcesExt'
// bundle first, followed by the 'resources' bundle...
if (resourcesExt != null) {
try {
return resourcesExt.getString(key);
} catch (MissingResourceException e) {}
}
try {
return resources.getString(key);
} catch (MissingResourceException ignore) {
}
return null;
}
private static boolean resourcesInitialized = false;
private static ResourceBundle resources;
private static ResourceBundle resourcesExt = null;
private static void initResources() {
try {
resources =
ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic");
resourcesInitialized = true;
try {
resourcesExt =
ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext");
} catch (MissingResourceException e) {}
} catch (MissingResourceException e) {
throw new Error("fatal: missing resource bundle: " +
e.getClassName());
}
}
public static String getText(String key) {
String message = getString(key);
if (message == null) {
message = "no text found: \"" + key + "\"";
}
return message;
}
public static String getText(String key, int num) {
return getText(key, Integer.toString(num), null, null);
}
public static String getText(String key, String arg0) {
return getText(key, arg0, null, null);
}
public static String getText(String key, String arg0, String arg1) {
return getText(key, arg0, arg1, null);
}
public static String getText(String key,
String arg0, String arg1, String arg2)
{
String format = getString(key);
if (format == null) {
format = "no text found: key = \"" + key + "\", " +
"arguments = \"{0}\", \"{1}\", \"{2}\"";
}
String[] args = new String[3];
args[0] = (arg0 != null ? arg0.toString() : "null");
args[1] = (arg1 != null ? arg1.toString() : "null");
args[2] = (arg2 != null ? arg2.toString() : "null");
return java.text.MessageFormat.format(format, (Object[]) args);
}
}