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.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: import org.codehaus.janino.util.resource.PathResourceFinder;
045:
046: /**
047: * A simplified version of {@link Compiler} that can compile only a single
048: * compilation unit. (A "compilation unit" is the characters stored in a
049: * ".java" file.)
050: * <p>
051: * Opposed to a normal ".java" file, you can declare multiple public classes
052: * here.
053: * <p>
054: * To set up a {@link SimpleCompiler} object, proceed as follows:
055: * <ol>
056: * <li>
057: * Create the {@link SimpleCompiler} using {@link #SimpleCompiler()}
058: * <li>
059: * Optionally set an alternate parent class loader through
060: * {@link #setParentClassLoader(ClassLoader)}.
061: * <li>
062: * Call any of the {@link org.codehaus.janino.Cookable#cook(Scanner)} methods to scan,
063: * parse, compile and load the compilation unit into the JVM.
064: * </ol>
065: * Alternatively, a number of "convenience constructors" exist that execute the steps described
066: * above instantly.
067: */
068: public class SimpleCompiler extends Cookable {
069: private final static boolean DEBUG = false;
070:
071: private final ClassLoaderIClassLoader DEFAULT_ICLASSLOADER = new ClassLoaderIClassLoader(
072: Thread.currentThread().getContextClassLoader());
073: private ClassLoaderIClassLoader classLoaderIClassLoader = this .DEFAULT_ICLASSLOADER;
074:
075: private ClassLoader classLoader = null; // null=uncooked
076:
077: public static void main(String[] args) throws Exception {
078: if (args.length < 1) {
079: System.err.println("Usage:");
080: System.err
081: .println(" org.codehaus.janino.SimpleCompiler <source-file> <class-name> <arg> [ ... ]");
082: System.err
083: .println("Reads a compilation unit from the given <source-file> and invokes method");
084: System.err
085: .println("\"public static void main(String[])\" of class <class-name>.");
086: System.exit(1);
087: }
088:
089: String sourceFileName = args[0];
090: String className = args[1];
091: String[] mainArgs = new String[args.length - 2];
092: System.arraycopy(args, 2, mainArgs, 0, mainArgs.length);
093:
094: ClassLoader cl = new SimpleCompiler(sourceFileName,
095: new FileInputStream(sourceFileName)).getClassLoader();
096: Class c = cl.loadClass(className);
097: Method m = c.getMethod("main", new Class[] { String[].class });
098: m.invoke(null, new Object[] { mainArgs });
099: }
100:
101: /**
102: * Equivalent to<pre>
103: * SimpleCompiler sc = new SimpleCompiler();
104: * sc.cook(optionalFileName, in);</pre>
105: *
106: * @see #SimpleCompiler()
107: * @see Cookable#cook(String, Reader)
108: */
109: public SimpleCompiler(String optionalFileName, Reader in)
110: throws IOException, Scanner.ScanException,
111: Parser.ParseException, CompileException {
112: this .cook(optionalFileName, in);
113: }
114:
115: /**
116: * Equivalent to<pre>
117: * SimpleCompiler sc = new SimpleCompiler();
118: * sc.cook(optionalFileName, is);</pre>
119: *
120: * @see #SimpleCompiler()
121: * @see Cookable#cook(String, InputStream)
122: */
123: public SimpleCompiler(String optionalFileName, InputStream is)
124: throws IOException, Scanner.ScanException,
125: Parser.ParseException, CompileException {
126: this .cook(optionalFileName, is);
127: }
128:
129: /**
130: * Equivalent to<pre>
131: * SimpleCompiler sc = new SimpleCompiler();
132: * sc.cook(fileName);</pre>
133: *
134: * @see #SimpleCompiler()
135: * @see Cookable#cookFile(String)
136: */
137: public SimpleCompiler(String fileName) throws IOException,
138: Scanner.ScanException, Parser.ParseException,
139: CompileException {
140: this .cookFile(fileName);
141: }
142:
143: /**
144: * Equivalent to<pre>
145: * SimpleCompiler sc = new SimpleCompiler();
146: * sc.setParentClassLoader(optionalParentClassLoader);
147: * sc.cook(scanner);</pre>
148: *
149: * @see #SimpleCompiler()
150: * @see #setParentClassLoader(ClassLoader)
151: * @see Cookable#cook(Scanner)
152: */
153: public SimpleCompiler(Scanner scanner,
154: ClassLoader optionalParentClassLoader) throws IOException,
155: Scanner.ScanException, Parser.ParseException,
156: CompileException {
157: this .setParentClassLoader(optionalParentClassLoader);
158: this .cook(scanner);
159: }
160:
161: public SimpleCompiler() {
162: }
163:
164: /**
165: * This SimpleCompiler implementation is used when compiling Java
166: * source code stored in a String value. The SimpleCompiler object
167: * is created once and the CLASSPATH is setup. Then the compile()
168: * method is invoked 1 or more times to compile Java class source
169: * contained in a String object. Unlike the other implementations,
170: * this version of the SimpleCompiler will not attempt to load the
171: * compiled class data into the current thread using the class loader.
172: * This implementation will just compile the source code into
173: * an array of ClassFile objects.
174: */
175:
176: boolean noloadSimpleCompiler = false;
177: IClassLoader icloader = null;
178:
179: public SimpleCompiler(boolean noload) {
180: //super(null);
181: //this.classLoader = null;
182: this .setParentClassLoader(null);
183: if (noload == false) {
184: throw new RuntimeException("noload must be true");
185: }
186: noloadSimpleCompiler = true;
187:
188: // Use context class loader unless explicit class loader is given
189: //ClassLoader cloader = null;
190: //if (cloader == null) {
191: // Thread cthread = Thread.currentThread();
192: // cloader = cthread.getContextClassLoader();
193: //}
194:
195: /*
196: // Load classes from the CLASSPATH
197: String classPath = System.getProperty("java.class.path");
198: System.out.println("classPath is \"" + classPath + "\"");
199: ResourceFinder classPathResourceFinder = new PathResourceFinder(
200: PathResourceFinder.parsePath(classPath));
201:
202: ResourceFinder classLoaderResourceFinder = new ResourceFinderClassLoader(
203: classPathResourceFinder, cloader);
204:
205: IClassLoader icloader = new ResourceFinderIClassLoader(
206: //classPathResourceFinder,
207: classLoaderResourceFinder,
208: null);
209:
210: //cloader = new ResourceFinderClassLoader(classPathResourceFinder, cloader);
211: //ClassLoaderIClassLoader icloader = new ClassLoaderIClassLoader(cloader);
212: */
213:
214: /*
215: // Load classes from the CLASSPATH
216:
217: String classPath = System.getProperty("java.class.path");
218: System.out.println("classPath is \"" + classPath + "\"");
219: ResourceFinder classPathResourceFinder = new PathResourceFinder(
220: PathResourceFinder.parsePath(classPath));
221: icloader = new ResourceFinderIClassLoader(
222: classPathResourceFinder,
223: null);
224: */
225:
226: String classPath = System.getProperty("java.class.path");
227: //System.out.println("CLASSPATH is \"" + classPath + "\"");
228:
229: icloader = Compiler.createJavacLikePathIClassLoader(null, // optionalBootClassPath
230: null, // optionalExtDirs
231: PathResourceFinder.parsePath(classPath));
232: }
233:
234: public ClassFile[] compile(String javasrc) {
235: if (!noloadSimpleCompiler) {
236: throw new RuntimeException(
237: "SimpleCompiler.compile() can only be used with "
238: + "a SimpleCompiler() constructed with no arguments");
239: }
240:
241: try {
242: // FIXME: Not clear that this implementation is saving
243: // the Class data read from the CLASSPATH, keeping
244: // already read classes around would really speed things
245: // on successive compiles. Look more into optimizing this.
246: StringReader sreader = new StringReader(javasrc);
247: Scanner scanner = new Scanner(null, sreader);
248: Parser parser = new Parser(scanner);
249: Java.CompilationUnit cunit = parser.parseCompilationUnit();
250:
251: UnitCompiler ucompiler = new UnitCompiler(cunit, icloader);
252: EnumeratorSet defaultDebug = DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION;
253:
254: ClassFile[] cfiles = ucompiler.compileUnit(defaultDebug);
255: return cfiles;
256: } catch (Exception ex) {
257: ex.printStackTrace(System.err);
258: return null;
259: }
260: }
261:
262: /**
263: * The "parent class loader" is used to load referenced classes. It defaults to the current
264: * thread's "context class loader".
265: */
266: public void setParentClassLoader(
267: ClassLoader optionalParentClassLoader) {
268: this .classLoaderIClassLoader = (optionalParentClassLoader != null ? new ClassLoaderIClassLoader(
269: optionalParentClassLoader)
270: : this .DEFAULT_ICLASSLOADER);
271: }
272:
273: /**
274: * Parse tokens delivered by the <code>scanner</code>, compile them and load them into the
275: * JVM.
276: * <p>
277: * This method must be called exactly once.
278: */
279: protected void internalCook(Scanner scanner)
280: throws CompileException, Parser.ParseException,
281: Scanner.ScanException, IOException {
282:
283: // Parse the compilation unit.
284: Java.CompilationUnit compilationUnit = new Parser(scanner)
285: .parseCompilationUnit();
286:
287: // Compile the classes and load them.
288: this .compileToClassLoader(compilationUnit,
289: DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION);
290: }
291:
292: /**
293: * Returns a {@link ClassLoader} object through which the previously compiled classes can
294: * be accessed. This {@link ClassLoader} can be used for subsequent calls to
295: * {@link #SimpleCompiler(Scanner, ClassLoader)} in order to compile compilation units that
296: * use types (e.g. declare derived types) declared in the previous one.
297: * <p>
298: * This method must only be called after {@link #cook(Scanner)}.
299: * <p>
300: * This method must not be called for instances of derived classes.
301: */
302: public ClassLoader getClassLoader() {
303: if (this .getClass() != SimpleCompiler.class)
304: throw new IllegalStateException(
305: "Must not be called on derived instances");
306: if (this .classLoader == null)
307: throw new IllegalStateException(
308: "Must only be called after \"cook()\"");
309: return this .classLoader;
310: }
311:
312: /**
313: * Two {@link SimpleCompiler}s are regarded equal iff
314: * <ul>
315: * <li>Both are objects of the same class (e.g. both are {@link ScriptEvaluator}s)
316: * <li>Both generated functionally equal classes as seen by {@link ByteArrayClassLoader#equals(Object)}
317: * </ul>
318: */
319: public boolean equals(Object o) {
320: if (!(o instanceof SimpleCompiler))
321: return false;
322: SimpleCompiler that = (SimpleCompiler) o;
323: if (this .getClass() != that.getClass())
324: return false;
325: if (this .classLoader == null || that.classLoader == null)
326: throw new IllegalStateException(
327: "Equality can only be checked after cooking");
328: return this .classLoader.equals(that.classLoader);
329: }
330:
331: public int hashCode() {
332: return this .classLoader.hashCode();
333: }
334:
335: /**
336: * Wrap a reflection {@link Class} in a {@link Java.Type} object.
337: */
338: protected Java.Type classToType(Location location,
339: final Class optionalClass) {
340: if (optionalClass == null)
341: return null;
342:
343: IClass iClass;
344: try {
345: iClass = this .classLoaderIClassLoader.loadIClass(Descriptor
346: .fromClassName(optionalClass.getName()));
347: } catch (ClassNotFoundException ex) {
348: throw new RuntimeException("Loading IClass \""
349: + optionalClass.getName() + "\": " + ex);
350: }
351: if (iClass == null)
352: throw new RuntimeException("Cannot load class \""
353: + optionalClass.getName()
354: + "\" through the given ClassLoader");
355:
356: return new Java.SimpleType(location, iClass);
357: }
358:
359: /**
360: * Convert an array of {@link Class}es into an array of{@link Java.Type}s.
361: */
362: protected Java.Type[] classesToTypes(Location location,
363: Class[] classes) {
364: Java.Type[] types = new Java.Type[classes.length];
365: for (int i = 0; i < classes.length; ++i) {
366: types[i] = this .classToType(location, classes[i]);
367: }
368: return types;
369: }
370:
371: /**
372: * Compile the given compilation unit. (A "compilation unit" is typically the contents
373: * of a Java<sup>TM</sup> source file.)
374: *
375: * @param compilationUnit The parsed compilation unit
376: * @param debuggingInformation What kind of debugging information to generate in the class file
377: * @return The {@link ClassLoader} into which the compiled classes were defined
378: * @throws CompileException
379: */
380: protected ClassLoader compileToClassLoader(
381: Java.CompilationUnit compilationUnit,
382: EnumeratorSet debuggingInformation) throws CompileException {
383: if (SimpleCompiler.DEBUG) {
384: UnparseVisitor.unparse(compilationUnit,
385: new OutputStreamWriter(System.out));
386: }
387:
388: // Compile compilation unit to class files.
389: ClassFile[] classFiles = new UnitCompiler(compilationUnit,
390: this .classLoaderIClassLoader)
391: .compileUnit(debuggingInformation);
392:
393: // Convert the class files to bytes and store them in a Map.
394: Map classes = new HashMap(); // String className => byte[] data
395: for (int i = 0; i < classFiles.length; ++i) {
396: ClassFile cf = classFiles[i];
397: classes.put(cf.getThisClassName(), cf.toByteArray());
398: }
399:
400: // Disassemble all generated classes (for debugging).
401: if (SimpleCompiler.DEBUG) {
402: // try {
403: // Disassembler.disasm(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 }));
404: // } catch (IOException e) {
405: // // TODO Auto-generated catch block
406: // e.printStackTrace();
407: // }
408: for (Iterator it = classes.entrySet().iterator(); it
409: .hasNext();) {
410: Map.Entry me = (Map.Entry) it.next();
411: String className = (String) me.getKey();
412: byte[] bytecode = (byte[]) me.getValue();
413: System.out.println("*** Disassembly of class \""
414: + className + "\":");
415: try {
416: new Disassembler().disasm(new ByteArrayInputStream(
417: bytecode));
418: System.out.flush();
419: } catch (IOException ex) {
420: throw new RuntimeException(
421: "SNO: IOException despite ByteArrayInputStream");
422: }
423: }
424: }
425:
426: // Create a ClassLoader that loads the generated classes.
427: this .classLoader = new ByteArrayClassLoader(classes, // classes
428: this .classLoaderIClassLoader.getClassLoader() // parent
429: );
430: return this.classLoader;
431: }
432: }
|