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: /**
039: * An expression evaluator that evaluates expressions in Java<sup>TM</sup> bytecode.
040: * <p>
041: * The syntax of the expression to compile is that of a Java<sup>TM</sup> expression, as defined
042: * in the <a href="http://java.sun.com/docs/books/jls/second_edition">Java Language Specification,
043: * 2nd edition</a>, section
044: * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#44393">15</a>.
045: * Notice that a Java<sup>TM</sup> expression does not have a concluding semicolon.
046: * <p>
047: * Example:<pre>
048: * a + 7 * b</pre>
049: * (Notice that this expression refers to two parameters "a" and "b", as explained below.)
050: * <p>
051: * The expression may optionally be preceeded with a sequence of import directives like
052: * <pre>
053: * import java.text.*;
054: * new DecimalFormat("####,###.##").format(10200020.345345)
055: * </pre>
056: * (Notice that the import directive is concluded with a semicolon, while the expression is not.)
057: * <p>
058: * The expression evaluator is implemented by creating and compiling a temporary compilation unit
059: * defining one class with one static method with one return statement.
060: * <p>
061: * To set up an {@link ExpressionEvaluator} object, proceed as follows:
062: * <ol>
063: * <li>
064: * Create the {@link ExpressionEvaluator} using {@link #ExpressionEvaluator()}
065: * <li>
066: * Configure the {@link ExpressionEvaluator} by calling any of the following methods:
067: * <ul>
068: * <li>{@link #setExpressionType(Class)}
069: * <li>{@link org.codehaus.janino.ScriptEvaluator#setParameters(String[], Class[])}
070: * <li>{@link org.codehaus.janino.ScriptEvaluator#setThrownExceptions(Class[])}
071: * <li>{@link org.codehaus.janino.SimpleCompiler#setParentClassLoader(ClassLoader)}
072: * <li>{@link org.codehaus.janino.ClassBodyEvaluator#setDefaultImports(String[])}
073: * </ul>
074: * <li>
075: * Call any of the {@link org.codehaus.janino.Cookable#cook(Scanner)} methods to scan,
076: * parse, compile and load the expression into the JVM.
077: * </ol>
078: * Alternatively, a number of "convenience constructors" exist that execute the steps described
079: * above instantly.
080: * <p>
081: * After the {@link ExpressionEvaluator} object is set up, the expression can be evaluated as
082: * often with different parameter values (see {@link #evaluate(Object[])}). This evaluation is
083: * very fast, compared to the setup.
084: * <p>
085: * Notice that for <i>functionally</i> identical {@link ExpressionEvaluator}s,
086: * {@link java.lang.Object#equals(java.lang.Object)} will return <code>true</code>. E.g. "a+b" and
087: * "c + d" are functionally identical if "a" and "c" have the same type, and so do "b" and "d".
088: * <p>
089: * If the parameter and return types of the expression are known at compile time, then a "fast"
090: * expression evaluator can be instantiated through
091: * {@link #createFastExpressionEvaluator(String, Class, String[], ClassLoader)}. Expression
092: * evaluation is faster than through {@link #evaluate(Object[])}, because it is not done through
093: * reflection but through direct method invocation.
094: * <p>
095: * Example:
096: * <pre>
097: * public interface Foo {
098: * int bar(int a, int b);
099: * }
100: * ...
101: * Foo f = (Foo) ExpressionEvaluator.createFastExpressionEvaluator(
102: * "a + b", // expression to evaluate
103: * Foo.class, // interface that describes the expression's signature
104: * new String[] { "a", "b" }, // the parameters' names
105: * (ClassLoader) null // Use current thread's context class loader
106: * );
107: * System.out.println("1 + 2 = " + f.bar(1, 2)); // Evaluate the expression
108: * </pre>
109: * Notice: The <code>interfaceToImplement</code> must either be declared <code>public</code>,
110: * or with package scope in the root package (i.e. "no" package).
111: * <p>
112: * On my system (Intel P4, 2 GHz, MS Windows XP, JDK 1.4.1), expression "x + 1"
113: * evaluates as follows:
114: * <table>
115: * <tr><td></td><th>Server JVM</th><th>Client JVM</th></td></tr>
116: * <tr><td>Normal EE</td><td>23.7 ns</td><td>64.0 ns</td></tr>
117: * <tr><td>Fast EE</td><td>31.2 ns</td><td>42.2 ns</td></tr>
118: * </table>
119: * (How can it be that interface method invocation is slower than reflection for
120: * the server JVM?)
121: * <p>
122: * The expression may refer to a set of parameters with the given
123: * <code>parameterNames</code> and <code>parameterTypes</code>.
124: * <p>
125: * <code>parameterNames</code> and <code>parameterTypes</code> must have the
126: * same number of elements.
127: * <p>
128: * The parameters and/or the return value can be of primitive type, e.g.
129: * {@link Double#TYPE}.
130: * <p>
131: * The <code>optionalClassLoader</code> serves two purposes:
132: * <ul>
133: * <li>It is used to look for classes referenced by the script.
134: * <li>It is used to load the generated Java<sup>TM</sup> class
135: * into the JVM; directly if it is a subclass of {@link
136: * ByteArrayClassLoader}, or by creation of a temporary
137: * {@link ByteArrayClassLoader} if not.
138: * </ul>
139: * If the <code>optionalClassLoader</code> is <code>null</code>, then the
140: * current thread's context class loader is used.
141: * <p>
142: * A number of constructors exist that provide useful default values for
143: * the various parameters, or parse their script from a {@link String}
144: * instead of a {@link Scanner}. (You hardly want to use a scanner other than
145: * the default scanner.)
146: * <p>
147: * If the type of the expression is not fixed, you can pass a <code>null</code>
148: * <code>optionalExpressionType<code> argument; in this case, references are
149: * returned as {@link Object}s, and primitive values are wrapped in their
150: * wrapper classes.
151: * <p>
152: * If <code>optionalExpressionType</code> is {@link Void#TYPE}, then the
153: * expression must be an invocation of a <code>void</code> method.
154: */
155: public class ExpressionEvaluator extends ScriptEvaluator {
156: public static final Class ANY_TYPE = null;
157: private Class expressionType = ExpressionEvaluator.ANY_TYPE;
158:
159: /**
160: * Equivalent to<pre>
161: * ExpressionEvaluator ee = new ExpressionEvaluator();
162: * ee.setExpressionType(expressionType);
163: * ee.setParameters(parameterNames, parameterTypes);
164: * ee.cook(expression);</pre>
165: *
166: * @see #ExpressionEvaluator()
167: * @see ExpressionEvaluator#setExpressionType(Class)
168: * @see ScriptEvaluator#setParameters(String[], Class[])
169: * @see Cookable#cook(String)
170: */
171: public ExpressionEvaluator(String expression, Class expressionType,
172: String[] parameterNames, Class[] parameterTypes)
173: throws CompileException, Parser.ParseException,
174: Scanner.ScanException {
175: this .setExpressionType(expressionType);
176: this .setParameters(parameterNames, parameterTypes);
177: this .cook(expression);
178: }
179:
180: /**
181: * Equivalent to<pre>
182: * ExpressionEvaluator ee = new ExpressionEvaluator();
183: * ee.setExpressionType(expressionType);
184: * ee.setParameters(parameterNames, parameterTypes);
185: * ee.setThrownExceptions(thrownExceptions);
186: * ee.setParentClassLoader(optionalParentClassLoader);
187: * ee.cook(expression);</pre>
188: *
189: * @see #ExpressionEvaluator()
190: * @see ExpressionEvaluator#setExpressionType(Class)
191: * @see ScriptEvaluator#setParameters(String[], Class[])
192: * @see ScriptEvaluator#setThrownExceptions(Class[])
193: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
194: * @see Cookable#cook(String)
195: */
196: public ExpressionEvaluator(String expression, Class expressionType,
197: String[] parameterNames, Class[] parameterTypes,
198: Class[] thrownExceptions,
199: ClassLoader optionalParentClassLoader)
200: throws CompileException, Parser.ParseException,
201: Scanner.ScanException {
202: this .setExpressionType(expressionType);
203: this .setParameters(parameterNames, parameterTypes);
204: this .setThrownExceptions(thrownExceptions);
205: this .setParentClassLoader(optionalParentClassLoader);
206: this .cook(expression);
207: }
208:
209: /**
210: * Equivalent to<pre>
211: * ExpressionEvaluator ee = new ExpressionEvaluator();
212: * ee.setExpressionType(expressionType);
213: * ee.setParameters(parameterNames, parameterTypes);
214: * ee.setThrownExceptions(thrownExceptions);
215: * ee.setExtendedType(optionalExtendedType);
216: * ee.setImplementedTypes(implementedTypes);
217: * ee.setParentClassLoader(optionalParentClassLoader);
218: * ee.cook(expression);</pre>
219: *
220: * @see #ExpressionEvaluator()
221: * @see ExpressionEvaluator#setExpressionType(Class)
222: * @see ScriptEvaluator#setParameters(String[], Class[])
223: * @see ScriptEvaluator#setThrownExceptions(Class[])
224: * @see ClassBodyEvaluator#setExtendedType(Class)
225: * @see ClassBodyEvaluator#setImplementedTypes(Class[])
226: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
227: * @see Cookable#cook(String)
228: */
229: public ExpressionEvaluator(String expression, Class expressionType,
230: String[] parameterNames, Class[] parameterTypes,
231: Class[] thrownExceptions, Class optionalExtendedType,
232: Class[] implementedTypes,
233: ClassLoader optionalParentClassLoader)
234: throws CompileException, Parser.ParseException,
235: Scanner.ScanException {
236: this .setExpressionType(expressionType);
237: this .setParameters(parameterNames, parameterTypes);
238: this .setThrownExceptions(thrownExceptions);
239: this .setExtendedType(optionalExtendedType);
240: this .setImplementedTypes(implementedTypes);
241: this .setParentClassLoader(optionalParentClassLoader);
242: this .cook(expression);
243: }
244:
245: /**
246: * Equivalent to<pre>
247: * ExpressionEvaluator ee = new ExpressionEvaluator();
248: * ee.setClassName(className);
249: * ee.setExtendedType(optionalExtendedType);
250: * ee.setImplementedTypes(implementedTypes);
251: * ee.setStaticMethod(staticMethod);
252: * ee.setExpressionType(expressionType);
253: * ee.setMethodName(methodName);
254: * ee.setParameters(parameterNames, parameterTypes);
255: * ee.setThrownExceptions(thrownExceptions);
256: * ee.setParentClassLoader(optionalParentClassLoader);
257: * ee.cook(scanner);
258: *
259: * @see #ExpressionEvaluator()
260: * @see ClassBodyEvaluator#setClassName(String)
261: * @see ClassBodyEvaluator#setExtendedType(Class)
262: * @see ClassBodyEvaluator#setImplementedTypes(Class[])
263: * @see ScriptEvaluator#setStaticMethod(boolean)
264: * @see ExpressionEvaluator#setExpressionType(Class)
265: * @see ScriptEvaluator#setMethodName(String)
266: * @see ScriptEvaluator#setParameters(String[], Class[])
267: * @see ScriptEvaluator#setThrownExceptions(Class[])
268: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
269: * @see Cookable#cook(Scanner)
270: */
271: public ExpressionEvaluator(Scanner scanner, String className,
272: Class optionalExtendedType, Class[] implementedTypes,
273: boolean staticMethod, Class expressionType,
274: String methodName, String[] parameterNames,
275: Class[] parameterTypes, Class[] thrownExceptions,
276: ClassLoader optionalParentClassLoader)
277: throws Scanner.ScanException, Parser.ParseException,
278: CompileException, IOException {
279: this .setClassName(className);
280: this .setExtendedType(optionalExtendedType);
281: this .setImplementedTypes(implementedTypes);
282: this .setStaticMethod(staticMethod);
283: this .setExpressionType(expressionType);
284: this .setMethodName(methodName);
285: this .setParameters(parameterNames, parameterTypes);
286: this .setThrownExceptions(thrownExceptions);
287: this .setParentClassLoader(optionalParentClassLoader);
288: this .cook(scanner);
289: }
290:
291: public ExpressionEvaluator() {
292: }
293:
294: /**
295: * Define the type of the expression. The special type {@link #ANY_TYPE} allows the expression
296: * to return any type (primitive or reference).
297: * <p>
298: * Defaults to {@link #ANY_TYPE}.
299: */
300: public void setExpressionType(Class expressionType) {
301: this .expressionType = expressionType;
302: }
303:
304: public final void setReturnType(Class returnType) {
305: throw new RuntimeException(
306: "Don't call \"setReturnType()\" on an ExpressionEvaluator, user \"setExpressionType()\" instead");
307: }
308:
309: protected void internalCook(Scanner scanner)
310: throws CompileException, Parser.ParseException,
311: Scanner.ScanException, IOException {
312: Java.CompilationUnit compilationUnit = this
313: .makeCompilationUnit(scanner);
314:
315: // Create class, method and block.
316: Java.Block block = this .addClassMethodBlockDeclaration(scanner
317: .location(), // location
318: compilationUnit, // compilationUnit
319: ( // returnType
320: this .expressionType == null ? Object.class
321: : this .expressionType));
322:
323: Parser parser = new Parser(scanner);
324: if (this .expressionType == void.class) {
325:
326: // ExpressionEvaluator with an expression type "void" is a simple expression statement.
327: block.addStatement(new Java.ExpressionStatement(parser
328: .parseExpression().toRvalueOrPE()));
329: } else {
330:
331: // Compute expression value
332: Java.Rvalue value = parser.parseExpression().toRvalueOrPE();
333:
334: // Special case: A "null" expression type means return type "Object" and automatic
335: // wrapping of primitive types.
336: if (this .expressionType == null) {
337: value = new Java.MethodInvocation(scanner.location(), // location
338: new Java.ReferenceType( // optionalTarget
339: scanner.location(), // location
340: new String[] { "org", "codehaus",
341: "janino", "util",
342: "PrimitiveWrapper" } // identifiers
343: ), "wrap", new Java.Rvalue[] { value } // arguments
344: );
345: org.codehaus.janino.util.PrimitiveWrapper.wrap(99); // Make sure "PrimitiveWrapper" is compiled.
346: }
347:
348: // Add a return statement.
349: block.addStatement(new Java.ReturnStatement(scanner
350: .location(), value));
351: }
352: if (!scanner.peek().isEOF())
353: throw new Parser.ParseException("Unexpected token \""
354: + scanner.peek() + "\"", scanner.location());
355:
356: this .compileToMethod(compilationUnit);
357: }
358:
359: /**
360: * Creates a "fast expression evaluator" from the given {@link java.lang.String}
361: * <code>expression</code>, generating a class with the {@link #DEFAULT_CLASS_NAME} that
362: * extends {@link Object}.
363: * <p>
364: * See the class description for an explanation of the "fast expression evaluator" concept.
365: *
366: * @see #createFastExpressionEvaluator(Scanner, String[], String, Class, Class, String[], ClassLoader)
367: * @see ExpressionEvaluator
368: */
369: public static Object createFastExpressionEvaluator(
370: String expression, Class interfaceToImplement,
371: String[] parameterNames, ClassLoader optionalClassLoader)
372: throws CompileException, Parser.ParseException,
373: Scanner.ScanException {
374: ExpressionEvaluator ee = new ExpressionEvaluator();
375: return ScriptEvaluator.createFastEvaluator(ee, expression,
376: parameterNames, interfaceToImplement);
377: }
378:
379: /**
380: * Creates a "fast expression evaluator" from the given {@link Scanner} with no default
381: * imports.
382: * <p>
383: * See the class description for an explanation of the "fast expression evaluator" concept.
384: *
385: * @see #createFastExpressionEvaluator(Scanner, String[], String, Class, Class, String[], ClassLoader)
386: * @see ExpressionEvaluator
387: */
388: public static Object createFastExpressionEvaluator(Scanner scanner,
389: String className, Class optionalExtendedType,
390: Class interfaceToImplement, String[] parameterNames,
391: ClassLoader optionalParentClassLoader)
392: throws CompileException, Parser.ParseException,
393: Scanner.ScanException, IOException {
394: ExpressionEvaluator ee = new ExpressionEvaluator();
395: ee.setClassName(className);
396: ee.setExtendedType(optionalExtendedType);
397: ee.setParentClassLoader(optionalParentClassLoader);
398: return ScriptEvaluator.createFastEvaluator(ee, scanner,
399: parameterNames, interfaceToImplement);
400: }
401:
402: /**
403: * Creates a "fast expression evaluator".
404: * <p>
405: * See the class description for an explanation of the "fast expression evaluator" concept.
406: * <p>
407: * Notice: The <code>interfaceToImplement</code> must either be declared <code>public</code>,
408: * or with package scope in the same package as <code>className</code>.
409: *
410: * @param scanner Source of expression tokens
411: * @param optionalDefaultImports Default imports, e.g. <code>{ "java.util.Map", "java.io.*" }</code>
412: * @param className Name of generated class
413: * @param optionalExtendedType Class to extend
414: * @param interfaceToImplement Must declare exactly the one method that defines the expression's signature
415: * @param parameterNames The expression references the parameters through these names
416: * @param optionalParentClassLoader Used to load referenced classes, defaults to the current thread's "context class loader"
417: * @return an object that implements the given interface and extends the <code>optionalExtendedType</code>
418: * @see ExpressionEvaluator
419: */
420: public static Object createFastExpressionEvaluator(Scanner scanner,
421: String[] optionalDefaultImports, String className,
422: Class optionalExtendedType, Class interfaceToImplement,
423: String[] parameterNames,
424: ClassLoader optionalParentClassLoader)
425: throws CompileException, Parser.ParseException,
426: Scanner.ScanException, IOException {
427: ExpressionEvaluator ee = new ExpressionEvaluator();
428: ee.setClassName(className);
429: ee.setExtendedType(optionalExtendedType);
430: ee.setDefaultImports(optionalDefaultImports);
431: ee.setParentClassLoader(optionalParentClassLoader);
432: return ScriptEvaluator.createFastEvaluator(ee, scanner,
433: parameterNames, interfaceToImplement);
434: }
435: }
|