aoqi@0: /* aoqi@0: * Copyright (c) 1998, 2007, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * Licensed Materials - Property of IBM aoqi@0: * RMI-IIOP v1.0 aoqi@0: * Copyright IBM Corp. 1998 1999 All Rights Reserved aoqi@0: * aoqi@0: */ aoqi@0: aoqi@0: aoqi@0: package sun.rmi.rmic.iiop; aoqi@0: aoqi@0: import java.io.File; aoqi@0: import java.io.FileOutputStream; aoqi@0: import java.io.OutputStreamWriter; aoqi@0: import java.io.IOException; aoqi@0: import sun.tools.java.Identifier; aoqi@0: import sun.tools.java.ClassPath; aoqi@0: import sun.tools.java.ClassFile; aoqi@0: import sun.tools.java.ClassNotFound; aoqi@0: import sun.tools.java.ClassDefinition; aoqi@0: import sun.tools.java.ClassDeclaration; aoqi@0: import sun.rmi.rmic.IndentingWriter; aoqi@0: import sun.rmi.rmic.Main; aoqi@0: import sun.rmi.rmic.iiop.Util; aoqi@0: import java.util.HashSet; aoqi@0: aoqi@0: /** aoqi@0: * Generator provides a small framework from which IIOP-specific aoqi@0: * generators can inherit. Common logic is implemented here which uses aoqi@0: * both abstract methods as well as concrete methods which subclasses may aoqi@0: * want to override. The following methods must be present in any subclass: aoqi@0: *
aoqi@0:  *      Default constructor
aoqi@0:  *              CompoundType getTopType(BatchEnvironment env, ClassDefinition cdef);
aoqi@0:  *      int parseArgs(String argv[], int currentIndex);
aoqi@0:  *      boolean requireNewInstance();
aoqi@0:  *              OutputType[] getOutputTypesFor(CompoundType topType,
aoqi@0:  *                                     HashSet alreadyChecked);
aoqi@0:  *              String getFileNameExtensionFor(OutputType outputType);
aoqi@0:  *              void writeOutputFor (   OutputType outputType,
aoqi@0:  *                              HashSet alreadyChecked,
aoqi@0:  *                                                              IndentingWriter writer) throws IOException;
aoqi@0:  * 
aoqi@0: * @author Bryan Atsatt aoqi@0: */ aoqi@0: public abstract class Generator implements sun.rmi.rmic.Generator, aoqi@0: sun.rmi.rmic.iiop.Constants { aoqi@0: aoqi@0: protected boolean alwaysGenerate = false; aoqi@0: protected BatchEnvironment env = null; aoqi@0: protected ContextStack contextStack = null; aoqi@0: private boolean trace = false; aoqi@0: protected boolean idl = false; aoqi@0: aoqi@0: /** aoqi@0: * Examine and consume command line arguments. aoqi@0: * @param argv The command line arguments. Ignore null aoqi@0: * and unknown arguments. Set each consumed argument to null. aoqi@0: * @param error Report any errors using the main.error() methods. aoqi@0: * @return true if no errors, false otherwise. aoqi@0: */ aoqi@0: public boolean parseArgs(String argv[], Main main) { aoqi@0: for (int i = 0; i < argv.length; i++) { aoqi@0: if (argv[i] != null) { aoqi@0: if (argv[i].equalsIgnoreCase("-always") || aoqi@0: argv[i].equalsIgnoreCase("-alwaysGenerate")) { aoqi@0: alwaysGenerate = true; aoqi@0: argv[i] = null; aoqi@0: } else if (argv[i].equalsIgnoreCase("-xtrace")) { aoqi@0: trace = true; aoqi@0: argv[i] = null; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Return true if non-conforming types should be parsed. aoqi@0: * @param stack The context stack. aoqi@0: */ aoqi@0: protected abstract boolean parseNonConforming(ContextStack stack); aoqi@0: aoqi@0: /** aoqi@0: * Create and return a top-level type. aoqi@0: * @param cdef The top-level class definition. aoqi@0: * @param stack The context stack. aoqi@0: * @return The compound type or null if is non-conforming. aoqi@0: */ aoqi@0: protected abstract CompoundType getTopType(ClassDefinition cdef, ContextStack stack); aoqi@0: aoqi@0: /** aoqi@0: * Return an array containing all the file names and types that need to be aoqi@0: * generated for the given top-level type. The file names must NOT have an aoqi@0: * extension (e.g. ".java"). aoqi@0: * @param topType The type returned by getTopType(). aoqi@0: * @param alreadyChecked A set of Types which have already been checked. aoqi@0: * Intended to be passed to Type.collectMatching(filter,alreadyChecked). aoqi@0: */ aoqi@0: protected abstract OutputType[] getOutputTypesFor(CompoundType topType, aoqi@0: HashSet alreadyChecked); aoqi@0: aoqi@0: /** aoqi@0: * Return the file name extension for the given file name (e.g. ".java"). aoqi@0: * All files generated with the ".java" extension will be compiled. To aoqi@0: * change this behavior for ".java" files, override the compileJavaSourceFile aoqi@0: * method to return false. aoqi@0: * @param outputType One of the items returned by getOutputTypesFor(...) aoqi@0: */ aoqi@0: protected abstract String getFileNameExtensionFor(OutputType outputType); aoqi@0: aoqi@0: /** aoqi@0: * Write the output for the given OutputFileName into the output stream. aoqi@0: * @param name One of the items returned by getOutputTypesFor(...) aoqi@0: * @param alreadyChecked A set of Types which have already been checked. aoqi@0: * Intended to be passed to Type.collectMatching(filter,alreadyChecked). aoqi@0: * @param writer The output stream. aoqi@0: */ aoqi@0: protected abstract void writeOutputFor(OutputType outputType, aoqi@0: HashSet alreadyChecked, aoqi@0: IndentingWriter writer) throws IOException; aoqi@0: aoqi@0: /** aoqi@0: * Return true if a new instance should be created for each aoqi@0: * class on the command line. Subclasses which return true aoqi@0: * should override newInstance() to return an appropriately aoqi@0: * constructed instance. aoqi@0: */ aoqi@0: protected abstract boolean requireNewInstance(); aoqi@0: aoqi@0: /** aoqi@0: * Return true if the specified file needs generation. aoqi@0: */ aoqi@0: public boolean requiresGeneration (File target, Type theType) { aoqi@0: aoqi@0: boolean result = alwaysGenerate; aoqi@0: aoqi@0: if (!result) { aoqi@0: aoqi@0: // Get a ClassFile instance for base source or class aoqi@0: // file. We use ClassFile so that if the base is in aoqi@0: // a zip file, we can still get at it's mod time... aoqi@0: aoqi@0: ClassFile baseFile; aoqi@0: ClassPath path = env.getClassPath(); aoqi@0: String className = theType.getQualifiedName().replace('.',File.separatorChar); aoqi@0: aoqi@0: // First try the source file... aoqi@0: aoqi@0: baseFile = path.getFile(className + ".source"); aoqi@0: aoqi@0: if (baseFile == null) { aoqi@0: aoqi@0: // Then try class file... aoqi@0: aoqi@0: baseFile = path.getFile(className + ".class"); aoqi@0: } aoqi@0: aoqi@0: // Do we have a baseFile? aoqi@0: aoqi@0: if (baseFile != null) { aoqi@0: aoqi@0: // Yes, grab baseFile's mod time... aoqi@0: aoqi@0: long baseFileMod = baseFile.lastModified(); aoqi@0: aoqi@0: // Get a File instance for the target. If it is a source aoqi@0: // file, create a class file instead since the source file aoqi@0: // will frequently be deleted... aoqi@0: aoqi@0: String targetName = IDLNames.replace(target.getName(),".java",".class"); aoqi@0: String parentPath = target.getParent(); aoqi@0: File targetFile = new File(parentPath,targetName); aoqi@0: aoqi@0: // Does the target file exist? aoqi@0: aoqi@0: if (targetFile.exists()) { aoqi@0: aoqi@0: // Yes, so grab it's mod time... aoqi@0: aoqi@0: long targetFileMod = targetFile.lastModified(); aoqi@0: aoqi@0: // Set result... aoqi@0: aoqi@0: result = targetFileMod < baseFileMod; aoqi@0: aoqi@0: } else { aoqi@0: aoqi@0: // No, so we must generate... aoqi@0: aoqi@0: result = true; aoqi@0: } aoqi@0: } else { aoqi@0: aoqi@0: // No, so we must generate... aoqi@0: aoqi@0: result = true; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: return result; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Create and return a new instance of self. Subclasses aoqi@0: * which need to do something other than default construction aoqi@0: * must override this method. aoqi@0: */ aoqi@0: protected Generator newInstance() { aoqi@0: Generator result = null; aoqi@0: try { aoqi@0: result = (Generator) getClass().newInstance(); aoqi@0: } aoqi@0: catch (Exception e){} // Should ALWAYS work! aoqi@0: aoqi@0: return result; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Default constructor for subclasses to use. aoqi@0: */ aoqi@0: protected Generator() { aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Generate output. Any source files created which need compilation should aoqi@0: * be added to the compiler environment using the addGeneratedFile(File) aoqi@0: * method. aoqi@0: * aoqi@0: * @param env The compiler environment aoqi@0: * @param cdef The definition for the implementation class or interface from aoqi@0: * which to generate output aoqi@0: * @param destDir The directory for the root of the package hierarchy aoqi@0: * for generated files. May be null. aoqi@0: */ aoqi@0: public void generate(sun.rmi.rmic.BatchEnvironment env, ClassDefinition cdef, File destDir) { aoqi@0: aoqi@0: this.env = (BatchEnvironment) env; aoqi@0: contextStack = new ContextStack(this.env); aoqi@0: contextStack.setTrace(trace); aoqi@0: aoqi@0: // Make sure the environment knows whether or not to parse aoqi@0: // non-conforming types. This will clear out any previously aoqi@0: // parsed types if necessary... aoqi@0: aoqi@0: this.env.setParseNonConforming(parseNonConforming(contextStack)); aoqi@0: aoqi@0: // Get our top level type... aoqi@0: aoqi@0: CompoundType topType = getTopType(cdef,contextStack); aoqi@0: if (topType != null) { aoqi@0: aoqi@0: Generator generator = this; aoqi@0: aoqi@0: // Do we need to make a new instance? aoqi@0: aoqi@0: if (requireNewInstance()) { aoqi@0: aoqi@0: // Yes, so make one. 'this' instance is the one instantiated by Main aoqi@0: // and which knows any needed command line args... aoqi@0: aoqi@0: generator = newInstance(); aoqi@0: } aoqi@0: aoqi@0: // Now generate all output files... aoqi@0: aoqi@0: generator.generateOutputFiles(topType, this.env, destDir); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Create and return a new instance of self. Subclasses aoqi@0: * which need to do something other than default construction aoqi@0: * must override this method. aoqi@0: */ aoqi@0: protected void generateOutputFiles (CompoundType topType, aoqi@0: BatchEnvironment env, aoqi@0: File destDir) { aoqi@0: aoqi@0: // Grab the 'alreadyChecked' HashSet from the environment... aoqi@0: aoqi@0: HashSet alreadyChecked = env.alreadyChecked; aoqi@0: aoqi@0: // Ask subclass for a list of output types... aoqi@0: aoqi@0: OutputType[] types = getOutputTypesFor(topType,alreadyChecked); aoqi@0: aoqi@0: // Process each file... aoqi@0: aoqi@0: for (int i = 0; i < types.length; i++) { aoqi@0: OutputType current = types[i]; aoqi@0: String className = current.getName(); aoqi@0: File file = getFileFor(current,destDir); aoqi@0: boolean sourceFile = false; aoqi@0: aoqi@0: // Do we need to generate this file? aoqi@0: aoqi@0: if (requiresGeneration(file,current.getType())) { aoqi@0: aoqi@0: // Yes. If java source file, add to environment so will be compiled... aoqi@0: aoqi@0: if (file.getName().endsWith(".java")) { aoqi@0: sourceFile = compileJavaSourceFile(current); aoqi@0: aoqi@0: // Are we supposeded to compile this one? aoqi@0: aoqi@0: if (sourceFile) { aoqi@0: env.addGeneratedFile(file); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // Now create an output stream and ask subclass to fill it up... aoqi@0: aoqi@0: try { aoqi@0: IndentingWriter out = new IndentingWriter( aoqi@0: new OutputStreamWriter(new FileOutputStream(file)),INDENT_STEP,TAB_SIZE); aoqi@0: aoqi@0: long startTime = 0; aoqi@0: if (env.verbose()) { aoqi@0: startTime = System.currentTimeMillis(); aoqi@0: } aoqi@0: aoqi@0: writeOutputFor(types[i],alreadyChecked,out); aoqi@0: out.close(); aoqi@0: aoqi@0: if (env.verbose()) { aoqi@0: long duration = System.currentTimeMillis() - startTime; aoqi@0: env.output(Main.getText("rmic.generated", file.getPath(), Long.toString(duration))); aoqi@0: } aoqi@0: if (sourceFile) { aoqi@0: env.parseFile(new ClassFile(file)); aoqi@0: } aoqi@0: } catch (IOException e) { aoqi@0: env.error(0, "cant.write", file.toString()); aoqi@0: return; aoqi@0: } aoqi@0: } else { aoqi@0: aoqi@0: // No, say so if we need to... aoqi@0: aoqi@0: if (env.verbose()) { aoqi@0: env.output(Main.getText("rmic.previously.generated", file.getPath())); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Return the File object that should be used as the output file aoqi@0: * for the given OutputType. aoqi@0: * @param outputType The type to create a file for. aoqi@0: * @param destinationDir The directory to use as the root of the aoqi@0: * package heirarchy. May be null, in which case the current aoqi@0: * classpath is searched to find the directory in which to create aoqi@0: * the output file. If that search fails (most likely because the aoqi@0: * package directory lives in a zip or jar file rather than the aoqi@0: * file system), the current user directory is used. aoqi@0: */ aoqi@0: protected File getFileFor(OutputType outputType, File destinationDir) { aoqi@0: // Calling this method does some crucial initialization aoqi@0: // in a subclass implementation. Don't skip it. aoqi@0: Identifier id = getOutputId(outputType); aoqi@0: File packageDir = null; aoqi@0: if(idl){ aoqi@0: packageDir = Util.getOutputDirectoryForIDL(id,destinationDir,env); aoqi@0: } else { aoqi@0: packageDir = Util.getOutputDirectoryForStub(id,destinationDir,env); aoqi@0: } aoqi@0: String classFileName = outputType.getName() + getFileNameExtensionFor(outputType); aoqi@0: return new File(packageDir, classFileName); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Return an identifier to use for output. aoqi@0: * @param outputType the type for which output is to be generated. aoqi@0: * @return the new identifier. This implementation returns the input parameter. aoqi@0: */ aoqi@0: protected Identifier getOutputId (OutputType outputType) { aoqi@0: return outputType.getType().getIdentifier(); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Return true if the given file should be compiled. aoqi@0: * @param outputType One of the items returned by getOutputTypesFor(...) for aoqi@0: * which getFileNameExtensionFor(OutputType) returned ".java". aoqi@0: */ aoqi@0: protected boolean compileJavaSourceFile (OutputType outputType) { aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: //_____________________________________________________________________ aoqi@0: // OutputType is a simple wrapper for a name and a Type aoqi@0: //_____________________________________________________________________ aoqi@0: aoqi@0: public class OutputType { aoqi@0: private String name; aoqi@0: private Type type; aoqi@0: aoqi@0: public OutputType (String name, Type type) { aoqi@0: this.name = name; aoqi@0: this.type = type; aoqi@0: } aoqi@0: aoqi@0: public String getName() { aoqi@0: return name; aoqi@0: } aoqi@0: aoqi@0: public Type getType() { aoqi@0: return type; aoqi@0: } aoqi@0: } aoqi@0: }