ExpressionEvaluator.java
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.commons.compiler.jdk;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.Cookable;
import org.codehaus.commons.compiler.ErrorHandler;
import org.codehaus.commons.compiler.IExpressionEvaluator;
import org.codehaus.commons.compiler.InternalCompilerException;
import org.codehaus.commons.compiler.MultiCookable;
import org.codehaus.commons.compiler.WarningHandler;
import org.codehaus.commons.compiler.io.Readers;
import org.codehaus.commons.nullanalysis.Nullable;
/**
* This {@link IExpressionEvaluator} is implemented by creating and compiling a temporary compilation unit defining one
* class with one static method with one RETURN statement.
* <p>
* A number of "convenience constructors" exist that execute the set-up steps described for {@link
* IExpressionEvaluator} instantly.
* </p>
* <p>
* If the parameter and return types of the expression are known at compile time, then a "fast" expression evaluator
* can be instantiated through {@link #createFastEvaluator(String, Class, String[])}. Expression evaluation is faster
* than through {@link #evaluate(Object[])}, because it is not done through reflection but through direct method
* invocation.
* </p>
* <p>
* Example:
* </p>
* <pre>
* public interface Foo {
* int bar(int a, int b);
* }
* ...
* Foo f = (Foo) ExpressionEvaluator.createFastExpressionEvaluator(
* "a + b", // expression to evaluate
* Foo.class, // interface that describes the expression's signature
* new String[] { "a", "b" }, // the parameters' names
* (ClassLoader) null // Use current thread's context class loader
* );
* System.out.println("1 + 2 = " + f.bar(1, 2)); // Evaluate the expression
* </pre>
* <p>
* Notice: The <var>interfaceToImplement</var> must either be declared <var>public</var>, or with package scope in
* the root package (i.e. "no" package).
* </p>
* <p>
* On my system (Intel P4, 2 GHz, MS Windows XP, JDK 1.4.1), expression "x + 1" evaluates as follows:
* </p>
* <table>
* <tr><td></td><th>Server JVM</th><th>Client JVM</th></tr>
* <tr><td>Normal EE</td><td>23.7 ns</td><td>64.0 ns</td></tr>
* <tr><td>Fast EE</td><td>31.2 ns</td><td>42.2 ns</td></tr>
* </table>
* <p>
* (How can it be that interface method invocation is slower than reflection for the server JVM?)
* </p>
*/
public
class ExpressionEvaluator extends MultiCookable implements IExpressionEvaluator {
private final ScriptEvaluator se = new ScriptEvaluator();
{
this.se.setClassName(IExpressionEvaluator.DEFAULT_CLASS_NAME);
this.se.setDefaultReturnType(IExpressionEvaluator.DEFAULT_EXPRESSION_TYPE);
}
/**
* Equivalent to
* <pre>
* ExpressionEvaluator ee = new ExpressionEvaluator();
* ee.setExpressionType(expressionType);
* ee.setParameters(parameterNames, parameterTypes);
* ee.cook(expression);
* </pre>
*
* @see #ExpressionEvaluator()
* @see ExpressionEvaluator#setExpressionType(Class)
* @see ScriptEvaluator#setParameters(String[], Class[])
* @see Cookable#cook(String)
*/
public
ExpressionEvaluator(
String expression,
Class<?> expressionType,
String[] parameterNames,
Class<?>[] parameterTypes
) throws CompileException {
this.setExpressionType(expressionType);
this.setParameters(parameterNames, parameterTypes);
this.cook(expression);
}
/**
* Equivalent to
* <pre>
* ExpressionEvaluator ee = new ExpressionEvaluator();
* ee.setExpressionType(expressionType);
* ee.setParameters(parameterNames, parameterTypes);
* ee.setThrownExceptions(thrownExceptions);
* ee.setParentClassLoader(parentClassLoader);
* ee.cook(expression);
* </pre>
*
* @see #ExpressionEvaluator()
* @see ExpressionEvaluator#setExpressionType(Class)
* @see ScriptEvaluator#setParameters(String[], Class[])
* @see ScriptEvaluator#setThrownExceptions(Class[])
* @see SimpleCompiler#setParentClassLoader(ClassLoader)
* @see Cookable#cook(String)
*/
public
ExpressionEvaluator(
String expression,
Class<?> expressionType,
String[] parameterNames,
Class<?>[] parameterTypes,
Class<?>[] thrownExceptions,
@Nullable ClassLoader parentClassLoader
) throws CompileException {
this.setExpressionType(expressionType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(expression);
}
/**
* Equivalent to
* <pre>
* ExpressionEvaluator ee = new ExpressionEvaluator();
* ee.setExpressionType(expressionType);
* ee.setParameters(parameterNames, parameterTypes);
* ee.setThrownExceptions(thrownExceptions);
* ee.setExtendedType(extendedType);
* ee.setImplementedTypes(implementedTypes);
* ee.setParentClassLoader(parentClassLoader);
* ee.cook(expression);
* </pre>
*
* @see #ExpressionEvaluator()
* @see ExpressionEvaluator#setExpressionType(Class)
* @see ScriptEvaluator#setParameters(String[], Class[])
* @see ScriptEvaluator#setThrownExceptions(Class[])
* @see ClassBodyEvaluator#setExtendedClass(Class)
* @see ClassBodyEvaluator#setImplementedInterfaces(Class[])
* @see SimpleCompiler#setParentClassLoader(ClassLoader)
* @see Cookable#cook(String)
*/
public
ExpressionEvaluator(
String expression,
Class<?> expressionType,
String[] parameterNames,
Class<?>[] parameterTypes,
Class<?>[] thrownExceptions,
@Nullable Class<?> extendedType,
Class<?>[] implementedTypes,
@Nullable ClassLoader parentClassLoader
) throws CompileException {
this.setExpressionType(expressionType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setExtendedClass(extendedType);
this.setImplementedInterfaces(implementedTypes);
this.setParentClassLoader(parentClassLoader);
this.cook(expression);
}
public ExpressionEvaluator() {}
// ============================= CONFIGURATION SETTS AND GETTERS =============================
@Override public void
setParentClassLoader(@Nullable ClassLoader parentClassLoader) {
this.se.setParentClassLoader(parentClassLoader);
}
@Override public void
setDebuggingInformation(boolean debugSource, boolean debugLines, boolean debugVars) {
this.se.setDebuggingInformation(debugSource, debugLines, debugVars);
}
@Override public void
setSourceVersion(int version) { this.se.setSourceVersion(version); }
@Override public void
setTargetVersion(int version) { this.se.setTargetVersion(version); }
@Override public void
setCompileErrorHandler(@Nullable ErrorHandler compileErrorHandler) {
this.se.setCompileErrorHandler(compileErrorHandler);
}
@Override public void
setWarningHandler(@Nullable WarningHandler warningHandler) {
this.se.setWarningHandler(warningHandler);
}
@Override public void
setDefaultImports(String... defaultImports) { this.se.setDefaultImports(defaultImports); }
@Override public String[]
getDefaultImports() { return this.se.getDefaultImports(); }
@Override public void
setDefaultExpressionType(Class<?> defaultExpressionType) { this.se.setDefaultReturnType(defaultExpressionType); }
@Override public Class<?>
getDefaultExpressionType() { return this.se.getDefaultReturnType(); }
@Override public void
setImplementedInterfaces(Class<?>[] implementedTypes) { this.se.setImplementedInterfaces(implementedTypes); }
@Override public void
setReturnType(Class<?> returnType) { this.se.setReturnType(returnType); }
@Override public void
setExpressionType(Class<?> expressionType) { this.se.setReturnType(expressionType); }
@Override public void
setExpressionTypes(Class<?>[] expressionTypes) { this.se.setReturnTypes(expressionTypes); }
@Override public void
setOverrideMethod(boolean overrideMethod) { this.se.setOverrideMethod(overrideMethod); }
@Override public void
setOverrideMethod(boolean[] overrideMethod) { this.se.setOverrideMethod(overrideMethod); }
@Override public void
setParameters(String[] parameterNames, Class<?>[] parameterTypes) {
this.se.setParameters(parameterNames, parameterTypes);
}
@Override public void
setParameters(String[][] parameterNames, Class<?>[][] parameterTypes) {
this.se.setParameters(parameterNames, parameterTypes);
}
@Override public void
setClassName(String className) { this.se.setClassName(className); }
@Override public void
setExtendedClass(@Nullable Class<?> extendedType) { this.se.setExtendedClass(extendedType); }
@Override public void
setStaticMethod(boolean staticMethod) { this.se.setStaticMethod(staticMethod); }
@Override public void
setStaticMethod(boolean[] staticMethod) { this.se.setStaticMethod(staticMethod); }
@Override public void
setMethodName(String methodName) { this.se.setMethodName(methodName); }
@Override public void
setMethodNames(String[] methodNames) { this.se.setMethodNames(methodNames); }
@Override public void
setThrownExceptions(Class<?>[] thrownExceptions) { this.se.setThrownExceptions(thrownExceptions); }
@Override public void
setThrownExceptions(Class<?>[][] thrownExceptions) { this.se.setThrownExceptions(thrownExceptions); }
@Override public void
cook(@Nullable String fileName, Reader reader) throws CompileException, IOException {
this.se.setScriptCount(1);
if (!reader.markSupported()) reader = new BufferedReader(reader);
final String[] imports = ClassBodyEvaluator.parseImportDeclarations(reader);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
Class<?> returnType = this.se.getReturnType(0);
if (returnType != void.class && returnType != Void.class) pw.print("return ");
Readers.copy(reader, pw);
pw.println(";");
pw.close();
} finally {
try { pw.close(); } catch (Exception e) {}
}
reader = new StringReader(sw.toString());
this.se.cook(new String[] { fileName }, new Reader[] { reader }, imports);
}
@Override public void
cook(String[] fileNames, Reader[] readers) throws CompileException, IOException {
readers = readers.clone(); // Don't modify the argument array.
String[] imports;
if (readers.length == 1) {
if (!readers[0].markSupported()) readers[0] = new BufferedReader(readers[0]);
imports = ClassBodyEvaluator.parseImportDeclarations(readers[0]);
} else
{
imports = new String[0];
}
Class<?>[] returnTypes = new Class[readers.length];
for (int i = 0; i < readers.length; ++i) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
returnTypes[i] = this.se.getReturnType(i);
if (returnTypes[i] != void.class && returnTypes[i] != Void.class) {
pw.print("return ");
}
Readers.copy(readers[i], pw);
pw.println(";");
pw.close();
readers[i] = new StringReader(sw.toString());
}
this.se.cook(fileNames, readers, imports);
}
@Override @Nullable public Object
evaluate() throws InvocationTargetException { return this.evaluate(new Object[0]); }
@Override @Nullable public Object
evaluate(@Nullable Object[] arguments) throws InvocationTargetException { return this.se.evaluate(arguments); }
@Override @Nullable public Object
evaluate(int idx, @Nullable Object[] arguments) throws InvocationTargetException {
return this.se.evaluate(idx, arguments);
}
@Override public Method
getMethod() { return this.se.getMethod(); }
@Override public Method
getMethod(int idx) { return this.se.getMethod(idx); }
@Override public Class<?>
getClazz() { return this.se.getClazz(); }
@Override public Method[]
getResult() { return this.se.getResult(); }
@Override public Map<String, byte[]>
getBytecodes() { return this.se.getBytecodes(); }
@Override public <T> T
createFastEvaluator(String expression, Class<? extends T> interfaceToImplement, String... parameterNames)
throws CompileException {
try {
return this.createFastEvaluator(
new StringReader(expression),
interfaceToImplement,
parameterNames
);
} catch (IOException ex) {
throw new InternalCompilerException("IOException despite StringReader", ex);
}
}
@Override public <T> T
createFastEvaluator(Reader reader, Class<? extends T> interfaceToImplement, String... parameterNames)
throws CompileException, IOException {
if (!interfaceToImplement.isInterface()) {
throw new InternalCompilerException("\"" + interfaceToImplement + "\" is not an interface");
}
Method methodToImplement;
{
Method[] methods = interfaceToImplement.getDeclaredMethods();
if (methods.length != 1) {
throw new InternalCompilerException(
"Interface \""
+ interfaceToImplement
+ "\" must declare exactly one method"
);
}
methodToImplement = methods[0];
}
this.setImplementedInterfaces(new Class[] { interfaceToImplement });
this.setOverrideMethod(true);
this.setStaticMethod(false);
this.setExpressionType(methodToImplement.getReturnType());
this.setMethodName(methodToImplement.getName());
this.setParameters(parameterNames, methodToImplement.getParameterTypes());
this.setThrownExceptions(methodToImplement.getExceptionTypes());
this.cook(reader);
@SuppressWarnings("unchecked") Class<? extends T>
actualClass = (Class<? extends T>) this.getMethod().getDeclaringClass();
try {
return actualClass.newInstance();
} catch (InstantiationException e) {
// SNO - Declared class is always non-abstract.
throw new InternalCompilerException(e.toString(), e);
} catch (IllegalAccessException e) {
// SNO - interface methods are always PUBLIC.
throw new InternalCompilerException(e.toString(), e);
}
}
}