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