Generator.java revision 636:1908b886ba1e
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
34package sun.rmi.rmic.iiop;
35
36import java.io.File;
37import java.io.FileOutputStream;
38import java.io.OutputStreamWriter;
39import java.io.IOException;
40import sun.tools.java.Identifier;
41import sun.tools.java.ClassPath;
42import sun.tools.java.ClassFile;
43import sun.tools.java.ClassNotFound;
44import sun.tools.java.ClassDefinition;
45import sun.tools.java.ClassDeclaration;
46import sun.rmi.rmic.IndentingWriter;
47import sun.rmi.rmic.Main;
48import sun.rmi.rmic.iiop.Util;
49import 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 */
70public 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}
437