994 lines
40 KiB
Java
994 lines
40 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.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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.cook(script);</pre>
|
||
|
*
|
||
|
* @see #ScriptEvaluator()
|
||
|
* @see Cookable#cook(String)
|
||
|
*/
|
||
|
public
|
||
|
ScriptEvaluator(String script) throws CompileException {
|
||
|
this.cook(script);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Equivalent to<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.cook(script);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.setParameters(parameterNames, parameterTypes);
|
||
|
* se.cook(script);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.setParameters(parameterNames, parameterTypes);
|
||
|
* se.setThrownExceptions(thrownExceptions);
|
||
|
* se.cook(script);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.setParameters(parameterNames, parameterTypes);
|
||
|
* se.setThrownExceptions(thrownExceptions);
|
||
|
* se.setParentClassLoader(optionalParentClassLoader);
|
||
|
* se.cook(optionalFileName, is);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.setParameters(parameterNames, parameterTypes);
|
||
|
* se.setThrownExceptions(thrownExceptions);
|
||
|
* se.setParentClassLoader(optionalParentClassLoader);
|
||
|
* se.cook(reader);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* ScriptEvaluator se = new ScriptEvaluator();
|
||
|
* se.setReturnType(returnType);
|
||
|
* se.setParameters(parameterNames, parameterTypes);
|
||
|
* se.setThrownExceptions(thrownExceptions);
|
||
|
* se.setParentClassLoader(optionalParentClassLoader);
|
||
|
* se.cook(scanner);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* 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);</pre>
|
||
|
*
|
||
|
* @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<pre>
|
||
|
* 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);</pre>
|
||
|
*
|
||
|
* @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 <i>set</i> of scripts into one class. Notice that
|
||
|
* if <i>any</i> of the scripts causes trouble, the entire compilation will fail. If you
|
||
|
* need to report <i>which</i> of the scripts causes the exception, you may want to use the
|
||
|
* <code>optionalFileName</code> argument of {@link Scanner#Scanner(String, Reader)} to
|
||
|
* distinguish between the individual token sources.
|
||
|
* <p>
|
||
|
* 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.
|
||
|
* <p>
|
||
|
* The number and the complexity of the scripts is restricted by the
|
||
|
* <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#88659">Limitations
|
||
|
* of the Java Virtual Machine</a>, 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 <code>set...()</code> had an array size different from that
|
||
|
* of <code>scanners</code>
|
||
|
*/
|
||
|
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];
|
||
|
|
||
|
List<Java.BlockStatement> statements = this.makeStatements(i, parser);
|
||
|
|
||
|
// Determine the following script properties AFTER the call to "makeBlock()",
|
||
|
// because "makeBlock()" may modify these script properties on-the-fly.
|
||
|
boolean staticMethod = this.optionalStaticMethod == null || this.optionalStaticMethod[i];
|
||
|
boolean overrideMethod = this.optionalOverrideMethod != null && this.optionalOverrideMethod[i];
|
||
|
|
||
|
Class returnType = (
|
||
|
this.optionalReturnTypes == null
|
||
|
? this.getDefaultReturnType()
|
||
|
: this.optionalReturnTypes[i]
|
||
|
);
|
||
|
String[] parameterNames = (
|
||
|
this.optionalParameterNames == null
|
||
|
? new String[0]
|
||
|
: this.optionalParameterNames[i]
|
||
|
);
|
||
|
Class[] parameterTypes = (
|
||
|
this.optionalParameterTypes == null
|
||
|
? new Class[0]
|
||
|
: this.optionalParameterTypes[i]
|
||
|
);
|
||
|
Class[] thrownExceptions = (
|
||
|
this.optionalThrownExceptions == null
|
||
|
? new Class[0]
|
||
|
: this.optionalThrownExceptions[i]
|
||
|
);
|
||
|
|
||
|
// If the method is non-static, assume that it overrides a method in a supertype.
|
||
|
Location loc = parser.location();
|
||
|
cd.addDeclaredMethod(this.makeMethodDeclaration(
|
||
|
loc, // location
|
||
|
( // annotations
|
||
|
overrideMethod
|
||
|
? new Java.Annotation[] { new Java.MarkerAnnotation(this.classToType(loc, Override.class)) }
|
||
|
: new Java.Annotation[0]
|
||
|
),
|
||
|
staticMethod, // staticMethod
|
||
|
returnType, // returnType
|
||
|
methodNames[i], // methodName
|
||
|
parameterTypes, // parameterTypes
|
||
|
parameterNames, // parameterNames
|
||
|
thrownExceptions, // thrownExceptions
|
||
|
statements // statements
|
||
|
));
|
||
|
}
|
||
|
|
||
|
// Compile and load the compilation unit.
|
||
|
Class c = this.compileToClass(compilationUnit);
|
||
|
|
||
|
// Find the script methods by name.
|
||
|
this.result = new Method[count];
|
||
|
if (count <= 10) {
|
||
|
for (int i = 0; i < count; ++i) {
|
||
|
try {
|
||
|
this.result[i] = c.getDeclaredMethod(
|
||
|
methodNames[i],
|
||
|
this.optionalParameterTypes == null ? new Class[0] : this.optionalParameterTypes[i]
|
||
|
);
|
||
|
} catch (NoSuchMethodException ex) {
|
||
|
throw new JaninoRuntimeException((
|
||
|
"SNO: Loaded class does not declare method \""
|
||
|
+ methodNames[i]
|
||
|
+ "\""
|
||
|
), ex);
|
||
|
}
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
class MethodWrapper {
|
||
|
|
||
|
private final String name;
|
||
|
private final Class[] parameterTypes;
|
||
|
|
||
|
MethodWrapper(String name, Class[] parameterTypes) {
|
||
|
this.name = name;
|
||
|
this.parameterTypes = parameterTypes;
|
||
|
}
|
||
|
|
||
|
@Override public boolean
|
||
|
equals(Object o) {
|
||
|
if (!(o instanceof MethodWrapper)) return false;
|
||
|
MethodWrapper that = (MethodWrapper) o;
|
||
|
if (!this.name.equals(that.name)) return false;
|
||
|
int cnt = this.parameterTypes.length;
|
||
|
if (cnt != that.parameterTypes.length) return false;
|
||
|
for (int i = 0; i < cnt; ++i) {
|
||
|
if (!this.parameterTypes[i].equals(that.parameterTypes[i])) return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override public int
|
||
|
hashCode() {
|
||
|
int hc = this.name.hashCode();
|
||
|
for (Class parameterType : this.parameterTypes) hc ^= parameterType.hashCode();
|
||
|
return hc;
|
||
|
}
|
||
|
}
|
||
|
Method[] ma = c.getDeclaredMethods();
|
||
|
Map<MethodWrapper, Method> dms = new HashMap(2 * count);
|
||
|
for (Method m : ma) dms.put(new MethodWrapper(m.getName(), m.getParameterTypes()), m);
|
||
|
for (int i = 0; i < count; ++i) {
|
||
|
Method m = (Method) dms.get(new MethodWrapper(
|
||
|
methodNames[i],
|
||
|
this.optionalParameterTypes == null ? new Class[0] : this.optionalParameterTypes[i]
|
||
|
));
|
||
|
if (m == null) {
|
||
|
throw new JaninoRuntimeException(
|
||
|
"SNO: Loaded class does not declare method \""
|
||
|
+ methodNames[i]
|
||
|
+ "\""
|
||
|
);
|
||
|
}
|
||
|
this.result[i] = m;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override public final void
|
||
|
cook(Reader[] readers) throws CompileException, IOException {
|
||
|
this.cook(new String[readers.length], readers);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
* <p>
|
||
|
* The number and the complexity of the scripts is restricted by the
|
||
|
* <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#88659">Limitations
|
||
|
* of the Java Virtual Machine</a>, 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 <code>block</code> by parsing statements until EOF and adding them to the block. */
|
||
|
protected List<Java.BlockStatement>
|
||
|
makeStatements(int idx, Parser parser) throws CompileException, IOException {
|
||
|
List<Java.BlockStatement> statements = new ArrayList();
|
||
|
while (!parser.peekEof()) {
|
||
|
statements.add(parser.parseBlockStatement());
|
||
|
}
|
||
|
|
||
|
return statements;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To the given {@link Java.ClassDeclaration}, add
|
||
|
* <ul>
|
||
|
* <li>A public method declaration with the given return type, name, parameter
|
||
|
* names and values and thrown exceptions
|
||
|
* <li>A block
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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<Java.BlockStatement> statements
|
||
|
) {
|
||
|
if (parameterNames.length != parameterTypes.length) {
|
||
|
throw new JaninoRuntimeException(
|
||
|
"Lengths of \"parameterNames\" ("
|
||
|
+ parameterNames.length
|
||
|
+ ") and \"parameterTypes\" ("
|
||
|
+ parameterTypes.length
|
||
|
+ ") do not match"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
Java.FunctionDeclarator.FormalParameters fps = new Java.FunctionDeclarator.FormalParameters(
|
||
|
location,
|
||
|
new Java.FunctionDeclarator.FormalParameter[parameterNames.length],
|
||
|
false
|
||
|
);
|
||
|
|
||
|
for (int i = 0; i < fps.parameters.length; ++i) {
|
||
|
fps.parameters[i] = new Java.FunctionDeclarator.FormalParameter(
|
||
|
location, // location
|
||
|
true, // finaL
|
||
|
this.classToType(location, parameterTypes[i]), // type
|
||
|
parameterNames[i] // name
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return new Java.MethodDeclarator(
|
||
|
location, // location
|
||
|
null, // optionalDocComment
|
||
|
new Java.Modifiers( // modifiers
|
||
|
staticMethod ? (short) (Mod.PUBLIC | Mod.STATIC) : (short) Mod.PUBLIC,
|
||
|
annotations
|
||
|
),
|
||
|
this.classToType(location, returnType), // type
|
||
|
methodName, // name
|
||
|
fps, // formalParameters
|
||
|
this.classesToTypes(location, thrownExceptions), // thrownExceptions
|
||
|
statements // optionalStatements
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #createFastScriptEvaluator(Scanner, String[], String, Class, Class, String[],
|
||
|
* ClassLoader)} instead
|
||
|
*/
|
||
|
@Deprecated public static Object
|
||
|
createFastScriptEvaluator(String script, Class interfaceToImplement, String[] parameterNames)
|
||
|
throws CompileException {
|
||
|
ScriptEvaluator se = new ScriptEvaluator();
|
||
|
return se.createFastEvaluator(script, interfaceToImplement, parameterNames);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #createFastScriptEvaluator(Scanner, String[], String, Class, Class, String[],
|
||
|
* ClassLoader)} instead
|
||
|
*/
|
||
|
@Deprecated public static Object
|
||
|
createFastScriptEvaluator(
|
||
|
Scanner scanner,
|
||
|
Class interfaceToImplement,
|
||
|
String[] parameterNames,
|
||
|
ClassLoader optionalParentClassLoader
|
||
|
) throws CompileException, IOException {
|
||
|
ScriptEvaluator se = new ScriptEvaluator();
|
||
|
se.setParentClassLoader(optionalParentClassLoader);
|
||
|
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #createFastScriptEvaluator(Scanner, String[], String, Class, Class, String[],
|
||
|
* ClassLoader)} instead
|
||
|
*/
|
||
|
@Deprecated public static Object
|
||
|
createFastScriptEvaluator(
|
||
|
Scanner scanner,
|
||
|
String className,
|
||
|
Class optionalExtendedType,
|
||
|
Class interfaceToImplement,
|
||
|
String[] parameterNames,
|
||
|
ClassLoader optionalParentClassLoader
|
||
|
) throws CompileException, IOException {
|
||
|
ScriptEvaluator se = new ScriptEvaluator();
|
||
|
se.setClassName(className);
|
||
|
se.setExtendedClass(optionalExtendedType);
|
||
|
se.setParentClassLoader(optionalParentClassLoader);
|
||
|
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <pre>
|
||
|
* {@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);
|
||
|
* </pre>
|
||
|
*
|
||
|
* @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 <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[])}.
|
||
|
*
|
||
|
* @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.
|
||
|
* <ul>
|
||
|
* <li>If any component starts with an upper-case letter, then ambiguous name is assumed to
|
||
|
* be a type name.
|
||
|
* <li>Otherwise, if the first component of the ambiguous name matches the name of a
|
||
|
* previously defined local variable, then the first component of the ambiguous name is
|
||
|
* assumed to be a local variable name. (Notice that this strategy does not consider that
|
||
|
* the scope of a local variable declaration may end before the end of the script.)
|
||
|
* <li>Otherwise, the first component of the ambiguous name 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 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<String> localVariableNames = new HashSet();
|
||
|
final Set<String> parameterNames = new HashSet();
|
||
|
new Traverser() {
|
||
|
|
||
|
@Override public void
|
||
|
traverseLocalVariableDeclarationStatement(Java.LocalVariableDeclarationStatement lvds) {
|
||
|
for (VariableDeclarator vd : lvds.variableDeclarators) localVariableNames.add(vd.name);
|
||
|
super.traverseLocalVariableDeclarationStatement(lvds);
|
||
|
}
|
||
|
|
||
|
@Override public void
|
||
|
traverseAmbiguousName(Java.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 (int i = 0; i < an.identifiers.length; ++i) {
|
||
|
if (Character.isUpperCase(an.identifiers[i].charAt(0))) return;
|
||
|
}
|
||
|
|
||
|
// Is it a local variable's name?
|
||
|
if (localVariableNames.contains(an.identifiers[0])) return;
|
||
|
|
||
|
// It's most probably a parameter name (although it could be a field name as well).
|
||
|
parameterNames.add(an.identifiers[0]);
|
||
|
}
|
||
|
}.traverseBlock(block);
|
||
|
|
||
|
return (String[]) parameterNames.toArray(new String[parameterNames.size()]);
|
||
|
}
|
||
|
|
||
|
@Override public Object
|
||
|
evaluate(int idx, Object[] arguments) throws InvocationTargetException {
|
||
|
if (this.result == null) throw new IllegalStateException("Must only be called after \"cook()\"");
|
||
|
try {
|
||
|
return this.result[idx].invoke(null, arguments);
|
||
|
} catch (IllegalAccessException ex) {
|
||
|
throw new JaninoRuntimeException(ex.toString(), ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override public Method
|
||
|
getMethod(int idx) {
|
||
|
if (this.result == null) throw new IllegalStateException("Must only be called after \"cook()\"");
|
||
|
return this.result[idx];
|
||
|
}
|
||
|
}
|