001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2001-2007, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino;
035:
036: import java.util.*;
037: import java.io.*;
038:
039: import org.codehaus.janino.Parser.ParseException;
040: import org.codehaus.janino.Scanner.ScanException;
041: import org.codehaus.janino.UnitCompiler.ErrorHandler;
042: import org.codehaus.janino.tools.Disassembler;
043: import org.codehaus.janino.util.*;
044: import org.codehaus.janino.util.enumerator.*;
045: import org.codehaus.janino.util.resource.*;
046:
047: /**
048: * A simplified substitute for the <tt>javac</tt> tool.
049: *
050: * Usage:
051: * <pre>
052: * java org.codehaus.janino.Compiler \
053: * [ -d <i>destination-dir</i> ] \
054: * [ -sourcepath <i>dirlist</i> ] \
055: * [ -classpath <i>dirlist</i> ] \
056: * [ -extdirs <i>dirlist</i> ] \
057: * [ -bootclasspath <i>dirlist</i> ] \
058: * [ -encoding <i>encoding</i> ] \
059: * [ -verbose ] \
060: * [ -g:none ] \
061: * [ -g:{lines,vars,source} ] \
062: * [ -warn:<i>pattern-list</i> ] \
063: * <i>source-file</i> ...
064: * java org.codehaus.janino.Compiler -help
065: * </pre>
066: */
067: public class Compiler {
068: private static final boolean DEBUG = false;
069:
070: /**
071: * Command line interface.
072: */
073: public static void main(String[] args) {
074: File destinationDirectory = Compiler.NO_DESTINATION_DIRECTORY;
075: File[] optionalSourcePath = null;
076: File[] classPath = { new File(".") };
077: File[] optionalExtDirs = null;
078: File[] optionalBootClassPath = null;
079: String optionalCharacterEncoding = null;
080: boolean verbose = false;
081: EnumeratorSet debuggingInformation = DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION;
082: StringPattern[] warningHandlePatterns = Compiler.DEFAULT_WARNING_HANDLE_PATTERNS;
083: boolean rebuild = false;
084:
085: // Process command line options.
086: int i;
087: for (i = 0; i < args.length; ++i) {
088: String arg = args[i];
089: if (arg.charAt(0) != '-')
090: break;
091: if (arg.equals("-d")) {
092: destinationDirectory = new File(args[++i]);
093: } else if (arg.equals("-sourcepath")) {
094: optionalSourcePath = PathResourceFinder
095: .parsePath(args[++i]);
096: } else if (arg.equals("-classpath")) {
097: classPath = PathResourceFinder.parsePath(args[++i]);
098: } else if (arg.equals("-extdirs")) {
099: optionalExtDirs = PathResourceFinder
100: .parsePath(args[++i]);
101: } else if (arg.equals("-bootclasspath")) {
102: optionalBootClassPath = PathResourceFinder
103: .parsePath(args[++i]);
104: } else if (arg.equals("-encoding")) {
105: optionalCharacterEncoding = args[++i];
106: } else if (arg.equals("-verbose")) {
107: verbose = true;
108: } else if (arg.equals("-g")) {
109: debuggingInformation = DebuggingInformation.ALL;
110: } else if (arg.startsWith("-g:")) {
111: try {
112: debuggingInformation = new EnumeratorSet(
113: DebuggingInformation.class, arg
114: .substring(3));
115: } catch (EnumeratorFormatException ex) {
116: System.err.println("Invalid debugging option \""
117: + arg + "\", only \""
118: + DebuggingInformation.ALL + "\" allowed");
119: System.exit(1);
120: }
121: } else if (arg.startsWith("-warn:")) {
122: warningHandlePatterns = StringPattern
123: .parseCombinedPattern(arg.substring(6));
124: } else if (arg.equals("-rebuild")) {
125: rebuild = true;
126: } else if (arg.equals("-help")) {
127: for (int j = 0; j < Compiler.USAGE.length; ++j)
128: System.out.println(Compiler.USAGE[j]);
129: System.exit(1);
130: } else {
131: System.err
132: .println("Unrecognized command line option \""
133: + arg + "\"; try \"-help\".");
134: System.exit(1);
135: }
136: }
137:
138: // Get source file names.
139: if (i == args.length) {
140: System.err
141: .println("No source files given on command line; try \"-help\".");
142: System.exit(1);
143: }
144: File[] sourceFiles = new File[args.length - i];
145: for (int j = i; j < args.length; ++j)
146: sourceFiles[j - i] = new File(args[j]);
147:
148: // Create the compiler object.
149: final Compiler compiler = new Compiler(optionalSourcePath,
150: classPath, optionalExtDirs, optionalBootClassPath,
151: destinationDirectory, optionalCharacterEncoding,
152: verbose, debuggingInformation, warningHandlePatterns,
153: rebuild);
154:
155: // Compile source files.
156: try {
157: compiler.compile(sourceFiles);
158: } catch (Exception e) {
159: System.err.println(e.toString());
160: System.exit(1);
161: }
162: }
163:
164: private static final String[] USAGE = {
165: "Usage:",
166: "",
167: " java " + Compiler.class.getName()
168: + " [ <option> ] ... <source-file> ...",
169: "",
170: "Supported <option>s are:",
171: " -d <output-dir> Where to save class files",
172: " -sourcepath <dirlist> Where to look for other source files",
173: " -classpath <dirlist> Where to look for other class files",
174: " -extdirs <dirlist> Where to look for other class files",
175: " -bootclasspath <dirlist> Where to look for other class files",
176: " -encoding <encoding> Encoding of source files, e.g. \"UTF-8\" or \"ISO-8859-1\"",
177: " -verbose",
178: " -g Generate all debugging info",
179: " -g:none Generate no debugging info",
180: " -g:{lines,vars,source} Generate only some debugging info",
181: " -warn:<pattern-list> Issue certain warnings; examples:",
182: " -warn:* Enables all warnings",
183: " -warn:IASF Only warn against implicit access to static fields",
184: " -warn:*-IASF Enables all warnings, except those against implicit",
185: " access to static fields",
186: " -warn:*-IA*+IASF Enables all warnings, except those against implicit",
187: " accesses, but do warn against implicit access to",
188: " static fields",
189: " -rebuild Compile all source files, even if the class files",
190: " seems up-to-date",
191: " -help",
192: "",
193: "The default encoding in this environment is \""
194: + new InputStreamReader(new ByteArrayInputStream(
195: new byte[0])).getEncoding() + "\".", };
196:
197: private/*final*/ResourceFinder classFileFinder;
198: public static final ResourceFinder FIND_NEXT_TO_SOURCE_FILE = null; // Special value for "classFileResourceFinder".
199: private/*final*/ResourceCreator classFileCreator;
200: public static final ResourceCreator CREATE_NEXT_TO_SOURCE_FILE = null; // Special value for "classFileResourceCreator".
201: private/*final*/String optionalCharacterEncoding;
202: private/*final*/Benchmark benchmark;
203: private/*final*/EnumeratorSet debuggingInformation;
204: private/*final*/WarningHandler optionalWarningHandler;
205: private UnitCompiler.ErrorHandler optionalCompileErrorHandler = null;
206:
207: private/*final*/IClassLoader iClassLoader;
208: private final ArrayList parsedCompilationUnits = new ArrayList(); // UnitCompiler
209:
210: /**
211: * Initialize a Java<sup>TM</sup> compiler with the given parameters.
212: * <p>
213: * Classes are searched in the following order:
214: * <ul>
215: * <li>If <code>optionalBootClassPath</code> is <code>null</code>:
216: * <ul>
217: * <li>Through the system class loader of the JVM that runs JANINO
218: * </ul>
219: * <li>If <code>optionalBootClassPath</code> is not <code>null</code>:
220: * <ul>
221: * <li>Through the <code>optionalBootClassPath</code>
222: * </ul>
223: * <li>If <code>optionalExtDirs</code> is not <code>null</code>:
224: * <ul>
225: * <li>Through the <code>optionalExtDirs</code>
226: * </ul>
227: * <li>Through the <code>classPath</code>
228: * <li>If <code>optionalSourcePath</code> is <code>null</code>:
229: * <ul>
230: * <li>Through source files found on the <code>classPath</code>
231: * </ul>
232: * <li>If <code>optionalSourcePath</code> is not <code>null</code>:
233: * <ul>
234: * <li>Through source files found on the <code>sourcePath</code>
235: * </ul>
236: * </ul>
237: * <p>
238: * The file name of a class file that represents class "pkg.Example"
239: * is determined as follows:
240: * <ul>
241: * <li>
242: * If <code>optionalDestinationDirectory</code> is not {@link #NO_DESTINATION_DIRECTORY}:
243: * <code><i>optionalDestinationDirectory</i>/pkg/Example.class</code>
244: * <li>
245: * If <code>optionalDestinationDirectory</code> is {@link #NO_DESTINATION_DIRECTORY}:
246: * <code>dir1/dir2/Example.class</code> (Assuming that the file name of the
247: * source file that declares the class was
248: * <code>dir1/dir2/Any.java</code>.)
249: * </ul>
250: *
251: * @see #DEFAULT_WARNING_HANDLE_PATTERNS
252: */
253: public Compiler(final File[] optionalSourcePath,
254: final File[] classPath, final File[] optionalExtDirs,
255: final File[] optionalBootClassPath,
256: final File destinationDirectory,
257: final String optionalCharacterEncoding, boolean verbose,
258: EnumeratorSet debuggingInformation,
259: StringPattern[] warningHandlePatterns, boolean rebuild) {
260: this (
261: new PathResourceFinder( // sourceFinder
262: optionalSourcePath == null ? classPath
263: : optionalSourcePath),
264: Compiler.createJavacLikePathIClassLoader(
265: // iClassLoader
266: optionalBootClassPath, optionalExtDirs,
267: classPath),
268: ( // classFileFinder
269: rebuild ? ResourceFinder.EMPTY_RESOURCE_FINDER
270: : destinationDirectory == Compiler.NO_DESTINATION_DIRECTORY ? Compiler.FIND_NEXT_TO_SOURCE_FILE
271: : new DirectoryResourceFinder(
272: destinationDirectory)),
273: ( // classFileCreator
274: destinationDirectory == Compiler.NO_DESTINATION_DIRECTORY ? Compiler.CREATE_NEXT_TO_SOURCE_FILE
275: : new DirectoryResourceCreator(
276: destinationDirectory)),
277: optionalCharacterEncoding, // optionalCharacterEncoding
278: verbose, // verbose
279: debuggingInformation, // debuggingInformation
280: new FilterWarningHandler(
281: // optionalWarningHandler
282: warningHandlePatterns,
283: new SimpleWarningHandler() // <= Anonymous class here is complicated because the enclosing instance is not fully initialized yet
284: ));
285:
286: this .benchmark
287: .report("*** JANINO - an embedded compiler for the Java(TM) programming language");
288: this .benchmark
289: .report("*** For more information visit http://janino.codehaus.org");
290: this .benchmark.report("Source path", optionalSourcePath);
291: this .benchmark.report("Class path", classPath);
292: this .benchmark.report("Ext dirs", optionalExtDirs);
293: this .benchmark.report("Boot class path", optionalBootClassPath);
294: this .benchmark.report("Destination directory",
295: destinationDirectory);
296: this .benchmark.report("Character encoding",
297: optionalCharacterEncoding);
298: this .benchmark.report("Verbose", new Boolean(verbose));
299: this .benchmark.report("Debugging information",
300: debuggingInformation);
301: this .benchmark.report("Warning handle patterns",
302: warningHandlePatterns);
303: this .benchmark.report("Rebuild", new Boolean(rebuild));
304: }
305:
306: public static final File NO_DESTINATION_DIRECTORY = null; // Backwards compatibility -- previously, "null" was officially documented
307:
308: public static class SimpleWarningHandler implements WarningHandler {
309: public void handleWarning(String handle, String message,
310: Location optionalLocation) {
311: StringBuffer sb = new StringBuffer();
312: if (optionalLocation != null)
313: sb.append(optionalLocation).append(": ");
314: sb.append("Warning ").append(handle).append(": ").append(
315: message);
316: System.err.println(sb.toString());
317: }
318: }
319:
320: public static final StringPattern[] DEFAULT_WARNING_HANDLE_PATTERNS = StringPattern.PATTERNS_NONE;
321:
322: /**
323: * To mimic the behavior of JAVAC with a missing "-d" command line option,
324: * pass {@link #FIND_NEXT_TO_SOURCE_FILE} as the <code>classFileResourceFinder</code> and
325: * {@link #CREATE_NEXT_TO_SOURCE_FILE} as the <code>classFileResourceCreator</code>.
326: * <p>
327: * If it is impossible to check whether an already-compiled class file
328: * exists, or if you want to enforce recompilation, pass
329: * {@link ResourceFinder#EMPTY_RESOURCE_FINDER} as the
330: * <code>classFileResourceFinder</code>.
331: *
332: * @param sourceFinder Finds extra Java compilation units that need to be compiled (a.k.a. "sourcepath")
333: * @param iClassLoader loads auxiliary {@link IClass}es; e.g. <code>new ClassLoaderIClassLoader(ClassLoader)</code>
334: * @param classFileFinder Where to look for up-to-date class files that need not be compiled
335: * @param classFileCreator Used to store generated class files
336: * @param optionalCharacterEncoding
337: * @param verbose
338: * @param debuggingInformation a combination of <code>Java.DEBUGGING_...</code>
339: * @param optionalWarningHandler used to issue warnings
340: */
341: public Compiler(ResourceFinder sourceFinder,
342: IClassLoader iClassLoader, ResourceFinder classFileFinder,
343: ResourceCreator classFileCreator,
344: final String optionalCharacterEncoding, boolean verbose,
345: EnumeratorSet debuggingInformation,
346: WarningHandler optionalWarningHandler) {
347: this .classFileFinder = classFileFinder;
348: this .classFileCreator = classFileCreator;
349: this .optionalCharacterEncoding = optionalCharacterEncoding;
350: this .benchmark = new Benchmark(verbose);
351: this .debuggingInformation = debuggingInformation;
352: this .optionalWarningHandler = optionalWarningHandler;
353:
354: // Set up the IClassLoader.
355: this .iClassLoader = new CompilerIClassLoader(sourceFinder,
356: iClassLoader);
357: }
358:
359: /**
360: * Install a custom {@link UnitCompiler.ErrorHandler}. The default
361: * {@link UnitCompiler.ErrorHandler} prints the first 20 compile errors to
362: * {@link System#err} and then throws a {@link CompileException}.
363: * <p>
364: * Passing <code>null</code> restores the default {@link UnitCompiler.ErrorHandler}.
365: * <p>
366: * Notice that scan and parse errors are <i>not</i> redirected to this {@link ErrorHandler},
367: * instead, they cause a {@link ScanException} or a {@link ParseException} to be thrown.
368: * Also, the {@link Compiler} may choose to throw {@link CompileException}s in certain,
369: * fatal compile error situations, even if an {@link ErrorHandler} is installed.
370: * <p>
371: * In other words: In situations where compilation can reasonably continue after a compile
372: * error, the {@link ErrorHandler} is called; all other error conditions cause a
373: * {@link CompileException}, {@link ParseException} or {@link ScanException} to be thrown.
374: */
375: public void setCompileErrorHandler(
376: UnitCompiler.ErrorHandler optionalCompileErrorHandler) {
377: this .optionalCompileErrorHandler = optionalCompileErrorHandler;
378: }
379:
380: /**
381: * Create an {@link IClassLoader} that looks for classes in the given "boot class
382: * path", then in the given "extension directories", and then in the given
383: * "class path".
384: * <p>
385: * The default for the <code>optionalBootClassPath</code> is the path defined in
386: * the system property "sun.boot.class.path", and the default for the
387: * <code>optionalExtensionDirs</code> is the path defined in the "java.ext.dirs"
388: * system property.
389: */
390: private static IClassLoader createJavacLikePathIClassLoader(
391: final File[] optionalBootClassPath,
392: final File[] optionalExtDirs, final File[] classPath) {
393: ResourceFinder bootClassPathResourceFinder = new PathResourceFinder(
394: optionalBootClassPath == null ? PathResourceFinder
395: .parsePath(System
396: .getProperty("sun.boot.class.path"))
397: : optionalBootClassPath);
398: ResourceFinder extensionDirectoriesResourceFinder = new JarDirectoriesResourceFinder(
399: optionalExtDirs == null ? PathResourceFinder
400: .parsePath(System.getProperty("java.ext.dirs"))
401: : optionalExtDirs);
402: ResourceFinder classPathResourceFinder = new PathResourceFinder(
403: classPath);
404:
405: // We can load classes through "ResourceFinderIClassLoader"s, which means
406: // they are read into "ClassFile" objects, or we can load classes through
407: // "ClassLoaderIClassLoader"s, which means they are loaded into the JVM.
408: //
409: // In my environment, the latter is slightly faster. No figures about
410: // resource usage yet.
411: //
412: // In applications where the generated classes are not loaded into the
413: // same JVM instance, we should avoid to use the
414: // ClassLoaderIClassLoader, because that assumes that final fields have
415: // a constant value, even if not compile-time-constant but only
416: // initialization-time constant. The classical example is
417: // "File.separator", which is non-blank final, but not compile-time-
418: // constant.
419: if (true) {
420: IClassLoader icl;
421: icl = new ResourceFinderIClassLoader(
422: bootClassPathResourceFinder, null);
423: icl = new ResourceFinderIClassLoader(
424: extensionDirectoriesResourceFinder, icl);
425: icl = new ResourceFinderIClassLoader(
426: classPathResourceFinder, icl);
427: return icl;
428: } else {
429: ClassLoader cl;
430:
431: cl = SimpleCompiler.BOOT_CLASS_LOADER;
432: cl = new ResourceFinderClassLoader(
433: bootClassPathResourceFinder, cl);
434: cl = new ResourceFinderClassLoader(
435: extensionDirectoriesResourceFinder, cl);
436: cl = new ResourceFinderClassLoader(classPathResourceFinder,
437: cl);
438:
439: return new ClassLoaderIClassLoader(cl);
440: }
441: }
442:
443: /**
444: * Reads a set of Java<sup>TM</sup> compilation units (a.k.a. "source
445: * files") from the file system, compiles them into a set of "class
446: * files" and stores these in the file system. Additional source files are
447: * parsed and compiled on demand through the "source path" set of
448: * directories.
449: * <p>
450: * For example, if the source path comprises the directories "A/B" and "../C",
451: * then the source file for class "com.acme.Main" is searched in
452: * <dl>
453: * <dd>A/B/com/acme/Main.java
454: * <dd>../C/com/acme/Main.java
455: * </dl>
456: * Notice that it does make a difference whether you pass multiple source
457: * files to {@link #compile(File[])} or if you invoke
458: * {@link #compile(File[])} multiply: In the former case, the source
459: * files may contain arbitrary references among each other (even circular
460: * ones). In the latter case, only the source files on the source path
461: * may contain circular references, not the <code>sourceFiles</code>.
462: * <p>
463: * This method must be called exactly once after object construction.
464: * <p>
465: * Compile errors are reported as described at
466: * {@link #setCompileErrorHandler(UnitCompiler.ErrorHandler)}.
467: *
468: * @param sourceFiles Contain the compilation units to compile
469: * @return <code>true</code> for backwards compatibility (return value can safely be ignored)
470: * @throws CompileException Fatal compilation error, or the {@link CompileException} thrown be the installed compile error handler
471: * @throws ParseException Parse error
472: * @throws ScanException Scan error
473: * @throws IOException Occurred when reading from the <code>sourceFiles</code>
474: */
475: public boolean compile(File[] sourceFiles)
476: throws Scanner.ScanException, Parser.ParseException,
477: CompileException, IOException {
478: this .benchmark.report("Source files", sourceFiles);
479:
480: Resource[] sourceFileResources = new Resource[sourceFiles.length];
481: for (int i = 0; i < sourceFiles.length; ++i)
482: sourceFileResources[i] = new FileResource(sourceFiles[i]);
483: this .compile(sourceFileResources);
484: return true;
485: }
486:
487: /**
488: * See {@link #compile(File[])}.
489: *
490: * @param sourceResources Contain the compilation units to compile
491: * @return <code>true</code> for backwards compatibility (return value can safely be ignored)
492: */
493: public boolean compile(Resource[] sourceResources)
494: throws Scanner.ScanException, Parser.ParseException,
495: CompileException, IOException {
496:
497: // Set up the compile error handler as described at "setCompileErrorHandler()".
498: UnitCompiler.ErrorHandler ceh = (this .optionalCompileErrorHandler != null ? this .optionalCompileErrorHandler
499: : new UnitCompiler.ErrorHandler() {
500: int compileErrorCount = 0;
501:
502: public void handleError(String message,
503: Location optionalLocation)
504: throws CompileException {
505: CompileException ex = new CompileException(
506: message, optionalLocation);
507: if (++this .compileErrorCount >= 20)
508: throw ex;
509: System.err.println(ex.getMessage());
510: }
511: });
512:
513: this .benchmark.beginReporting();
514: try {
515:
516: // Parse all source files.
517: this .parsedCompilationUnits.clear();
518: for (int i = 0; i < sourceResources.length; ++i) {
519: if (Compiler.DEBUG)
520: System.out.println("Compiling \""
521: + sourceResources[i] + "\"");
522: this .parsedCompilationUnits.add(new UnitCompiler(this
523: .parseCompilationUnit(sourceResources[i]
524: .getFileName(), // fileName
525: new BufferedInputStream(
526: sourceResources[i].open()), // inputStream
527: this .optionalCharacterEncoding // optionalCharacterEncoding
528: ), this .iClassLoader));
529: }
530:
531: // Compile all parsed compilation units. The vector of parsed CUs may
532: // grow while they are being compiled, but eventually all CUs will
533: // be compiled.
534: for (int i = 0; i < this .parsedCompilationUnits.size(); ++i) {
535: UnitCompiler unitCompiler = (UnitCompiler) this .parsedCompilationUnits
536: .get(i);
537: Java.CompilationUnit cu = unitCompiler.compilationUnit;
538: if (cu.optionalFileName == null)
539: throw new RuntimeException();
540: File sourceFile = new File(cu.optionalFileName);
541:
542: unitCompiler.setCompileErrorHandler(ceh);
543: unitCompiler
544: .setWarningHandler(this .optionalWarningHandler);
545:
546: this .benchmark
547: .beginReporting("Compiling compilation unit \""
548: + sourceFile + "\"");
549: ClassFile[] classFiles;
550: try {
551:
552: // Compile the compilation unit.
553: classFiles = unitCompiler
554: .compileUnit(this .debuggingInformation);
555: } finally {
556: this .benchmark.endReporting();
557: }
558:
559: // Store the compiled classes and interfaces into class files.
560: this .benchmark
561: .beginReporting("Storing "
562: + classFiles.length
563: + " class file(s) resulting from compilation unit \""
564: + sourceFile + "\"");
565: try {
566: for (int j = 0; j < classFiles.length; ++j) {
567: this .storeClassFile(classFiles[j], sourceFile);
568: }
569: } finally {
570: this .benchmark.endReporting();
571: }
572: }
573: } finally {
574: this .benchmark.endReporting("Compiled "
575: + this .parsedCompilationUnits.size()
576: + " compilation unit(s)");
577: }
578: return true;
579: }
580:
581: /**
582: * Read one compilation unit from a file and parse it.
583: * <p>
584: * The <code>inputStream</code> is closed before the method returns.
585: * @return the parsed compilation unit
586: */
587: private Java.CompilationUnit parseCompilationUnit(String fileName,
588: InputStream inputStream, String optionalCharacterEncoding)
589: throws Scanner.ScanException, Parser.ParseException,
590: IOException {
591: try {
592: Scanner scanner = new Scanner(fileName, inputStream,
593: optionalCharacterEncoding);
594: scanner.setWarningHandler(this .optionalWarningHandler);
595: Parser parser = new Parser(scanner);
596: parser.setWarningHandler(this .optionalWarningHandler);
597:
598: this .benchmark.beginReporting("Parsing \"" + fileName
599: + "\"");
600: try {
601: return parser.parseCompilationUnit();
602: } finally {
603: this .benchmark.endReporting();
604: }
605: } finally {
606: inputStream.close();
607: }
608: }
609:
610: /**
611: * Construct the name of a file that could store the byte code of the class with the given
612: * name.
613: * <p>
614: * If <code>optionalDestinationDirectory</code> is non-null, the returned path is the
615: * <code>optionalDestinationDirectory</code> plus the package of the class (with dots replaced
616: * with file separators) plus the class name plus ".class". Example:
617: * "destdir/pkg1/pkg2/Outer$Inner.class"
618: * <p>
619: * If <code>optionalDestinationDirectory</code> is null, the returned path is the
620: * directory of the <code>sourceFile</code> plus the class name plus ".class". Example:
621: * "srcdir/Outer$Inner.class"
622: * @param className E.g. "pkg1.pkg2.Outer$Inner"
623: * @param sourceFile E.g. "srcdir/Outer.java"
624: * @param optionalDestinationDirectory E.g. "destdir"
625: */
626: public static File getClassFile(String className, File sourceFile,
627: File optionalDestinationDirectory) {
628: if (optionalDestinationDirectory != null) {
629: return new File(optionalDestinationDirectory, ClassFile
630: .getClassFileResourceName(className));
631: } else {
632: int idx = className.lastIndexOf('.');
633: return new File(sourceFile.getParentFile(), ClassFile
634: .getClassFileResourceName(className
635: .substring(idx + 1)));
636: }
637: }
638:
639: /**
640: * Store the byte code of this {@link ClassFile} in the file system. Directories are created
641: * as necessary.
642: * @param classFile
643: * @param sourceFile Required to compute class file path if no destination directory given
644: */
645: public void storeClassFile(ClassFile classFile,
646: final File sourceFile) throws IOException {
647: String classFileResourceName = ClassFile
648: .getClassFileResourceName(classFile.getThisClassName());
649:
650: // Determine where to create the class file.
651: ResourceCreator rc;
652: if (this .classFileCreator != Compiler.CREATE_NEXT_TO_SOURCE_FILE) {
653: rc = this .classFileCreator;
654: } else {
655:
656: // If the JAVAC option "-d" is given, place the class file next
657: // to the source file, irrespective of the package name.
658: rc = new FileResourceCreator() {
659: protected File getFile(String resourceName) {
660: return new File(sourceFile.getParentFile(),
661: resourceName.substring(resourceName
662: .lastIndexOf('/') + 1));
663: }
664: };
665: }
666: OutputStream os = rc.createResource(classFileResourceName);
667: try {
668: if (DEBUG) {
669: ByteArrayOutputStream baos = new ByteArrayOutputStream();
670: classFile.store(baos);
671: byte[] ba = baos.toByteArray();
672: System.out.println("*** Disassembly of class \""
673: + classFile.getThisClassName() + "\":");
674: try {
675: new Disassembler().disasm(new ByteArrayInputStream(
676: ba));
677: System.out.flush();
678: } catch (IOException ex) {
679: throw new RuntimeException(
680: "SNO: IOException despite ByteArrayInputStream");
681: }
682: os.write(ba);
683: } else {
684: classFile.store(os);
685: }
686: } catch (IOException ex) {
687: try {
688: os.close();
689: } catch (IOException e) {
690: }
691: os = null;
692: if (!rc.deleteResource(classFileResourceName))
693: throw new IOException(
694: "Could not delete incompletely written class file \""
695: + classFileResourceName + "\"");
696: throw ex;
697: } finally {
698: if (os != null)
699: try {
700: os.close();
701: } catch (IOException e) {
702: }
703: }
704: }
705:
706: /**
707: * A specialized {@link IClassLoader} that loads {@link IClass}es from the following
708: * sources:
709: * <ol>
710: * <li>An already-parsed compilation unit
711: * <li>A class file in the output directory (if existant and younger than source file)
712: * <li>A source file in any of the source path directories
713: * <li>The parent class loader
714: * </ol>
715: * Notice that the {@link CompilerIClassLoader} is an inner class of {@link Compiler} and
716: * heavily uses {@link Compiler}'s members.
717: */
718: private class CompilerIClassLoader extends IClassLoader {
719: private final ResourceFinder sourceFinder;
720:
721: /**
722: * @param sourceFinder Where to look for source files
723: * @param optionalParentIClassLoader {@link IClassLoader} through which {@link IClass}es are to be loaded
724: */
725: public CompilerIClassLoader(ResourceFinder sourceFinder,
726: IClassLoader optionalParentIClassLoader) {
727: super (optionalParentIClassLoader);
728: this .sourceFinder = sourceFinder;
729: super .postConstruct();
730: }
731:
732: /**
733: * @param type field descriptor of the {@IClass} to load, e.g. "Lpkg1/pkg2/Outer$Inner;"
734: * @return <code>null</code> if a the type could not be found
735: * @throws ClassNotFoundException if an exception was raised while loading the {@link IClass}
736: */
737: protected IClass findIClass(final String type)
738: throws ClassNotFoundException {
739: if (Compiler.DEBUG)
740: System.out.println("type = " + type);
741:
742: // Determine the class name.
743: String className = Descriptor.toClassName(type); // E.g. "pkg1.pkg2.Outer$Inner"
744: if (Compiler.DEBUG)
745: System.out.println("2 className = \"" + className
746: + "\"");
747:
748: // Do not attempt to load classes from package "java".
749: if (className.startsWith("java."))
750: return null;
751:
752: // Determine the name of the top-level class.
753: String topLevelClassName;
754: {
755: int idx = className.indexOf('$');
756: topLevelClassName = idx == -1 ? className : className
757: .substring(0, idx);
758: }
759:
760: // Check the already-parsed compilation units.
761: for (int i = 0; i < Compiler.this .parsedCompilationUnits
762: .size(); ++i) {
763: UnitCompiler uc = (UnitCompiler) Compiler.this .parsedCompilationUnits
764: .get(i);
765: IClass res = uc.findClass(topLevelClassName);
766: if (res != null) {
767: if (!className.equals(topLevelClassName)) {
768: res = uc.findClass(className);
769: if (res == null)
770: return null;
771: }
772: this .defineIClass(res);
773: return res;
774: }
775: }
776:
777: // Search source path for uncompiled class.
778: final Resource sourceResource = this .sourceFinder
779: .findResource(ClassFile
780: .getSourceResourceName(className));
781: if (sourceResource == null)
782: return null;
783:
784: // Find an existing class file.
785: Resource classFileResource;
786: if (Compiler.this .classFileFinder != Compiler.FIND_NEXT_TO_SOURCE_FILE) {
787: classFileResource = Compiler.this .classFileFinder
788: .findResource(ClassFile
789: .getClassFileResourceName(className));
790: } else {
791: if (!(sourceResource instanceof FileResource))
792: return null;
793: File classFile = new File(
794: ((FileResource) sourceResource).getFile()
795: .getParentFile(), ClassFile
796: .getClassFileResourceName(className
797: .substring(className
798: .lastIndexOf('.') + 1)));
799: classFileResource = classFile.exists() ? new FileResource(
800: classFile)
801: : null;
802: }
803:
804: // Compare source modification time against class file modification time.
805: if (classFileResource != null
806: && sourceResource.lastModified() <= classFileResource
807: .lastModified()) {
808:
809: // The class file is up-to-date; load it.
810: return this
811: .defineIClassFromClassFileResource(classFileResource);
812: } else {
813:
814: // Source file not yet compiled or younger than class file.
815: return this .defineIClassFromSourceResource(
816: sourceResource, className);
817: }
818: }
819:
820: /**
821: * Parse the compilation unit stored in the given <code>sourceResource</code>, remember it in
822: * <code>Compiler.this.parsedCompilationUnits</code> (it may declare other classes that
823: * are needed later), find the declaration of the type with the given
824: * <code>className</code>, and define it in the {@link IClassLoader}.
825: * <p>
826: * Notice that the CU is not compiled here!
827: */
828: private IClass defineIClassFromSourceResource(
829: Resource sourceResource, String className)
830: throws ClassNotFoundException {
831:
832: // Parse the source file.
833: UnitCompiler uc;
834: try {
835: Java.CompilationUnit cu = Compiler.this
836: .parseCompilationUnit(sourceResource
837: .getFileName(), // fileName
838: new BufferedInputStream(sourceResource
839: .open()), // inputStream
840: Compiler.this .optionalCharacterEncoding // optionalCharacterEncoding
841: );
842: uc = new UnitCompiler(cu, Compiler.this .iClassLoader);
843: } catch (IOException ex) {
844: throw new ClassNotFoundException(
845: "Parsing compilation unit \"" + sourceResource
846: + "\"", ex);
847: } catch (Parser.ParseException ex) {
848: throw new ClassNotFoundException(
849: "Parsing compilation unit \"" + sourceResource
850: + "\"", ex);
851: } catch (Scanner.ScanException ex) {
852: throw new ClassNotFoundException(
853: "Parsing compilation unit \"" + sourceResource
854: + "\"", ex);
855: } catch (CompileException ex) {
856: throw new ClassNotFoundException(
857: "Parsing compilation unit \"" + sourceResource
858: + "\"", ex);
859: }
860:
861: // Remember compilation unit for later compilation.
862: Compiler.this .parsedCompilationUnits.add(uc);
863:
864: // Define the class.
865: IClass res = uc.findClass(className);
866: if (res == null) {
867:
868: // This is a really complicated case: We may find a source file on the source
869: // path that seemingly contains the declaration of the class we are looking
870: // for, but doesn't. This is possible if the underlying file system has
871: // case-insensitive file names and/or file names that are limited in length
872: // (e.g. DOS 8.3).
873: return null;
874: }
875: this .defineIClass(res);
876: return res;
877: }
878:
879: /**
880: * Open the given <code>classFileResource</code>, read its contents, define it in the
881: * {@link IClassLoader}, and resolve it (this step may involve loading more classes).
882: */
883: private IClass defineIClassFromClassFileResource(
884: Resource classFileResource)
885: throws ClassNotFoundException {
886: Compiler.this .benchmark
887: .beginReporting("Loading class file \""
888: + classFileResource.getFileName() + "\"");
889: try {
890: InputStream is = null;
891: ClassFile cf;
892: try {
893: cf = new ClassFile(new BufferedInputStream(
894: classFileResource.open()));
895: } catch (IOException ex) {
896: throw new ClassNotFoundException(
897: "Opening class file resource \""
898: + classFileResource + "\"", ex);
899: } finally {
900: if (is != null)
901: try {
902: is.close();
903: } catch (IOException e) {
904: }
905: }
906: ClassFileIClass result = new ClassFileIClass(cf, // classFile
907: CompilerIClassLoader.this // iClassLoader
908: );
909:
910: // Important: We must FIRST call "defineIClass()" so that the
911: // new IClass is known to the IClassLoader, and THEN
912: // "resolveAllClasses()", because otherwise endless recursion could
913: // occur.
914: this.defineIClass(result);
915: result.resolveAllClasses();
916:
917: return result;
918: } finally {
919: Compiler.this.benchmark.endReporting();
920: }
921: }
922: }
923: }
|