1 /*
   2  * Copyright (c) 1998, 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * Licensed Materials - Property of IBM
  28  * RMI-IIOP v1.0
  29  * Copyright IBM Corp. 1998 1999  All Rights Reserved
  30  *
  31  */
  32 
  33 
  34 package sun.rmi.rmic.iiop;
  35 
  36 import java.io.File;
  37 import java.io.FileOutputStream;
  38 import java.io.OutputStreamWriter;
  39 import java.io.IOException;
  40 import sun.tools.java.Identifier;
  41 import sun.tools.java.ClassPath;
  42 import sun.tools.java.ClassFile;
  43 import sun.tools.java.ClassNotFound;
  44 import sun.tools.java.ClassDefinition;
  45 import sun.tools.java.ClassDeclaration;
  46 import sun.rmi.rmic.IndentingWriter;
  47 import sun.rmi.rmic.Main;
  48 import sun.rmi.rmic.iiop.Util;
  49 import java.util.HashSet;
  50 
  51 /**
  52  * Generator provides a small framework from which IIOP-specific
  53  * generators can inherit.  Common logic is implemented here which uses
  54  * both abstract methods as well as concrete methods which subclasses may
  55  * want to override. The following methods must be present in any subclass:
  56  * <pre>
  57  *      Default constructor
  58  *              CompoundType getTopType(BatchEnvironment env, ClassDefinition cdef);
  59  *      int parseArgs(String argv[], int currentIndex);
  60  *      boolean requireNewInstance();
  61  *              OutputType[] getOutputTypesFor(CompoundType topType,
  62  *                                     HashSet alreadyChecked);
  63  *              String getFileNameExtensionFor(OutputType outputType);
  64  *              void writeOutputFor (   OutputType outputType,
  65  *                              HashSet alreadyChecked,
  66  *                                                              IndentingWriter writer) throws IOException;
  67  * </pre>
  68  * @author      Bryan Atsatt
  69  */
  70 public abstract class Generator implements      sun.rmi.rmic.Generator,
  71                                                 sun.rmi.rmic.iiop.Constants {
  72 
  73     protected boolean alwaysGenerate = false;
  74     protected BatchEnvironment env = null;
  75     protected ContextStack contextStack = null;
  76     private boolean trace = false;
  77     protected boolean idl = false;
  78 
  79     /**
  80      * Examine and consume command line arguments.
  81      * @param argv The command line arguments. Ignore null
  82      * and unknown arguments. Set each consumed argument to null.
  83      * @param error Report any errors using the main.error() methods.
  84      * @return true if no errors, false otherwise.
  85      */
  86     public boolean parseArgs(String argv[], Main main) {
  87         for (int i = 0; i < argv.length; i++) {
  88             if (argv[i] != null) {
  89                 if (argv[i].equalsIgnoreCase("-always") ||
  90                     argv[i].equalsIgnoreCase("-alwaysGenerate")) {
  91                     alwaysGenerate = true;
  92                     argv[i] = null;
  93                 } else if (argv[i].equalsIgnoreCase("-xtrace")) {
  94                     trace = true;
  95                     argv[i] = null;
  96                 }
  97             }
  98         }
  99         return true;
 100     }
 101 
 102     /**
 103      * Return true if non-conforming types should be parsed.
 104      * @param stack The context stack.
 105      */
 106     protected abstract boolean parseNonConforming(ContextStack stack);
 107 
 108     /**
 109      * Create and return a top-level type.
 110      * @param cdef The top-level class definition.
 111      * @param stack The context stack.
 112      * @return The compound type or null if is non-conforming.
 113      */
 114     protected abstract CompoundType getTopType(ClassDefinition cdef, ContextStack stack);
 115 
 116     /**
 117      * Return an array containing all the file names and types that need to be
 118      * generated for the given top-level type.  The file names must NOT have an
 119      * extension (e.g. ".java").
 120      * @param topType The type returned by getTopType().
 121      * @param alreadyChecked A set of Types which have already been checked.
 122      *  Intended to be passed to Type.collectMatching(filter,alreadyChecked).
 123      */
 124     protected abstract OutputType[] getOutputTypesFor(CompoundType topType,
 125                                                       HashSet alreadyChecked);
 126 
 127     /**
 128      * Return the file name extension for the given file name (e.g. ".java").
 129      * All files generated with the ".java" extension will be compiled. To
 130      * change this behavior for ".java" files, override the compileJavaSourceFile
 131      * method to return false.
 132      * @param outputType One of the items returned by getOutputTypesFor(...)
 133      */
 134     protected abstract String getFileNameExtensionFor(OutputType outputType);
 135 
 136     /**
 137      * Write the output for the given OutputFileName into the output stream.
 138      * @param name One of the items returned by getOutputTypesFor(...)
 139      * @param alreadyChecked A set of Types which have already been checked.
 140      *  Intended to be passed to Type.collectMatching(filter,alreadyChecked).
 141      * @param writer The output stream.
 142      */
 143     protected abstract void writeOutputFor(OutputType outputType,
 144                                                 HashSet alreadyChecked,
 145                                                 IndentingWriter writer) throws IOException;
 146 
 147     /**
 148      * Return true if a new instance should be created for each
 149      * class on the command line. Subclasses which return true
 150      * should override newInstance() to return an appropriately
 151      * constructed instance.
 152      */
 153     protected abstract boolean requireNewInstance();
 154 
 155     /**
 156      * Return true if the specified file needs generation.
 157      */
 158     public boolean requiresGeneration (File target, Type theType) {
 159 
 160         boolean result = alwaysGenerate;
 161 
 162         if (!result) {
 163 
 164             // Get a ClassFile instance for base source or class
 165             // file.  We use ClassFile so that if the base is in
 166             // a zip file, we can still get at it's mod time...
 167 
 168             ClassFile baseFile;
 169             ClassPath path = env.getClassPath();
 170             String className = theType.getQualifiedName().replace('.',File.separatorChar);
 171 
 172             // First try the source file...
 173 
 174             baseFile = path.getFile(className + ".source");
 175 
 176             if (baseFile == null) {
 177 
 178                 // Then try class file...
 179 
 180                 baseFile = path.getFile(className + ".class");
 181             }
 182 
 183             // Do we have a baseFile?
 184 
 185             if (baseFile != null) {
 186 
 187                 // Yes, grab baseFile's mod time...
 188 
 189                 long baseFileMod = baseFile.lastModified();
 190 
 191                 // Get a File instance for the target. If it is a source
 192                 // file, create a class file instead since the source file
 193                 // will frequently be deleted...
 194 
 195                 String targetName = IDLNames.replace(target.getName(),".java",".class");
 196                 String parentPath = target.getParent();
 197                 File targetFile = new File(parentPath,targetName);
 198 
 199                 // Does the target file exist?
 200 
 201                 if (targetFile.exists()) {
 202 
 203                     // Yes, so grab it's mod time...
 204 
 205                     long targetFileMod = targetFile.lastModified();
 206 
 207                     // Set result...
 208 
 209                     result = targetFileMod < baseFileMod;
 210 
 211                 } else {
 212 
 213                     // No, so we must generate...
 214 
 215                     result = true;
 216                 }
 217             } else {
 218 
 219                 // No, so we must generate...
 220 
 221                 result = true;
 222             }
 223         }
 224 
 225         return result;
 226     }
 227 
 228     /**
 229      * Create and return a new instance of self. Subclasses
 230      * which need to do something other than default construction
 231      * must override this method.
 232      */
 233     protected Generator newInstance() {
 234         Generator result = null;
 235         try {
 236             result = (Generator) getClass().newInstance();
 237         }
 238         catch (Exception e){} // Should ALWAYS work!
 239 
 240         return result;
 241     }
 242 
 243     /**
 244      * Default constructor for subclasses to use.
 245      */
 246     protected Generator() {
 247     }
 248 
 249     /**
 250      * Generate output. Any source files created which need compilation should
 251      * be added to the compiler environment using the addGeneratedFile(File)
 252      * method.
 253      *
 254      * @param env       The compiler environment
 255      * @param cdef      The definition for the implementation class or interface from
 256      *              which to generate output
 257      * @param destDir   The directory for the root of the package hierarchy
 258      *                          for generated files. May be null.
 259      */
 260     public void generate(sun.rmi.rmic.BatchEnvironment env, ClassDefinition cdef, File destDir) {
 261 
 262         this.env = (BatchEnvironment) env;
 263         contextStack = new ContextStack(this.env);
 264         contextStack.setTrace(trace);
 265 
 266         // Make sure the environment knows whether or not to parse
 267         // non-conforming types. This will clear out any previously
 268         // parsed types if necessary...
 269 
 270         this.env.setParseNonConforming(parseNonConforming(contextStack));
 271 
 272         // Get our top level type...
 273 
 274         CompoundType topType = getTopType(cdef,contextStack);
 275         if (topType != null) {
 276 
 277             Generator generator = this;
 278 
 279             // Do we need to make a new instance?
 280 
 281             if (requireNewInstance()) {
 282 
 283                                 // Yes, so make one.  'this' instance is the one instantiated by Main
 284                                 // and which knows any needed command line args...
 285 
 286                 generator = newInstance();
 287             }
 288 
 289             // Now generate all output files...
 290 
 291             generator.generateOutputFiles(topType, this.env, destDir);
 292         }
 293     }
 294 
 295     /**
 296      * Create and return a new instance of self. Subclasses
 297      * which need to do something other than default construction
 298      * must override this method.
 299      */
 300     protected void generateOutputFiles (CompoundType topType,
 301                                         BatchEnvironment env,
 302                                         File destDir) {
 303 
 304         // Grab the 'alreadyChecked' HashSet from the environment...
 305 
 306         HashSet alreadyChecked = env.alreadyChecked;
 307 
 308         // Ask subclass for a list of output types...
 309 
 310         OutputType[] types = getOutputTypesFor(topType,alreadyChecked);
 311 
 312         // Process each file...
 313 
 314         for (int i = 0; i < types.length; i++) {
 315             OutputType current = types[i];
 316             String className = current.getName();
 317             File file = getFileFor(current,destDir);
 318             boolean sourceFile = false;
 319 
 320             // Do we need to generate this file?
 321 
 322             if (requiresGeneration(file,current.getType())) {
 323 
 324                 // Yes. If java source file, add to environment so will be compiled...
 325 
 326                 if (file.getName().endsWith(".java")) {
 327                     sourceFile = compileJavaSourceFile(current);
 328 
 329                                 // Are we supposeded to compile this one?
 330 
 331                     if (sourceFile) {
 332                         env.addGeneratedFile(file);
 333                     }
 334                 }
 335 
 336                 // Now create an output stream and ask subclass to fill it up...
 337 
 338                 try {
 339                    IndentingWriter out = new IndentingWriter(
 340                                                               new OutputStreamWriter(new FileOutputStream(file)),INDENT_STEP,TAB_SIZE);
 341 
 342                     long startTime = 0;
 343                     if (env.verbose()) {
 344                         startTime = System.currentTimeMillis();
 345                     }
 346 
 347                     writeOutputFor(types[i],alreadyChecked,out);
 348                     out.close();
 349 
 350                     if (env.verbose()) {
 351                         long duration = System.currentTimeMillis() - startTime;
 352                         env.output(Main.getText("rmic.generated", file.getPath(), Long.toString(duration)));
 353                     }
 354                     if (sourceFile) {
 355                         env.parseFile(ClassFile.newClassFile(file));
 356                     }
 357                 } catch (IOException e) {
 358                     env.error(0, "cant.write", file.toString());
 359                     return;
 360                 }
 361             } else {
 362 
 363                 // No, say so if we need to...
 364 
 365                 if (env.verbose()) {
 366                     env.output(Main.getText("rmic.previously.generated", file.getPath()));
 367                 }
 368             }
 369         }
 370     }
 371 
 372     /**
 373      * Return the File object that should be used as the output file
 374      * for the given OutputType.
 375      * @param outputType The type to create a file for.
 376      * @param destinationDir The directory to use as the root of the
 377      * package heirarchy.  May be null, in which case the current
 378      * classpath is searched to find the directory in which to create
 379      * the output file.  If that search fails (most likely because the
 380      * package directory lives in a zip or jar file rather than the
 381      * file system), the current user directory is used.
 382      */
 383     protected File getFileFor(OutputType outputType, File destinationDir) {
 384         // Calling this method does some crucial initialization
 385         // in a subclass implementation. Don't skip it.
 386         Identifier id = getOutputId(outputType);
 387         File packageDir = null;
 388         if(idl){
 389             packageDir = Util.getOutputDirectoryForIDL(id,destinationDir,env);
 390         } else {
 391             packageDir = Util.getOutputDirectoryForStub(id,destinationDir,env);
 392         }
 393         String classFileName = outputType.getName() + getFileNameExtensionFor(outputType);
 394         return new File(packageDir, classFileName);
 395     }
 396 
 397     /**
 398      * Return an identifier to use for output.
 399      * @param outputType the type for which output is to be generated.
 400      * @return the new identifier. This implementation returns the input parameter.
 401      */
 402     protected Identifier getOutputId (OutputType outputType) {
 403         return outputType.getType().getIdentifier();
 404     }
 405 
 406     /**
 407      * Return true if the given file should be compiled.
 408      * @param outputType One of the items returned by getOutputTypesFor(...) for
 409      *   which getFileNameExtensionFor(OutputType) returned ".java".
 410      */
 411     protected boolean compileJavaSourceFile (OutputType outputType) {
 412         return true;
 413     }
 414 
 415     //_____________________________________________________________________
 416     // OutputType is a simple wrapper for a name and a Type
 417     //_____________________________________________________________________
 418 
 419     public class OutputType {
 420         private String name;
 421         private Type type;
 422 
 423         public OutputType (String name, Type type) {
 424             this.name = name;
 425             this.type = type;
 426         }
 427 
 428         public String getName() {
 429             return name;
 430         }
 431 
 432         public Type getType() {
 433             return type;
 434         }
 435     }
 436 }