/* * 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.InputStream; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.Cookable; import org.codehaus.commons.compiler.IExpressionEvaluator; import org.codehaus.commons.compiler.IScriptEvaluator; import org.codehaus.commons.compiler.Location; import org.codehaus.janino.Java.VariableDeclarator; import org.codehaus.janino.util.Traverser; /** A number of "convenience constructors" exist that execute the setup steps instantly. Their use is discouraged. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class ScriptEvaluator extends ClassBodyEvaluator implements IScriptEvaluator { /** Whether methods override a method declared by a supertype; {@code null} means "none". */ protected boolean[] optionalOverrideMethod; /** Whether methods are static; {@code null} means "all". */ protected boolean[] optionalStaticMethod; /** The methods' return types; {@code null} means "none". */ protected Class[] optionalReturnTypes; private String[] optionalMethodNames; private String[][] optionalParameterNames; private Class[][] optionalParameterTypes; private Class[][] optionalThrownExceptions; private Method[] result; // null=uncooked /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.cook(script);* * @see #ScriptEvaluator() * @see Cookable#cook(String) */ public ScriptEvaluator(String script) throws CompileException { this.cook(script); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.cook(script);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see Cookable#cook(String) */ public ScriptEvaluator(String script, Class returnType) throws CompileException { this.setReturnType(returnType); this.cook(script); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.cook(script);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see Cookable#cook(String) */ public ScriptEvaluator(String script, Class returnType, String[] parameterNames, Class[] parameterTypes) throws CompileException { this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.cook(script); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.cook(script);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see Cookable#cook(String) */ public ScriptEvaluator( String script, Class returnType, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions ) throws CompileException { this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.cook(script); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.setParentClassLoader(optionalParentClassLoader); * se.cook(optionalFileName, is);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see SimpleCompiler#setParentClassLoader(ClassLoader) * @see Cookable#cook(String, InputStream) */ public ScriptEvaluator( String optionalFileName, InputStream is, Class returnType, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions, ClassLoader optionalParentClassLoader // null = use current thread's context class loader ) throws CompileException, IOException { this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.setParentClassLoader(optionalParentClassLoader); this.cook(optionalFileName, is); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.setParentClassLoader(optionalParentClassLoader); * se.cook(reader);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see SimpleCompiler#setParentClassLoader(ClassLoader) * @see Cookable#cook(String, Reader) */ public ScriptEvaluator( String optionalFileName, Reader reader, Class returnType, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions, ClassLoader optionalParentClassLoader // null = use current thread's context class loader ) throws CompileException, IOException { this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.setParentClassLoader(optionalParentClassLoader); this.cook(optionalFileName, reader); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.setParentClassLoader(optionalParentClassLoader); * se.cook(scanner);* * @see #ScriptEvaluator() * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see SimpleCompiler#setParentClassLoader(ClassLoader) * @see Cookable#cook(Reader) */ public ScriptEvaluator( Scanner scanner, Class returnType, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions, ClassLoader optionalParentClassLoader // null = use current thread's context class loader ) throws CompileException, IOException { this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.setParentClassLoader(optionalParentClassLoader); this.cook(scanner); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setExtendedType(optionalExtendedType); * se.setImplementedTypes(implementedTypes); * se.setReturnType(returnType); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.setParentClassLoader(optionalParentClassLoader); * se.cook(scanner);* * @see #ScriptEvaluator() * @see ClassBodyEvaluator#setExtendedClass(Class) * @see ClassBodyEvaluator#setImplementedInterfaces(Class[]) * @see #setReturnType(Class) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see SimpleCompiler#setParentClassLoader(ClassLoader) * @see Cookable#cook(Reader) */ public ScriptEvaluator( Scanner scanner, Class optionalExtendedType, Class[] implementedTypes, Class returnType, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions, ClassLoader optionalParentClassLoader // null = use current thread's context class loader ) throws CompileException, IOException { this.setExtendedClass(optionalExtendedType); this.setImplementedInterfaces(implementedTypes); this.setReturnType(returnType); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.setParentClassLoader(optionalParentClassLoader); this.cook(scanner); } /** * Equivalent to
* ScriptEvaluator se = new ScriptEvaluator(); * se.setClassName(className); * se.setExtendedType(optionalExtendedType); * se.setImplementedTypes(implementedTypes); * se.setStaticMethod(staticMethod); * se.setReturnType(returnType); * se.setMethodName(methodName); * se.setParameters(parameterNames, parameterTypes); * se.setThrownExceptions(thrownExceptions); * se.setParentClassLoader(optionalParentClassLoader); * se.cook(scanner);* * @see #ScriptEvaluator() * @see ClassBodyEvaluator#setClassName(String) * @see ClassBodyEvaluator#setExtendedClass(Class) * @see ClassBodyEvaluator#setImplementedInterfaces(Class[]) * @see #setStaticMethod(boolean) * @see #setReturnType(Class) * @see #setMethodName(String) * @see #setParameters(String[], Class[]) * @see #setThrownExceptions(Class[]) * @see SimpleCompiler#setParentClassLoader(ClassLoader) * @see Cookable#cook(Reader) */ public ScriptEvaluator( Scanner scanner, String className, Class optionalExtendedType, Class[] implementedTypes, boolean staticMethod, Class returnType, String methodName, String[] parameterNames, Class[] parameterTypes, Class[] thrownExceptions, ClassLoader optionalParentClassLoader // null = use current thread's context class loader ) throws CompileException, IOException { this.setClassName(className); this.setExtendedClass(optionalExtendedType); this.setImplementedInterfaces(implementedTypes); this.setStaticMethod(staticMethod); this.setReturnType(returnType); this.setMethodName(methodName); this.setParameters(parameterNames, parameterTypes); this.setThrownExceptions(thrownExceptions); this.setParentClassLoader(optionalParentClassLoader); this.cook(scanner); } /** * Constructs a script evaluator with all the default settings (return type {@code void} */ public ScriptEvaluator() {} @Override public void setOverrideMethod(boolean overrideMethod) { this.setOverrideMethod(new boolean[] { overrideMethod }); } @Override public void setStaticMethod(boolean staticMethod) { this.setStaticMethod(new boolean[] { staticMethod }); } /** * Defines the return types of the generated methods. * * @param returnType The method's return type; {@code null} means the "default return type", which is the type * returned by {@link #getDefaultReturnType()} ({@code void.class} for {@link ScriptEvaluator} * and {@code Object.class} for {@link ExpressionEvaluator}) * @see ScriptEvaluator#getDefaultReturnType() * @see ExpressionEvaluator#getDefaultReturnType() */ @Override public void setReturnType(Class returnType) { this.setReturnTypes(new Class[] { returnType }); } @Override public void setMethodName(String methodName) { this.setMethodNames(new String[] { methodName }); } @Override public void setParameters(String[] parameterNames, Class[] parameterTypes) { this.setParameters(new String[][] { parameterNames }, new Class[][] { parameterTypes }); } @Override public void setThrownExceptions(Class[] thrownExceptions) { this.setThrownExceptions(new Class[][] { thrownExceptions }); } @Override public final void cook(Scanner scanner) throws CompileException, IOException { this.cook(new Scanner[] { scanner }); } @Override public Object evaluate(Object[] arguments) throws InvocationTargetException { return this.evaluate(0, arguments); } @Override public Method getMethod() { return this.getMethod(0); } @Override public void setOverrideMethod(boolean[] overrideMethod) { this.assertNotCooked(); this.optionalOverrideMethod = overrideMethod.clone(); } @Override public void setStaticMethod(boolean[] staticMethod) { this.assertNotCooked(); this.optionalStaticMethod = staticMethod.clone(); } /** * Defines the return types of the generated methods. * * @param returnTypes The methods' return types; {@code null} elements mean the "default return type", which is the * type returned by {@link #getDefaultReturnType()} ({@code void.class} for {@link * ScriptEvaluator} and {@code Object.class} for {@link ExpressionEvaluator}) * @see ScriptEvaluator#getDefaultReturnType() * @see ExpressionEvaluator#getDefaultReturnType() */ @Override public void setReturnTypes(Class[] returnTypes) { this.assertNotCooked(); this.optionalReturnTypes = returnTypes.clone(); } @Override public void setMethodNames(String[] methodNames) { this.assertNotCooked(); this.optionalMethodNames = methodNames.clone(); } @Override public void setParameters(String[][] parameterNames, Class[][] parameterTypes) { this.assertNotCooked(); this.optionalParameterNames = parameterNames.clone(); this.optionalParameterTypes = parameterTypes.clone(); } @Override public void setThrownExceptions(Class[][] thrownExceptions) { this.assertNotCooked(); this.optionalThrownExceptions = thrownExceptions.clone(); } /** * Like {@link #cook(Scanner)}, but cooks a set of scripts into one class. Notice that * if any of the scripts causes trouble, the entire compilation will fail. If you * need to report which of the scripts causes the exception, you may want to use the *
optionalFileName
argument of {@link Scanner#Scanner(String, Reader)} to
* distinguish between the individual token sources.
* * On a 2 GHz Intel Pentium Core Duo under Windows XP with an IBM 1.4.2 JDK, compiling * 10000 expressions "a + b" (integer) takes about 4 seconds and 56 MB of main memory. * The generated class file is 639203 bytes large. *
* The number and the complexity of the scripts is restricted by the
* Limitations
* of the Java Virtual Machine, where the most limiting factor is the 64K entries limit
* of the constant pool. Since every method with a distinct name requires one entry there,
* you can define at best 32K (very simple) scripts.
*
* If and only if the number of scanners is one, then that single script may contain leading
* IMPORT directives.
*
* @throws IllegalStateException Any of the preceeding
* The number and the complexity of the scripts is restricted by the
* Limitations
* of the Java Virtual Machine, where the most limiting factor is the 64K entries limit
* of the constant pool. Since every method with a distinct name requires one entry there,
* you can define at best 32K (very simple) scripts.
*/
@Override public final void
cook(String[] optionalFileNames, Reader[] readers) throws CompileException, IOException {
Scanner[] scanners = new Scanner[readers.length];
for (int i = 0; i < readers.length; ++i) {
scanners[i] = new Scanner(optionalFileNames == null ? null : optionalFileNames[i], readers[i]);
}
this.cook(scanners);
}
@Override public final void
cook(String[] strings) throws CompileException { this.cook(null, strings); }
@Override public final void
cook(String[] optionalFileNames, String[] strings) throws CompileException {
Reader[] readers = new Reader[strings.length];
for (int i = 0; i < strings.length; ++i) readers[i] = new StringReader(strings[i]);
try {
this.cook(optionalFileNames, readers);
} catch (IOException ex) {
throw new JaninoRuntimeException("SNO: IOException despite StringReader", ex);
}
}
/**
* @return {@code void.class}
* @see #setReturnTypes(Class[])
*/
protected Class
getDefaultReturnType() { return void.class; }
/** Fills the given set...()
had an array size different from that
* of scanners
*/
public final void
cook(Scanner[] scanners) throws CompileException, IOException {
if (scanners == null) throw new NullPointerException();
Parser[] parsers = new Parser[scanners.length];
for (int i = 0; i < scanners.length; ++i) {
parsers[i] = new Parser(scanners[i]);
}
this.cook(parsers);
}
/** @see #cook(Scanner[]) */
public final void
cook(Parser[] parsers) throws CompileException, IOException {
// The "dimension" of this ScriptEvaluator, i.e. how many scripts are cooked at the same
// time.
int count = parsers.length;
// Check array sizes.
if (this.optionalMethodNames != null && this.optionalMethodNames.length != count) {
throw new IllegalStateException("methodName count");
}
if (this.optionalParameterNames != null && this.optionalParameterNames.length != count) {
throw new IllegalStateException("parameterNames count");
}
if (this.optionalParameterTypes != null && this.optionalParameterTypes.length != count) {
throw new IllegalStateException("parameterTypes count");
}
if (this.optionalOverrideMethod != null && this.optionalOverrideMethod.length != count) {
throw new IllegalStateException("overrideMethod count");
}
if (this.optionalReturnTypes != null && this.optionalReturnTypes.length != count) {
throw new IllegalStateException("returnTypes count");
}
if (this.optionalStaticMethod != null && this.optionalStaticMethod.length != count) {
throw new IllegalStateException("staticMethod count");
}
if (this.optionalThrownExceptions != null && this.optionalThrownExceptions.length != count) {
throw new IllegalStateException("thrownExceptions count");
}
// Create compilation unit.
Java.CompilationUnit compilationUnit = this.makeCompilationUnit(count == 1 ? parsers[0] : null);
// Create class declaration.
Java.ClassDeclaration cd = this.addPackageMemberClassDeclaration(parsers[0].location(), compilationUnit);
// Determine method names.
String[] methodNames;
if (this.optionalMethodNames == null) {
methodNames = new String[count];
for (int i = 0; i < count; ++i) methodNames[i] = "eval" + i;
} else
{
methodNames = this.optionalMethodNames;
}
// Create methods with one block each.
for (int i = 0; i < count; ++i) {
Parser parser = parsers[i];
Listblock
by parsing statements until EOF and adding them to the block. */
protected List
*
*
* @param returnType Return type of the declared method
*/
protected Java.MethodDeclarator
makeMethodDeclaration(
Location location,
Java.Annotation[] annotations,
boolean staticMethod,
Class returnType,
String methodName,
Class[] parameterTypes,
String[] parameterNames,
Class[] thrownExceptions,
List
* {@link ScriptEvaluator} se = new {@link ScriptEvaluator#ScriptEvaluator() ScriptEvaluator}();
* se.{@link #setDefaultImports(String[]) setDefaultImports}.(optionalDefaultImports);
* se.{@link #setClassName(String) setClassName}.(className);
* se.{@link #setExtendedClass(Class) setExtendedClass}.(optionalExtendedClass);
* se.{@link #setParentClassLoader(ClassLoader) setParentClassLoader}(optionalParentClassLoader);
* return se.{@link #createFastEvaluator(Scanner, Class, String[]) createFastEvaluator}(scanner,
* interfaceToImplement, parameterNames);
*
*
* @deprecated Use {@link #createFastEvaluator(Scanner,Class,String[])} instead:
*/
@Deprecated public static Object
createFastScriptEvaluator(
Scanner scanner,
String[] optionalDefaultImports,
String className,
Class optionalExtendedClass,
Class interfaceToImplement,
String[] parameterNames,
ClassLoader optionalParentClassLoader
) throws CompileException, IOException {
ScriptEvaluator se = new ScriptEvaluator();
se.setDefaultImports(optionalDefaultImports);
se.setClassName(className);
se.setExtendedClass(optionalExtendedClass);
se.setParentClassLoader(optionalParentClassLoader);
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
}
/** Don't use. */
@Override public final Object
createInstance(Reader reader) {
throw new UnsupportedOperationException("createInstance");
}
@Override public Object
createFastEvaluator(Reader reader, Class interfaceToImplement, String[] parameterNames)
throws CompileException, IOException {
return this.createFastEvaluator(new Scanner(null, reader), interfaceToImplement, parameterNames);
}
@Override public Object
createFastEvaluator(String script, Class interfaceToImplement, String[] parameterNames) throws CompileException {
try {
return this.createFastEvaluator(
new StringReader(script),
interfaceToImplement,
parameterNames
);
} catch (IOException ex) {
throw new JaninoRuntimeException("IOException despite StringReader", ex);
}
}
/**
* Notice: This method is not declared in {@link IScriptEvaluator}, and is hence only available in this
* implementation of org.codehaus.commons.compiler
. To be independent from this particular
* implementation, try to switch to {@link #createFastEvaluator(Reader, Class, String[])}.
*
* @param scanner Source of tokens to read
* @see #createFastEvaluator(Reader, Class, String[])
*/
public Object
createFastEvaluator(Scanner scanner, Class interfaceToImplement, String[] parameterNames)
throws CompileException, IOException {
if (!interfaceToImplement.isInterface()) {
throw new JaninoRuntimeException("\"" + interfaceToImplement + "\" is not an interface");
}
Method methodToImplement;
{
Method[] methods = interfaceToImplement.getDeclaredMethods();
if (methods.length != 1) {
throw new JaninoRuntimeException(
"Interface \""
+ interfaceToImplement
+ "\" must declare exactly one method"
);
}
methodToImplement = methods[0];
}
this.setImplementedInterfaces(new Class[] { interfaceToImplement });
this.setOverrideMethod(true);
this.setStaticMethod(false);
if (this instanceof IExpressionEvaluator) {
// Must not call "IExpressionEvaluator.setReturnType()".
((IExpressionEvaluator) this).setExpressionType(methodToImplement.getReturnType());
} else {
this.setReturnType(methodToImplement.getReturnType());
}
this.setMethodName(methodToImplement.getName());
this.setParameters(parameterNames, methodToImplement.getParameterTypes());
this.setThrownExceptions(methodToImplement.getExceptionTypes());
this.cook(scanner);
Class c = this.getMethod().getDeclaringClass();
try {
return c.newInstance();
} catch (InstantiationException e) {
// SNO - Declared class is always non-abstract.
throw new JaninoRuntimeException(e.toString(), e);
} catch (IllegalAccessException e) {
// SNO - interface methods are always PUBLIC.
throw new JaninoRuntimeException(e.toString(), e);
}
}
/**
* 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 components of the ambiguous name.
*
*
*
* @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 script statements into a block.
Java.Block block = new Java.Block(scanner.location());
while (!parser.peekEof()) block.addStatement(parser.parseBlockStatement());
// Traverse the block for ambiguous names and guess which of them are parameter names.
final Set