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.io.*;
037: import java.lang.reflect.*;
038: import java.util.*;
039:
040: import org.codehaus.janino.tools.Disassembler;
041: import org.codehaus.janino.util.*;
042: import org.codehaus.janino.util.enumerator.*;
043:
044: /**
045: * A simplified version of {@link Compiler} that can compile only a single
046: * compilation unit. (A "compilation unit" is the characters stored in a
047: * ".java" file.)
048: * <p>
049: * Opposed to a normal ".java" file, you can declare multiple public classes
050: * here.
051: * <p>
052: * To set up a {@link SimpleCompiler} object, proceed as follows:
053: * <ol>
054: * <li>
055: * Create the {@link SimpleCompiler} using {@link #SimpleCompiler()}
056: * <li>
057: * Optionally set an alternate parent class loader through
058: * {@link #setParentClassLoader(ClassLoader)}.
059: * <li>
060: * Call any of the {@link org.codehaus.janino.Cookable#cook(Scanner)} methods to scan,
061: * parse, compile and load the compilation unit into the JVM.
062: * </ol>
063: * Alternatively, a number of "convenience constructors" exist that execute the steps described
064: * above instantly.
065: */
066: public class SimpleCompiler extends Cookable {
067: private final static boolean DEBUG = false;
068:
069: private ClassLoader parentClassLoader = Thread.currentThread()
070: .getContextClassLoader();
071: private Class[] optionalAuxiliaryClasses = null;
072:
073: // Set when "cook()"ing.
074: private AuxiliaryClassLoader classLoader = null;
075: private IClassLoader iClassLoader = null;
076:
077: private ClassLoader result = null;
078:
079: public static void main(String[] args) throws Exception {
080: if (args.length >= 1 && args[0].equals("-help")) {
081: System.out.println("Usage:");
082: System.out
083: .println(" org.codehaus.janino.SimpleCompiler <source-file> <class-name> { <argument> }");
084: System.out
085: .println("Reads a compilation unit from the given <source-file> and invokes method");
086: System.out
087: .println("\"public static void main(String[])\" of class <class-name>, passing the.");
088: System.out.println("given <argument>s.");
089: System.exit(1);
090: }
091:
092: if (args.length < 2) {
093: System.err
094: .println("Source file and/or class name missing; try \"-help\".");
095: System.exit(1);
096: }
097:
098: // Get source file.
099: String sourceFileName = args[0];
100:
101: // Get class name.
102: String className = args[1];
103:
104: // Get arguments.
105: String[] arguments = new String[args.length - 2];
106: System.arraycopy(args, 2, arguments, 0, arguments.length);
107:
108: // Compile the source file.
109: ClassLoader cl = new SimpleCompiler(sourceFileName,
110: new FileInputStream(sourceFileName)).getClassLoader();
111:
112: // Load the class.
113: Class c = cl.loadClass(className);
114:
115: // Invoke the "public static main(String[])" method.
116: Method m = c.getMethod("main", new Class[] { String[].class });
117: m.invoke(null, new Object[] { arguments });
118: }
119:
120: /**
121: * Equivalent to<pre>
122: * SimpleCompiler sc = new SimpleCompiler();
123: * sc.cook(optionalFileName, in);</pre>
124: *
125: * @see #SimpleCompiler()
126: * @see Cookable#cook(String, Reader)
127: */
128: public SimpleCompiler(String optionalFileName, Reader in)
129: throws IOException, Scanner.ScanException,
130: Parser.ParseException, CompileException {
131: this .cook(optionalFileName, in);
132: }
133:
134: /**
135: * Equivalent to<pre>
136: * SimpleCompiler sc = new SimpleCompiler();
137: * sc.cook(optionalFileName, is);</pre>
138: *
139: * @see #SimpleCompiler()
140: * @see Cookable#cook(String, InputStream)
141: */
142: public SimpleCompiler(String optionalFileName, InputStream is)
143: throws IOException, Scanner.ScanException,
144: Parser.ParseException, CompileException {
145: this .cook(optionalFileName, is);
146: }
147:
148: /**
149: * Equivalent to<pre>
150: * SimpleCompiler sc = new SimpleCompiler();
151: * sc.cook(fileName);</pre>
152: *
153: * @see #SimpleCompiler()
154: * @see Cookable#cookFile(String)
155: */
156: public SimpleCompiler(String fileName) throws IOException,
157: Scanner.ScanException, Parser.ParseException,
158: CompileException {
159: this .cookFile(fileName);
160: }
161:
162: /**
163: * Equivalent to<pre>
164: * SimpleCompiler sc = new SimpleCompiler();
165: * sc.setParentClassLoader(optionalParentClassLoader);
166: * sc.cook(scanner);</pre>
167: *
168: * @see #SimpleCompiler()
169: * @see #setParentClassLoader(ClassLoader)
170: * @see Cookable#cook(Scanner)
171: */
172: public SimpleCompiler(Scanner scanner,
173: ClassLoader optionalParentClassLoader) throws IOException,
174: Scanner.ScanException, Parser.ParseException,
175: CompileException {
176: this .setParentClassLoader(optionalParentClassLoader);
177: this .cook(scanner);
178: }
179:
180: public SimpleCompiler() {
181: }
182:
183: /**
184: * The "parent class loader" is used to load referenced classes. Useful values are:
185: * <table border="1"><tr>
186: * <td><code>System.getSystemClassLoader()</code></td>
187: * <td>The running JVM's class path</td>
188: * </tr><tr>
189: * <td><code>Thread.currentThread().getContextClassLoader()</code> or <code>null</code></td>
190: * <td>The class loader effective for the invoking thread</td>
191: * </tr><tr>
192: * <td>{@link #BOOT_CLASS_LOADER}</td>
193: * <td>The running JVM's boot class path</td>
194: * </tr></table>
195: * The parent class loader defaults to the current thread's context class loader.
196: */
197: public void setParentClassLoader(
198: ClassLoader optionalParentClassLoader) {
199: this .setParentClassLoader(optionalParentClassLoader, null);
200: }
201:
202: /**
203: * A {@link ClassLoader} that finds the classes on the JVM's <i>boot class path</i> (e.g.
204: * <code>java.io.*</code>), but not the classes on the JVM's <i>class path</i>.
205: */
206: public static final ClassLoader BOOT_CLASS_LOADER = new ClassLoader(
207: null) {
208: };
209:
210: /**
211: * Allowe references to the classes loaded through this parent class loader
212: * (@see {@link #setParentClassLoader(ClassLoader)}), plus the extra
213: * <code>auxiliaryClasses</code>.
214: * <p>
215: * Notice that the <code>auxiliaryClasses</code> must either be loadable through the
216: * <code>optionalParentClassLoader</code> (in which case they have no effect), or
217: * <b>no class with the same name</b> must be loadable through the
218: * <code>optionalParentClassLoader</code>.
219: */
220: public void setParentClassLoader(
221: ClassLoader optionalParentClassLoader,
222: Class[] auxiliaryClasses) {
223: this .parentClassLoader = (optionalParentClassLoader != null ? optionalParentClassLoader
224: : Thread.currentThread().getContextClassLoader());
225: this .optionalAuxiliaryClasses = auxiliaryClasses;
226: }
227:
228: public void cook(Scanner scanner) throws CompileException,
229: Parser.ParseException, Scanner.ScanException, IOException {
230: this .setUpClassLoaders();
231:
232: // Parse the compilation unit.
233: Java.CompilationUnit compilationUnit = new Parser(scanner)
234: .parseCompilationUnit();
235:
236: // Compile the classes and load them.
237: this .compileToClassLoader(compilationUnit,
238: DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION);
239: }
240:
241: /**
242: * Initializes {@link #classLoader} and {@link #iClassLoader} from the configured
243: * {@link #parentClassLoader} and {@link #optionalAuxiliaryClasses}. These are needed by
244: * {@link #classToType(Location, Class)} and friends which are used when creating the AST.
245: */
246: protected final void setUpClassLoaders() {
247: if (this .classLoader != null)
248: throw new RuntimeException(
249: "\"cook()\" must not be called more than once");
250:
251: // Set up the ClassLoader for the compilation and the loading.
252: this .classLoader = new AuxiliaryClassLoader(
253: this .parentClassLoader);
254: if (this .optionalAuxiliaryClasses != null) {
255: for (int i = 0; i < this .optionalAuxiliaryClasses.length; ++i) {
256: this .classLoader
257: .addAuxiliaryClass(this .optionalAuxiliaryClasses[i]);
258: }
259: }
260:
261: this .iClassLoader = new ClassLoaderIClassLoader(
262: this .classLoader);
263: }
264:
265: /**
266: * A {@link ClassLoader} that intermixes that classes loaded by its parent with a map of
267: * "auxiliary classes".
268: */
269: private static final class AuxiliaryClassLoader extends ClassLoader {
270: private final Map auxiliaryClasses = new HashMap(); // String name => Class
271:
272: private AuxiliaryClassLoader(ClassLoader parent) {
273: super (parent);
274: }
275:
276: protected Class loadClass(String name, boolean resolve)
277: throws ClassNotFoundException {
278: Class c = (Class) this .auxiliaryClasses.get(name);
279: if (c != null)
280: return c;
281:
282: return super .loadClass(name, resolve);
283: }
284:
285: private void addAuxiliaryClass(Class c) {
286: if (this .auxiliaryClasses.containsKey(c.getName()))
287: return;
288:
289: // Check whether the auxiliary class is conflicting with this ClassLoader.
290: try {
291: Class c2 = super .loadClass(c.getName(), false);
292: if (c2 != c)
293: throw new RuntimeException(
294: "Trying to add an auxiliary class \""
295: + c.getName()
296: + "\" while another class with the same name is already loaded");
297: } catch (ClassNotFoundException ex) {
298: ;
299: }
300:
301: this .auxiliaryClasses.put(c.getName(), c);
302:
303: {
304: Class sc = c.getSuperclass();
305: if (sc != null)
306: this .addAuxiliaryClass(sc);
307: }
308:
309: {
310: Class[] ifs = c.getInterfaces();
311: for (int i = 0; i < ifs.length; ++i)
312: this .addAuxiliaryClass(ifs[i]);
313: }
314: }
315:
316: public boolean equals(Object o) {
317: if (!(o instanceof AuxiliaryClassLoader))
318: return false;
319: AuxiliaryClassLoader that = (AuxiliaryClassLoader) o;
320:
321: {
322: final ClassLoader parentOfThis = this .getParent();
323: final ClassLoader parentOfThat = that.getParent();
324: if (parentOfThis == null ? parentOfThat != null
325: : !parentOfThis.equals(parentOfThat))
326: return false;
327: }
328:
329: return this .auxiliaryClasses.equals(that.auxiliaryClasses);
330: }
331:
332: public int hashCode() {
333: ClassLoader parent = this .getParent();
334: return (parent == null ? 0 : parent.hashCode())
335: ^ this .auxiliaryClasses.hashCode();
336: }
337: }
338:
339: /**
340: * Returns a {@link ClassLoader} object through which the previously compiled classes can
341: * be accessed. This {@link ClassLoader} can be used for subsequent calls to
342: * {@link #SimpleCompiler(Scanner, ClassLoader)} in order to compile compilation units that
343: * use types (e.g. declare derived types) declared in the previous one.
344: * <p>
345: * This method must only be called after {@link #cook(Scanner)}.
346: * <p>
347: * This method must not be called for instances of derived classes.
348: */
349: public ClassLoader getClassLoader() {
350: if (this .getClass() != SimpleCompiler.class)
351: throw new IllegalStateException(
352: "Must not be called on derived instances");
353: if (this .result == null)
354: throw new IllegalStateException(
355: "Must only be called after \"cook()\"");
356: return this .result;
357: }
358:
359: /**
360: * Two {@link SimpleCompiler}s are regarded equal iff
361: * <ul>
362: * <li>Both are objects of the same class (e.g. both are {@link ScriptEvaluator}s)
363: * <li>Both generated functionally equal classes as seen by {@link ByteArrayClassLoader#equals(Object)}
364: * </ul>
365: */
366: public boolean equals(Object o) {
367: if (!(o instanceof SimpleCompiler))
368: return false;
369: SimpleCompiler that = (SimpleCompiler) o;
370: if (this .getClass() != that.getClass())
371: return false;
372: if (this .result == null || that.result == null)
373: throw new IllegalStateException(
374: "Equality can only be checked after cooking");
375: return this .result.equals(that.result);
376: }
377:
378: public int hashCode() {
379: return this .classLoader.hashCode();
380: }
381:
382: /**
383: * Wrap a reflection {@link Class} in a {@link Java.Type} object.
384: */
385: protected Java.Type classToType(Location location,
386: final Class optionalClass) {
387: if (optionalClass == null)
388: return null;
389:
390: this .classLoader.addAuxiliaryClass(optionalClass);
391:
392: IClass iClass;
393: try {
394: iClass = this .iClassLoader.loadIClass(Descriptor
395: .fromClassName(optionalClass.getName()));
396: } catch (ClassNotFoundException ex) {
397: throw new RuntimeException("Loading IClass \""
398: + optionalClass.getName() + "\": " + ex);
399: }
400: if (iClass == null)
401: throw new RuntimeException("Cannot load class \""
402: + optionalClass.getName()
403: + "\" through the given ClassLoader");
404:
405: return new Java.SimpleType(location, iClass);
406: }
407:
408: /**
409: * Convert an array of {@link Class}es into an array of{@link Java.Type}s.
410: */
411: protected Java.Type[] classesToTypes(Location location,
412: Class[] classes) {
413: Java.Type[] types = new Java.Type[classes.length];
414: for (int i = 0; i < classes.length; ++i) {
415: types[i] = this .classToType(location, classes[i]);
416: }
417: return types;
418: }
419:
420: /**
421: * Compile the given compilation unit. (A "compilation unit" is typically the contents
422: * of a Java<sup>TM</sup> source file.)
423: *
424: * @param compilationUnit The parsed compilation unit
425: * @param debuggingInformation What kind of debugging information to generate in the class file
426: * @return The {@link ClassLoader} into which the compiled classes were defined
427: * @throws CompileException
428: */
429: protected final ClassLoader compileToClassLoader(
430: Java.CompilationUnit compilationUnit,
431: EnumeratorSet debuggingInformation) throws CompileException {
432: if (SimpleCompiler.DEBUG) {
433: UnparseVisitor.unparse(compilationUnit,
434: new OutputStreamWriter(System.out));
435: }
436:
437: // Compile compilation unit to class files.
438: ClassFile[] classFiles = new UnitCompiler(compilationUnit,
439: this .iClassLoader).compileUnit(debuggingInformation);
440:
441: // Convert the class files to bytes and store them in a Map.
442: Map classes = new HashMap(); // String className => byte[] data
443: for (int i = 0; i < classFiles.length; ++i) {
444: ClassFile cf = classFiles[i];
445: classes.put(cf.getThisClassName(), cf.toByteArray());
446: }
447:
448: // Disassemble all generated classes (for debugging).
449: if (SimpleCompiler.DEBUG) {
450: for (Iterator it = classes.entrySet().iterator(); it
451: .hasNext();) {
452: Map.Entry me = (Map.Entry) it.next();
453: String className = (String) me.getKey();
454: byte[] bytecode = (byte[]) me.getValue();
455: System.out.println("*** Disassembly of class \""
456: + className + "\":");
457: try {
458: new Disassembler().disasm(new ByteArrayInputStream(
459: bytecode));
460: System.out.flush();
461: } catch (IOException ex) {
462: throw new RuntimeException(
463: "SNO: IOException despite ByteArrayInputStream");
464: }
465: }
466: }
467:
468: // Create a ClassLoader that loads the generated classes.
469: this .result = new ByteArrayClassLoader(classes, // classes
470: this .classLoader // parent
471: );
472: return this.result;
473: }
474: }
|