blob: e775120460cd771e5b7a918f189386300542dd49 [file] [log] [blame]
/*
* Copyright (c) 1998, 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.iiop;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import sun.tools.java.Identifier;
import sun.tools.java.ClassPath;
import sun.tools.java.ClassFile;
import sun.tools.java.ClassNotFound;
import sun.tools.java.ClassDefinition;
import sun.tools.java.ClassDeclaration;
import sun.rmi.rmic.IndentingWriter;
import sun.rmi.rmic.Main;
import sun.rmi.rmic.iiop.Util;
import java.util.HashSet;
/**
* Generator provides a small framework from which IIOP-specific
* generators can inherit. Common logic is implemented here which uses
* both abstract methods as well as concrete methods which subclasses may
* want to override. The following methods must be present in any subclass:
* <pre>
* Default constructor
* CompoundType getTopType(BatchEnvironment env, ClassDefinition cdef);
* int parseArgs(String argv[], int currentIndex);
* boolean requireNewInstance();
* OutputType[] getOutputTypesFor(CompoundType topType,
* HashSet alreadyChecked);
* String getFileNameExtensionFor(OutputType outputType);
* void writeOutputFor ( OutputType outputType,
* HashSet alreadyChecked,
* IndentingWriter writer) throws IOException;
* </pre>
* @author Bryan Atsatt
*/
public abstract class Generator implements sun.rmi.rmic.Generator,
sun.rmi.rmic.iiop.Constants {
protected boolean alwaysGenerate = false;
protected BatchEnvironment env = null;
protected ContextStack contextStack = null;
private boolean trace = false;
protected boolean idl = false;
/**
* Examine and consume command line arguments.
* @param argv The command line arguments. Ignore null
* and unknown arguments. Set each consumed argument to null.
* @param error Report any errors using the main.error() methods.
* @return true if no errors, false otherwise.
*/
public boolean parseArgs(String argv[], Main main) {
for (int i = 0; i < argv.length; i++) {
if (argv[i] != null) {
if (argv[i].equalsIgnoreCase("-always") ||
argv[i].equalsIgnoreCase("-alwaysGenerate")) {
alwaysGenerate = true;
argv[i] = null;
} else if (argv[i].equalsIgnoreCase("-xtrace")) {
trace = true;
argv[i] = null;
}
}
}
return true;
}
/**
* Return true if non-conforming types should be parsed.
* @param stack The context stack.
*/
protected abstract boolean parseNonConforming(ContextStack stack);
/**
* Create and return a top-level type.
* @param cdef The top-level class definition.
* @param stack The context stack.
* @return The compound type or null if is non-conforming.
*/
protected abstract CompoundType getTopType(ClassDefinition cdef, ContextStack stack);
/**
* Return an array containing all the file names and types that need to be
* generated for the given top-level type. The file names must NOT have an
* extension (e.g. ".java").
* @param topType The type returned by getTopType().
* @param alreadyChecked A set of Types which have already been checked.
* Intended to be passed to Type.collectMatching(filter,alreadyChecked).
*/
protected abstract OutputType[] getOutputTypesFor(CompoundType topType,
HashSet alreadyChecked);
/**
* Return the file name extension for the given file name (e.g. ".java").
* All files generated with the ".java" extension will be compiled. To
* change this behavior for ".java" files, override the compileJavaSourceFile
* method to return false.
* @param outputType One of the items returned by getOutputTypesFor(...)
*/
protected abstract String getFileNameExtensionFor(OutputType outputType);
/**
* Write the output for the given OutputFileName into the output stream.
* @param name One of the items returned by getOutputTypesFor(...)
* @param alreadyChecked A set of Types which have already been checked.
* Intended to be passed to Type.collectMatching(filter,alreadyChecked).
* @param writer The output stream.
*/
protected abstract void writeOutputFor(OutputType outputType,
HashSet alreadyChecked,
IndentingWriter writer) throws IOException;
/**
* Return true if a new instance should be created for each
* class on the command line. Subclasses which return true
* should override newInstance() to return an appropriately
* constructed instance.
*/
protected abstract boolean requireNewInstance();
/**
* Return true if the specified file needs generation.
*/
public boolean requiresGeneration (File target, Type theType) {
boolean result = alwaysGenerate;
if (!result) {
// Get a ClassFile instance for base source or class
// file. We use ClassFile so that if the base is in
// a zip file, we can still get at it's mod time...
ClassFile baseFile;
ClassPath path = env.getClassPath();
String className = theType.getQualifiedName().replace('.',File.separatorChar);
// First try the source file...
baseFile = path.getFile(className + ".source");
if (baseFile == null) {
// Then try class file...
baseFile = path.getFile(className + ".class");
}
// Do we have a baseFile?
if (baseFile != null) {
// Yes, grab baseFile's mod time...
long baseFileMod = baseFile.lastModified();
// Get a File instance for the target. If it is a source
// file, create a class file instead since the source file
// will frequently be deleted...
String targetName = IDLNames.replace(target.getName(),".java",".class");
String parentPath = target.getParent();
File targetFile = new File(parentPath,targetName);
// Does the target file exist?
if (targetFile.exists()) {
// Yes, so grab it's mod time...
long targetFileMod = targetFile.lastModified();
// Set result...
result = targetFileMod < baseFileMod;
} else {
// No, so we must generate...
result = true;
}
} else {
// No, so we must generate...
result = true;
}
}
return result;
}
/**
* Create and return a new instance of self. Subclasses
* which need to do something other than default construction
* must override this method.
*/
protected Generator newInstance() {
Generator result = null;
try {
result = (Generator) getClass().newInstance();
}
catch (Exception e){} // Should ALWAYS work!
return result;
}
/**
* Default constructor for subclasses to use.
*/
protected Generator() {
}
/**
* Generate output. Any source files created which need compilation should
* be added to the compiler environment using the addGeneratedFile(File)
* method.
*
* @param env The compiler environment
* @param cdef The definition for the implementation class or interface from
* which to generate output
* @param destDir The directory for the root of the package hierarchy
* for generated files. May be null.
*/
public void generate(sun.rmi.rmic.BatchEnvironment env, ClassDefinition cdef, File destDir) {
this.env = (BatchEnvironment) env;
contextStack = new ContextStack(this.env);
contextStack.setTrace(trace);
// Make sure the environment knows whether or not to parse
// non-conforming types. This will clear out any previously
// parsed types if necessary...
this.env.setParseNonConforming(parseNonConforming(contextStack));
// Get our top level type...
CompoundType topType = getTopType(cdef,contextStack);
if (topType != null) {
Generator generator = this;
// Do we need to make a new instance?
if (requireNewInstance()) {
// Yes, so make one. 'this' instance is the one instantiated by Main
// and which knows any needed command line args...
generator = newInstance();
}
// Now generate all output files...
generator.generateOutputFiles(topType, this.env, destDir);
}
}
/**
* Create and return a new instance of self. Subclasses
* which need to do something other than default construction
* must override this method.
*/
protected void generateOutputFiles (CompoundType topType,
BatchEnvironment env,
File destDir) {
// Grab the 'alreadyChecked' HashSet from the environment...
HashSet alreadyChecked = env.alreadyChecked;
// Ask subclass for a list of output types...
OutputType[] types = getOutputTypesFor(topType,alreadyChecked);
// Process each file...
for (int i = 0; i < types.length; i++) {
OutputType current = types[i];
String className = current.getName();
File file = getFileFor(current,destDir);
boolean sourceFile = false;
// Do we need to generate this file?
if (requiresGeneration(file,current.getType())) {
// Yes. If java source file, add to environment so will be compiled...
if (file.getName().endsWith(".java")) {
sourceFile = compileJavaSourceFile(current);
// Are we supposeded to compile this one?
if (sourceFile) {
env.addGeneratedFile(file);
}
}
// Now create an output stream and ask subclass to fill it up...
try {
IndentingWriter out = new IndentingWriter(
new OutputStreamWriter(new FileOutputStream(file)),INDENT_STEP,TAB_SIZE);
long startTime = 0;
if (env.verbose()) {
startTime = System.currentTimeMillis();
}
writeOutputFor(types[i],alreadyChecked,out);
out.close();
if (env.verbose()) {
long duration = System.currentTimeMillis() - startTime;
env.output(Main.getText("rmic.generated", file.getPath(), Long.toString(duration)));
}
if (sourceFile) {
env.parseFile(new ClassFile(file));
}
} catch (IOException e) {
env.error(0, "cant.write", file.toString());
return;
}
} else {
// No, say so if we need to...
if (env.verbose()) {
env.output(Main.getText("rmic.previously.generated", file.getPath()));
}
}
}
}
/**
* Return the File object that should be used as the output file
* for the given OutputType.
* @param outputType The type to create a file for.
* @param destinationDir The directory to use as the root of the
* package heirarchy. May be null, in which case the current
* classpath is searched to find the directory in which to create
* the output file. If that search fails (most likely because the
* package directory lives in a zip or jar file rather than the
* file system), the current user directory is used.
*/
protected File getFileFor(OutputType outputType, File destinationDir) {
// Calling this method does some crucial initialization
// in a subclass implementation. Don't skip it.
Identifier id = getOutputId(outputType);
File packageDir = null;
if(idl){
packageDir = Util.getOutputDirectoryForIDL(id,destinationDir,env);
} else {
packageDir = Util.getOutputDirectoryForStub(id,destinationDir,env);
}
String classFileName = outputType.getName() + getFileNameExtensionFor(outputType);
return new File(packageDir, classFileName);
}
/**
* Return an identifier to use for output.
* @param outputType the type for which output is to be generated.
* @return the new identifier. This implementation returns the input parameter.
*/
protected Identifier getOutputId (OutputType outputType) {
return outputType.getType().getIdentifier();
}
/**
* Return true if the given file should be compiled.
* @param outputType One of the items returned by getOutputTypesFor(...) for
* which getFileNameExtensionFor(OutputType) returned ".java".
*/
protected boolean compileJavaSourceFile (OutputType outputType) {
return true;
}
//_____________________________________________________________________
// OutputType is a simple wrapper for a name and a Type
//_____________________________________________________________________
public class OutputType {
private String name;
private Type type;
public OutputType (String name, Type type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
}
}