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:
038: import org.codehaus.janino.Parser.ParseException;
039: import org.codehaus.janino.Scanner.ScanException;
040: import org.codehaus.janino.util.enumerator.EnumeratorSet;
041:
042: /**
043: * Parses a class body and returns it as a <tt>java.lang.Class</tt> object
044: * ready for use with <tt>java.lang.reflect</tt>.
045: * <p>
046: * Example:
047: * <pre>
048: * import java.util.*;
049: *
050: * static private int a = 1;
051: * private int b = 2;
052: *
053: * public void func(int c, int d) {
054: * return func2(c, d);
055: * }
056: *
057: * private static void func2(int e, int f) {
058: * return e * f;
059: * }
060: * </pre>
061: * <p>
062: * The <code>optionalClassLoader</code> serves two purposes:
063: * <ul>
064: * <li>It is used to look for classes referenced by the class body.
065: * <li>It is used to load the generated Java<sup>TM</sup> class
066: * into the JVM; directly if it is a subclass of {@link
067: * ByteArrayClassLoader}, or by creation of a temporary
068: * {@link ByteArrayClassLoader} if not.
069: * </ul>
070: * To set up a {@link ClassBodyEvaluator} object, proceed as follows:
071: * <ol>
072: * <li>
073: * Create the {@link ClassBodyEvaluator} using {@link #ClassBodyEvaluator()}
074: * <li>
075: * Configure the {@link ClassBodyEvaluator} by calling any of the following methods:
076: * <ul>
077: * <li>{@link org.codehaus.janino.SimpleCompiler#setParentClassLoader(ClassLoader)}
078: * <li>{@link #setDefaultImports(String[])}
079: * </ul>
080: * <li>
081: * Call any of the {@link org.codehaus.janino.Cookable#cook(Scanner)} methods to scan,
082: * parse, compile and load the class body into the JVM.
083: * </ol>
084: * Alternatively, a number of "convenience constructors" exist that execute the steps described
085: * above instantly.
086: * <p>
087: * To compile a class body and immediately instantiate an object, one of the
088: * {@link #createFastClassBodyEvaluator(Scanner, Class, ClassLoader)} methods can be used.
089: * <p>
090: * The generated class may optionally extend/implement a given type; the returned instance can
091: * safely be type-casted to that <code>optionalBaseType</code>.
092: * <p>
093: * Example:
094: * <pre>
095: * public interface Foo {
096: * int bar(int a, int b);
097: * }
098: * ...
099: * Foo f = (Foo) ClassBodyEvaluator.createFastClassBodyEvaluator(
100: * new Scanner(null, new StringReader("public int bar(int a, int b) { return a + b; }")),
101: * Foo.class, // Base type to extend/implement
102: * (ClassLoader) null // Use current thread's context class loader
103: * );
104: * System.out.println("1 + 2 = " + f.bar(1, 2));
105: * </pre>
106: * Notice: The <code>optionalBaseType</code> must be accessible from the generated class,
107: * i.e. it must either be declared <code>public</code>, or with default accessibility in the
108: * same package as the generated class.
109: */
110: public class ClassBodyEvaluator extends SimpleCompiler {
111: public static final String DEFAULT_CLASS_NAME = "SC";
112: protected static final Class[] ZERO_CLASSES = new Class[0];
113:
114: private String[] optionalDefaultImports = null;
115: protected String className = ClassBodyEvaluator.DEFAULT_CLASS_NAME;
116: private Class optionalExtendedType = null;
117: private Class[] implementedTypes = ClassBodyEvaluator.ZERO_CLASSES;
118: private Class clazz = null; // null=uncooked
119:
120: /**
121: * Equivalent to<pre>
122: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
123: * cbe.cook(classBody);</pre>
124: *
125: * @see #ClassBodyEvaluator()
126: * @see Cookable#cook(String)
127: */
128: public ClassBodyEvaluator(String classBody)
129: throws CompileException, Parser.ParseException,
130: Scanner.ScanException {
131: this .cook(classBody);
132: }
133:
134: /**
135: * Equivalent to<pre>
136: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
137: * cbe.cook(optionalFileName, is);</pre>
138: *
139: * @see #ClassBodyEvaluator()
140: * @see Cookable#cook(String, InputStream)
141: */
142: public ClassBodyEvaluator(String optionalFileName, InputStream is)
143: throws CompileException, Parser.ParseException,
144: Scanner.ScanException, IOException {
145: this .cook(optionalFileName, is);
146: }
147:
148: /**
149: * Equivalent to<pre>
150: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
151: * cbe.cook(optionalFileName, reader);</pre>
152: *
153: * @see #ClassBodyEvaluator()
154: * @see Cookable#cook(String, Reader)
155: */
156: public ClassBodyEvaluator(String optionalFileName, Reader reader)
157: throws CompileException, Parser.ParseException,
158: Scanner.ScanException, IOException {
159: this .cook(optionalFileName, reader);
160: }
161:
162: /**
163: * Equivalent to<pre>
164: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
165: * cbe.setParentClassLoader(optionalParentClassLoader);
166: * cbe.cook(scanner);</pre>
167: *
168: * @see #ClassBodyEvaluator()
169: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
170: * @see Cookable#cook(Scanner)
171: */
172: public ClassBodyEvaluator(Scanner scanner,
173: ClassLoader optionalParentClassLoader)
174: throws CompileException, Parser.ParseException,
175: Scanner.ScanException, IOException {
176: this .setParentClassLoader(optionalParentClassLoader);
177: this .cook(scanner);
178: }
179:
180: /**
181: * Equivalent to<pre>
182: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
183: * cbe.setExtendedType(optionalExtendedType);
184: * cbe.setImplementedTypes(implementedTypes);
185: * cbe.setParentClassLoader(optionalParentClassLoader);
186: * cbe.cook(scanner);</pre>
187: *
188: * @see #ClassBodyEvaluator()
189: * @see #setExtendedType(Class)
190: * @see #setImplementedTypes(Class[])
191: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
192: * @see Cookable#cook(Scanner)
193: */
194: public ClassBodyEvaluator(Scanner scanner,
195: Class optionalExtendedType, Class[] implementedTypes,
196: ClassLoader optionalParentClassLoader)
197: throws CompileException, Parser.ParseException,
198: Scanner.ScanException, IOException {
199: this .setExtendedType(optionalExtendedType);
200: this .setImplementedTypes(implementedTypes);
201: this .setParentClassLoader(optionalParentClassLoader);
202: this .cook(scanner);
203: }
204:
205: /**
206: * Equivalent to<pre>
207: * ClassBodyEvaluator cbe = new ClassBodyEvaluator();
208: * cbe.setClassName(className);
209: * cbe.setExtendedType(optionalExtendedType);
210: * cbe.setImplementedTypes(implementedTypes);
211: * cbe.setParentClassLoader(optionalParentClassLoader);
212: * cbe.cook(scanner);</pre>
213: *
214: * @see #ClassBodyEvaluator()
215: * @see #setClassName(String)
216: * @see #setExtendedType(Class)
217: * @see #setImplementedTypes(Class[])
218: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
219: * @see Cookable#cook(Scanner)
220: */
221: public ClassBodyEvaluator(Scanner scanner, String className,
222: Class optionalExtendedType, Class[] implementedTypes,
223: ClassLoader optionalParentClassLoader)
224: throws CompileException, Parser.ParseException,
225: Scanner.ScanException, IOException {
226: this .setClassName(className);
227: this .setExtendedType(optionalExtendedType);
228: this .setImplementedTypes(implementedTypes);
229: this .setParentClassLoader(optionalParentClassLoader);
230: this .cook(scanner);
231: }
232:
233: public ClassBodyEvaluator() {
234: }
235:
236: /**
237: * "Default imports" add to the system import "java.lang", i.e. the evaluator may refer to
238: * classes imported by default imports without having to explicitly declare IMPORT statements.
239: * <p>
240: * Example: <code>sc.setDefaultImports(new String[] { "java.util.Map", "java.io.*" });</code>
241: */
242: public void setDefaultImports(String[] optionalDefaultImports) {
243: this .optionalDefaultImports = optionalDefaultImports;
244: }
245:
246: /**
247: * Set the name of the generated class. Defaults to {@link #DEFAULT_CLASS_NAME}. In most cases,
248: * there is no need to set this name, because the generated class is loaded into its own
249: * {@link java.lang.ClassLoader} where its name cannot collide with classes generated by
250: * other evaluators.
251: * <p>
252: * One reason to use this function is to have a class name in a non-default package, which
253: * can be relevant when types and members with DEFAULT accessibility are accessed.
254: */
255: public void setClassName(String className) {
256: this .className = className;
257: }
258:
259: /**
260: * Set a particular superclass that the generated class will extend. If <code>null</code> is
261: * passed, the generated class will extend {@link Object}.
262: * <p>
263: * The common reason to set a base class for an evaluator is that the generated class can
264: * directly access the base superclass's (non-private) members.
265: */
266: public void setExtendedType(Class optionalExtendedType) {
267: this .optionalExtendedType = optionalExtendedType;
268: }
269:
270: /**
271: * Set a particular set of interfaces that the generated class will implement.
272: */
273: public void setImplementedTypes(Class[] implementedTypes) {
274: if (implementedTypes == null)
275: throw new NullPointerException(
276: "Zero implemented types must be specified as \"new Class[0]\", not \"null\"");
277: this .implementedTypes = implementedTypes;
278: }
279:
280: protected void internalCook(Scanner scanner)
281: throws CompileException, ParseException, ScanException,
282: IOException {
283: Java.CompilationUnit compilationUnit = this
284: .makeCompilationUnit(scanner);
285:
286: // Add class declaration.
287: Java.ClassDeclaration cd = this
288: .addPackageMemberClassDeclaration(scanner.location(),
289: compilationUnit);
290:
291: // Parse class body declarations (member declarations) until EOF.
292: Parser parser = new Parser(scanner);
293: while (!scanner.peek().isEOF()) {
294: parser.parseClassBodyDeclaration(cd);
295: }
296:
297: // Compile and load it.
298: this .clazz = this .compileToClass(compilationUnit, // compilationUnit
299: DebuggingInformation.ALL, // debuggingInformation
300: this .className // className
301: );
302: }
303:
304: /**
305: * Create a {@link Java.CompilationUnit}, set the default imports, and parse the import
306: * declarations.
307: */
308: protected Java.CompilationUnit makeCompilationUnit(Scanner scanner)
309: throws Parser.ParseException, Scanner.ScanException,
310: IOException {
311: Java.CompilationUnit cu = new Java.CompilationUnit(scanner
312: .getFileName());
313:
314: // Set default imports.
315: if (this .optionalDefaultImports != null) {
316: for (int i = 0; i < this .optionalDefaultImports.length; ++i) {
317: Scanner s = new Scanner(null, new StringReader(
318: this .optionalDefaultImports[i]));
319: cu.addImportDeclaration(new Parser(s)
320: .parseImportDeclarationBody());
321: if (!s.peek().isEOF())
322: throw new ParseException("Unexpected token \""
323: + s.peek() + "\" in default import", s
324: .location());
325: }
326: }
327:
328: // Parse all available IMPORT declarations.
329: Parser parser = new Parser(scanner);
330: while (scanner.peek().isKeyword("import")) {
331: cu.addImportDeclaration(parser.parseImportDeclaration());
332: }
333:
334: return cu;
335: }
336:
337: /**
338: * To the given {@link Java.CompilationUnit}, add
339: * <ul>
340: * <li>A class declaration with the configured name, superclass and interfaces
341: * <li>A method declaration with the given return type, name, parameter
342: * names and values and thrown exceptions
343: * </ul>
344: * @param location
345: * @param compilationUnit
346: * @param className
347: * @param implementedTypes
348: * @return The created {@link Java.ClassDeclaration} object
349: * @throws Parser.ParseException
350: */
351: protected Java.PackageMemberClassDeclaration addPackageMemberClassDeclaration(
352: Location location, Java.CompilationUnit compilationUnit)
353: throws Parser.ParseException {
354: String cn = this .className;
355: int idx = cn.lastIndexOf('.');
356: if (idx != -1) {
357: compilationUnit
358: .setPackageDeclaration(new Java.PackageDeclaration(
359: location, cn.substring(0, idx)));
360: cn = cn.substring(idx + 1);
361: }
362: Java.PackageMemberClassDeclaration tlcd = new Java.PackageMemberClassDeclaration(
363: location, // location
364: null, // optionalDocComment
365: Mod.PUBLIC, // modifiers
366: cn, // name
367: this .classToType(location, this .optionalExtendedType), // optionalExtendedType
368: this .classesToTypes(location, this .implementedTypes) // implementedTypes
369: );
370: compilationUnit.addPackageMemberTypeDeclaration(tlcd);
371: return tlcd;
372: }
373:
374: /**
375: * Compile the given compilation unit, load all generated classes, and
376: * return the class with the given name.
377: * @param compilationUnit
378: * @param debuggingInformation TODO
379: * @param newClassName The fully qualified class name
380: * @return The loaded class
381: */
382: protected Class compileToClass(
383: Java.CompilationUnit compilationUnit,
384: EnumeratorSet debuggingInformation, String newClassName)
385: throws CompileException {
386:
387: // Compile and load the compilation unit.
388: ClassLoader cl = this .compileToClassLoader(compilationUnit,
389: debuggingInformation);
390:
391: // Find the generated class by name.
392: try {
393: return cl.loadClass(newClassName);
394: } catch (ClassNotFoundException ex) {
395: throw new RuntimeException(
396: "SNO: Generated compilation unit does not declare class \""
397: + newClassName + "\"");
398: }
399: }
400:
401: /**
402: * Returns the loaded {@link Class}.
403: * <p>
404: * This method must only be called after {@link #cook(Scanner)}.
405: * <p>
406: * This method must not be called for instances of derived classes.
407: */
408: public Class getClazz() {
409: if (this .getClass() != ClassBodyEvaluator.class)
410: throw new IllegalStateException(
411: "Must not be called on derived instances");
412: if (this .clazz == null)
413: throw new IllegalStateException(
414: "Must only be called after \"cook()\"");
415: return this .clazz;
416: }
417:
418: /**
419: * Scans, parses and compiles a class body from the tokens delivered by the the given
420: * {@link Scanner}.
421: * The generated class has the {@link #DEFAULT_CLASS_NAME} and extends the given
422: * <code>optionalBaseType</code> (if that is a class), and implements the given
423: * <code>optionalBaseType</code> (if that is an interface).
424: * <p>
425: * For an explanation of the "fast class body evaluator" concept, see the class description.
426: *
427: * @param scanner Source of class body tokens
428: * @param optionalBaseType Base type to extend/implement
429: * @param optionalParentClassLoader Used to load referenced classes, defaults to the current thread's "context class loader"
430: * @return an object that extends/implements the given <code>optionalBaseType</code>
431: * @see ClassBodyEvaluator
432: */
433: public static Object createFastClassBodyEvaluator(Scanner scanner,
434: Class optionalBaseType,
435: ClassLoader optionalParentClassLoader)
436: throws CompileException, Parser.ParseException,
437: Scanner.ScanException, IOException {
438: return ClassBodyEvaluator
439: .createFastClassBodyEvaluator(
440: scanner,
441: ClassBodyEvaluator.DEFAULT_CLASS_NAME, // className
442: ( // optionalExtendedType
443: optionalBaseType != null
444: && !optionalBaseType.isInterface() ? optionalBaseType
445: : null),
446: ( // implementedTypes
447: optionalBaseType != null
448: && optionalBaseType.isInterface() ? new Class[] { optionalBaseType }
449: : new Class[0]),
450: optionalParentClassLoader);
451: }
452:
453: /**
454: * Scans, parses and compiles a class body from the tokens delivered by the the given
455: * {@link Scanner} with no default imports.
456: * <p>
457: * For an explanation of the "fast class body evaluator" concept, see the class description.
458: *
459: * @param scanner Source of class body tokens
460: * @param className Name of generated class
461: * @param optionalExtendedType Class to extend
462: * @param implementedTypes Interfaces to implement
463: * @param optionalParentClassLoader Used to load referenced classes, defaults to the current thread's "context class loader"
464: * @return an object that extends the <code>optionalExtendedType</code> and implements the given <code>implementedTypes</code>
465: * @see ClassBodyEvaluator
466: */
467: public static Object createFastClassBodyEvaluator(Scanner scanner,
468: String className, Class optionalExtendedType,
469: Class[] implementedTypes,
470: ClassLoader optionalParentClassLoader)
471: throws CompileException, Parser.ParseException,
472: Scanner.ScanException, IOException {
473: Class c = new ClassBodyEvaluator(scanner, className,
474: optionalExtendedType, implementedTypes,
475: optionalParentClassLoader).getClazz();
476: try {
477: return c.newInstance();
478: } catch (InstantiationException e) {
479: throw new CompileException(
480: "Cannot instantiate abstract class -- one or more method implementations are missing",
481: null);
482: } catch (IllegalAccessException e) {
483: // SNO - type and default constructor of generated class are PUBLIC.
484: throw new RuntimeException(e.toString());
485: }
486: }
487: }
|