446 lines
18 KiB
Java
446 lines
18 KiB
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. The name of the author may not be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.janino;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Reader;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import org.codehaus.commons.compiler.CompileException;
|
|
import org.codehaus.commons.compiler.CompilerFactoryFactory;
|
|
import org.codehaus.commons.compiler.Cookable;
|
|
import org.codehaus.commons.compiler.IClassBodyEvaluator;
|
|
import org.codehaus.commons.compiler.ICompilerFactory;
|
|
import org.codehaus.commons.compiler.ICookable;
|
|
import org.codehaus.commons.compiler.IExpressionEvaluator;
|
|
import org.codehaus.commons.compiler.IScriptEvaluator;
|
|
import org.codehaus.commons.compiler.ISimpleCompiler;
|
|
import org.codehaus.commons.compiler.PrimitiveWrapper;
|
|
import org.codehaus.janino.Java.AmbiguousName;
|
|
import org.codehaus.janino.Java.BlockStatement;
|
|
import org.codehaus.janino.Java.Rvalue;
|
|
import org.codehaus.janino.Visitor.RvalueVisitor;
|
|
import org.codehaus.janino.util.Traverser;
|
|
|
|
/**
|
|
* 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>
|
|
* If the parameter and return types of the expression are known at compile time, then a "fast"
|
|
* expression evaluator can be instantiated through
|
|
* {@link #createFastExpressionEvaluator(String, Class, String[], ClassLoader)}. Expression
|
|
* evaluation is faster than through {@link #evaluate(Object[])}, because it is not done through
|
|
* reflection but through direct method invocation.
|
|
* <p>
|
|
* Example:
|
|
* <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>
|
|
* Notice: The <code>interfaceToImplement</code> must either be declared <code>public</code>,
|
|
* or with package scope in the root package (i.e. "no" package).
|
|
* <p>
|
|
* On my system (Intel P4, 2 GHz, MS Windows XP, JDK 1.4.1), expression "x + 1"
|
|
* evaluates as follows:
|
|
* <table>
|
|
* <tr><td></td><th>Server JVM</th><th>Client JVM</th></td></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>
|
|
* (How can it be that interface method invocation is slower than reflection for
|
|
* the server JVM?)
|
|
*/
|
|
@SuppressWarnings({ "rawtypes", "unchecked" }) public
|
|
class ExpressionEvaluator extends ScriptEvaluator implements IExpressionEvaluator {
|
|
|
|
private Class[] optionalExpressionTypes;
|
|
|
|
/**
|
|
* 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(optionalParentClassLoader);
|
|
* 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,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException {
|
|
this.setExpressionType(expressionType);
|
|
this.setParameters(parameterNames, parameterTypes);
|
|
this.setThrownExceptions(thrownExceptions);
|
|
this.setParentClassLoader(optionalParentClassLoader);
|
|
this.cook(expression);
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* ExpressionEvaluator ee = new ExpressionEvaluator();
|
|
* ee.setExpressionType(expressionType);
|
|
* ee.setParameters(parameterNames, parameterTypes);
|
|
* ee.setThrownExceptions(thrownExceptions);
|
|
* ee.setExtendedType(optionalExtendedType);
|
|
* ee.setImplementedTypes(implementedTypes);
|
|
* ee.setParentClassLoader(optionalParentClassLoader);
|
|
* 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,
|
|
Class optionalExtendedType,
|
|
Class[] implementedTypes,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException {
|
|
this.setExpressionType(expressionType);
|
|
this.setParameters(parameterNames, parameterTypes);
|
|
this.setThrownExceptions(thrownExceptions);
|
|
this.setExtendedClass(optionalExtendedType);
|
|
this.setImplementedInterfaces(implementedTypes);
|
|
this.setParentClassLoader(optionalParentClassLoader);
|
|
this.cook(expression);
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* ExpressionEvaluator ee = new ExpressionEvaluator();
|
|
* ee.setClassName(className);
|
|
* ee.setExtendedType(optionalExtendedType);
|
|
* ee.setImplementedTypes(implementedTypes);
|
|
* ee.setStaticMethod(staticMethod);
|
|
* ee.setExpressionType(expressionType);
|
|
* ee.setMethodName(methodName);
|
|
* ee.setParameters(parameterNames, parameterTypes);
|
|
* ee.setThrownExceptions(thrownExceptions);
|
|
* ee.setParentClassLoader(optionalParentClassLoader);
|
|
* ee.cook(scanner);
|
|
*
|
|
* @see IExpressionEvaluator
|
|
* @see IClassBodyEvaluator#setClassName(String)
|
|
* @see IClassBodyEvaluator#setExtendedClass(Class)
|
|
* @see IClassBodyEvaluator#setImplementedInterfaces(Class[])
|
|
* @see IScriptEvaluator#setStaticMethod(boolean)
|
|
* @see IExpressionEvaluator#setExpressionType(Class)
|
|
* @see IScriptEvaluator#setMethodName(String)
|
|
* @see IScriptEvaluator#setParameters(String[], Class[])
|
|
* @see IScriptEvaluator#setThrownExceptions(Class[])
|
|
* @see ISimpleCompiler#setParentClassLoader(ClassLoader)
|
|
* @see ICookable#cook(Reader)
|
|
*/
|
|
public
|
|
ExpressionEvaluator(
|
|
Scanner scanner,
|
|
String className,
|
|
Class optionalExtendedType,
|
|
Class[] implementedTypes,
|
|
boolean staticMethod,
|
|
Class expressionType,
|
|
String methodName,
|
|
String[] parameterNames,
|
|
Class[] parameterTypes,
|
|
Class[] thrownExceptions,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException, IOException {
|
|
this.setClassName(className);
|
|
this.setExtendedClass(optionalExtendedType);
|
|
this.setImplementedInterfaces(implementedTypes);
|
|
this.setStaticMethod(staticMethod);
|
|
this.setExpressionType(expressionType);
|
|
this.setMethodName(methodName);
|
|
this.setParameters(parameterNames, parameterTypes);
|
|
this.setThrownExceptions(thrownExceptions);
|
|
this.setParentClassLoader(optionalParentClassLoader);
|
|
this.cook(scanner);
|
|
}
|
|
|
|
public ExpressionEvaluator() {}
|
|
|
|
@Override public void
|
|
setExpressionType(Class expressionType) { this.setExpressionTypes(new Class[] { expressionType }); }
|
|
|
|
@Override public void
|
|
setExpressionTypes(Class[] expressionTypes) {
|
|
this.assertNotCooked();
|
|
this.optionalExpressionTypes = expressionTypes;
|
|
|
|
Class[] returnTypes = new Class[expressionTypes.length];
|
|
for (int i = 0; i < returnTypes.length; ++i) {
|
|
Class et = expressionTypes[i];
|
|
returnTypes[i] = et == IExpressionEvaluator.ANY_TYPE ? Object.class : et;
|
|
}
|
|
super.setReturnTypes(returnTypes);
|
|
}
|
|
|
|
/** @deprecated {@link #setExpressionType(Class)} should be called instead. */
|
|
@Override @Deprecated public final void
|
|
setReturnType(Class returnType) {
|
|
throw new AssertionError("Must not be used on an ExpressionEvaluator; use 'setExpressionType()' instead");
|
|
}
|
|
|
|
/** @deprecated {@link #setExpressionTypes(Class[])} should be called instead. */
|
|
@Override @Deprecated public final void
|
|
setReturnTypes(Class[] returnTypes) {
|
|
throw new AssertionError("Must not be used on an ExpressionEvaluator; use 'setExpressionTypes()' instead");
|
|
}
|
|
|
|
@Override protected Class
|
|
getDefaultReturnType() { return Object.class; }
|
|
|
|
@Override protected List<BlockStatement>
|
|
makeStatements(int idx, Parser parser) throws CompileException, IOException {
|
|
List<BlockStatement> statements = new ArrayList();
|
|
|
|
// Parse the expression.
|
|
Rvalue value = parser.parseExpression().toRvalueOrCompileException();
|
|
|
|
Class et = (
|
|
this.optionalExpressionTypes == null
|
|
? IExpressionEvaluator.ANY_TYPE
|
|
: this.optionalExpressionTypes[idx]
|
|
);
|
|
if (et == void.class) {
|
|
|
|
// ExpressionEvaluator with an expression type "void" is a simple expression statement.
|
|
statements.add(new Java.ExpressionStatement(value));
|
|
} else {
|
|
|
|
// Special case: Expression type "ANY_TYPE" means return type "Object" and automatic
|
|
// wrapping of primitive types.
|
|
if (et == IExpressionEvaluator.ANY_TYPE) {
|
|
value = new Java.MethodInvocation(
|
|
parser.location(), // location
|
|
new Java.ReferenceType( // optionalTarget
|
|
parser.location(), // location
|
|
new String[] { // identifiers
|
|
"org", "codehaus", "commons", "compiler", "PrimitiveWrapper"
|
|
},
|
|
null // optionalTypeArguments
|
|
),
|
|
"wrap", // methodName
|
|
new Java.Rvalue[] { value } // arguments
|
|
);
|
|
|
|
// Make sure "PrimitiveWrapper" is compiled.
|
|
PrimitiveWrapper.wrap(99);
|
|
|
|
// Verify that "PrimitiveWrapper" is loadable.
|
|
this.classToType(null, PrimitiveWrapper.class);
|
|
}
|
|
|
|
// Add a return statement.
|
|
statements.add(new Java.ReturnStatement(parser.location(), value));
|
|
}
|
|
if (!parser.peekEof()) {
|
|
throw new CompileException("Unexpected token \"" + parser.peek() + "\"", parser.location());
|
|
}
|
|
|
|
return statements;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* {@link IExpressionEvaluator} ee = {@link CompilerFactoryFactory}.{@link
|
|
* CompilerFactoryFactory#getDefaultCompilerFactory() getDefaultCompilerFactory}().{@link
|
|
* ICompilerFactory#newExpressionEvaluator() newExpressionEvaluator}();
|
|
* ee.setParentClassLoader(optionalParentClassLoader);
|
|
* return ee.{@link #createFastEvaluator createFastEvaluator}(expression, interfaceToImplement, parameterNames);
|
|
* </pre>
|
|
*
|
|
* @deprecated Use {@link #createFastEvaluator(String, Class, String[])} instead:
|
|
*/
|
|
@Deprecated public static Object
|
|
createFastExpressionEvaluator(
|
|
String expression,
|
|
Class interfaceToImplement,
|
|
String[] parameterNames,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException {
|
|
IExpressionEvaluator ee = new ExpressionEvaluator();
|
|
ee.setParentClassLoader(optionalParentClassLoader);
|
|
return ee.createFastEvaluator(expression, interfaceToImplement, parameterNames);
|
|
}
|
|
|
|
/**
|
|
* Notice: This method is not declared in {@link IExpressionEvaluator}, and is hence only available in <i>this</i>
|
|
* implementation of <code>org.codehaus.commons.compiler</code>. To be independent from this particular
|
|
* implementation, try to switch to {@link #createFastEvaluator(Reader, Class, String[])}.
|
|
*
|
|
* @deprecated Use {@link #createFastEvaluator(Reader, Class, String[])} instead
|
|
*/
|
|
@Deprecated public static Object
|
|
createFastExpressionEvaluator(
|
|
Scanner scanner,
|
|
String className,
|
|
Class optionalExtendedType,
|
|
Class interfaceToImplement,
|
|
String[] parameterNames,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException, IOException {
|
|
ExpressionEvaluator ee = new ExpressionEvaluator();
|
|
ee.setClassName(className);
|
|
ee.setExtendedClass(optionalExtendedType);
|
|
ee.setParentClassLoader(optionalParentClassLoader);
|
|
return ee.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
|
|
}
|
|
|
|
/**
|
|
* Notice: This method is not declared in {@link IExpressionEvaluator}, and is hence only available in <i>this</i>
|
|
* implementation of <code>org.codehaus.commons.compiler</code>. To be independent from this particular
|
|
* implementation, try to switch to {@link #createFastEvaluator(Reader, Class, String[])}.
|
|
*
|
|
* @deprecated Use {@link #createFastEvaluator(Reader, Class, String[])} instead
|
|
*/
|
|
@Deprecated public static Object
|
|
createFastExpressionEvaluator(
|
|
Scanner scanner,
|
|
String[] optionalDefaultImports,
|
|
String className,
|
|
Class optionalExtendedType,
|
|
Class interfaceToImplement,
|
|
String[] parameterNames,
|
|
ClassLoader optionalParentClassLoader
|
|
) throws CompileException, IOException {
|
|
ExpressionEvaluator ee = new ExpressionEvaluator();
|
|
ee.setClassName(className);
|
|
ee.setExtendedClass(optionalExtendedType);
|
|
ee.setDefaultImports(optionalDefaultImports);
|
|
ee.setParentClassLoader(optionalParentClassLoader);
|
|
return ee.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
|
|
}
|
|
|
|
/**
|
|
* Guess the names of the parameters used in the given expression. The strategy is to look
|
|
* at all "ambiguous names" in the expression (e.g. in "a.b.c.d()", the ambiguous name
|
|
* is "a.b.c"), and then at the first components of the ambiguous name.
|
|
* <ul>
|
|
* <li>If any component starts with an upper-case letter, then ambiguous name is assumed to
|
|
* be a type name.
|
|
* <li>Otherwise, it is assumed to be a parameter name.
|
|
* </ul>
|
|
*
|
|
* @see Scanner#Scanner(String, Reader)
|
|
*/
|
|
public static String[]
|
|
guessParameterNames(Scanner scanner) throws CompileException, IOException {
|
|
Parser parser = new Parser(scanner);
|
|
|
|
// Eat optional leading import declarations.
|
|
while (parser.peek("import")) parser.parseImportDeclaration();
|
|
|
|
// Parse the expression.
|
|
Rvalue rvalue = parser.parseExpression().toRvalueOrCompileException();
|
|
if (!parser.peekEof()) {
|
|
throw new CompileException("Unexpected token \"" + parser.peek() + "\"", scanner.location());
|
|
}
|
|
|
|
// Traverse the expression for ambiguous names and guess which of them are parameter names.
|
|
final Set<String> parameterNames = new HashSet();
|
|
rvalue.accept((RvalueVisitor) new Traverser() {
|
|
|
|
@Override public void
|
|
traverseAmbiguousName(AmbiguousName an) {
|
|
|
|
// If any of the components starts with an upper-case letter, then the ambiguous
|
|
// name is most probably a type name, e.g. "System.out" or "java.lang.System.out".
|
|
for (String identifier : an.identifiers) {
|
|
if (Character.isUpperCase(identifier.charAt(0))) return;
|
|
}
|
|
|
|
// It's most probably a parameter name (although it could be a field name as well).
|
|
parameterNames.add(an.identifiers[0]);
|
|
}
|
|
}.comprehensiveVisitor());
|
|
|
|
return (String[]) parameterNames.toArray(new String[parameterNames.size()]);
|
|
}
|
|
}
|