com.hazelcast.shaded.org.codehaus.janino.ScriptEvaluator Maven / Gradle / Ivy
/*
* 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 com.hazelcast.shaded.org.codehaus.janino;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.hazelcast.shaded.org.codehaus.commons.compiler.CompileException;
import com.hazelcast.shaded.org.codehaus.commons.compiler.Cookable;
import com.hazelcast.shaded.org.codehaus.commons.compiler.ErrorHandler;
import com.hazelcast.shaded.org.codehaus.commons.compiler.IScriptEvaluator;
import com.hazelcast.shaded.org.codehaus.commons.compiler.InternalCompilerException;
import com.hazelcast.shaded.org.codehaus.commons.compiler.Location;
import com.hazelcast.shaded.org.codehaus.commons.compiler.MultiCookable;
import com.hazelcast.shaded.org.codehaus.commons.compiler.WarningHandler;
import com.hazelcast.shaded.org.codehaus.commons.nullanalysis.Nullable;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.ImportDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.Atom;
import com.hazelcast.shaded.org.codehaus.janino.Java.BlockStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.CompilationUnit;
import com.hazelcast.shaded.org.codehaus.janino.Java.ExpressionStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalClassDeclarationStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.MethodDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Java.Modifier;
import com.hazelcast.shaded.org.codehaus.janino.Java.Primitive;
import com.hazelcast.shaded.org.codehaus.janino.Java.PrimitiveType;
import com.hazelcast.shaded.org.codehaus.janino.Java.Type;
import com.hazelcast.shaded.org.codehaus.janino.Java.VariableDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Parser.ClassDeclarationContext;
import com.hazelcast.shaded.org.codehaus.janino.Parser.MethodDeclarationContext;
import com.hazelcast.shaded.org.codehaus.janino.util.AbstractTraverser;
/**
* An implementation of {@link IScriptEvaluator} that utilizes the JANINO Java compiler.
*
* This implementation implements the concept of "Local methods", i.e. statements may be freely intermixed with
* method declarations. These methods are typically called by the "main code" of the script evaluator. One limitation
* exists: When cooking multiple scripts in one {@link ScriptEvaluator}, then local method signatures
* (names and parameter types) must not collide between scripts.
*
*
* A plethora of "convenience constructors" exist that execute the setup steps instantly. Their use is discouraged,
* in favor of using the default constructor, plus calling a number of setters, and then one of the {@code cook()}
* methods.
*
*/
public
class ScriptEvaluator extends MultiCookable implements IScriptEvaluator {
private int sourceVersion = -1;
@Nullable private WarningHandler warningHandler;
private final ClassBodyEvaluator cbe = new ClassBodyEvaluator();
/**
* Represents one script that this {@link ScriptEvaluator} declares. Typically there exactly one such
* script, but there can be two or more - see {@link ScriptEvaluator#ScriptEvaluator()}.
*/
class Script {
/**
* Whether the generated method overrides a method declared by a supertype; defaults to {@code false}.
*/
protected boolean overrideMethod;
/**
* Whether the method is generated {@code static}; defaults to {@code true}.
*/
protected boolean staticMethod = true;
/**
* The generated method's return type. {@code null} means "use the default return type".
*
* @see ScriptEvaluator#setDefaultReturnType(Class)
*/
@Nullable protected Class> returnType;
/**
* The name of the generated method.
*/
private String methodName;
private String[] parameterNames = new String[0];
private Class>[] parameterTypes = new Class>[0];
private Class>[] thrownExceptions = new Class>[0];
Script(String methodName) { this.methodName = methodName; }
}
/**
* The scripts to compile. Is initialized on the first call to {@link #setStaticMethod(boolean[])} or one of its
* friends.
*/
@Nullable private Script[] scripts;
private Class> defaultReturnType = IScriptEvaluator.DEFAULT_RETURN_TYPE;
@Override public void
setParentClassLoader(@Nullable ClassLoader parentClassLoader) { this.cbe.setParentClassLoader(parentClassLoader); }
@Override public void
setDebuggingInformation(boolean debugSource, boolean debugLines, boolean debugVars) {
this.cbe.setDebuggingInformation(debugSource, debugLines, debugVars);
}
@Override public void
setSourceVersion(int version) {
this.cbe.setSourceVersion(version);
this.sourceVersion = version;
}
@Override public void
setTargetVersion(int version) { this.cbe.setTargetVersion(version); }
@Override public void
setCompileErrorHandler(@Nullable ErrorHandler compileErrorHandler) {
this.cbe.setCompileErrorHandler(compileErrorHandler);
}
@Override public void
setWarningHandler(@Nullable WarningHandler warningHandler) {
this.cbe.setWarningHandler(warningHandler);
this.warningHandler = warningHandler;
}
/**
* @return A reference to the currently effective compilation options; changes to it take
* effect immediately
*/
public EnumSet
options() { return this.cbe.options(); }
/**
* Sets the options for all future compilations.
*/
public ScriptEvaluator
options(EnumSet options) {
this.cbe.options(options);
return this;
}
/**
* @throws IllegalArgumentException count is different from previous invocations of
* this method
*/
public void
setScriptCount(int count) {
Script[] ss = this.scripts;
if (ss == null) {
this.scripts = (ss = new Script[count]);
for (int i = 0; i < count; i++) {
ss[i] = new Script(IScriptEvaluator.DEFAULT_METHOD_NAME.replace("*", Integer.toString(i)));
}
} else {
if (count != ss.length) {
throw new IllegalArgumentException(
"Inconsistent script count; previously " + ss.length + ", now " + count
);
}
}
}
private Script
getScript(int index) {
if (this.scripts != null) return this.scripts[index];
throw new IllegalStateException("\"getScript()\" invoked before \"setScriptCount()\"");
}
/**
* 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(parentClassLoader);
* se.cook(fileName, is);
*
*
* @see #ScriptEvaluator()
* @see #setReturnType(Class)
* @see #setParameters(String[], Class[])
* @see #setThrownExceptions(Class[])
* @see SimpleCompiler#setParentClassLoader(ClassLoader)
* @see Cookable#cook(String, InputStream)
*/
public
ScriptEvaluator(
@Nullable String fileName,
InputStream is,
Class> returnType,
String[] parameterNames,
Class>[] parameterTypes,
Class>[] thrownExceptions,
@Nullable ClassLoader parentClassLoader // null = use current thread's context class loader
) throws CompileException, IOException {
this.setReturnType(returnType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(fileName, is);
}
/**
* Equivalent to
*
* ScriptEvaluator se = new ScriptEvaluator();
* se.setReturnType(returnType);
* se.setParameters(parameterNames, parameterTypes);
* se.setThrownExceptions(thrownExceptions);
* se.setParentClassLoader(parentClassLoader);
* 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(
@Nullable String fileName,
Reader reader,
Class> returnType,
String[] parameterNames,
Class>[] parameterTypes,
Class>[] thrownExceptions,
@Nullable ClassLoader parentClassLoader // null = use current thread's context class loader
) throws CompileException, IOException {
this.setReturnType(returnType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(fileName, reader);
}
/**
* Equivalent to
*
* ScriptEvaluator se = new ScriptEvaluator();
* se.setReturnType(returnType);
* se.setParameters(parameterNames, parameterTypes);
* se.setThrownExceptions(thrownExceptions);
* se.setParentClassLoader(parentClassLoader);
* 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,
@Nullable ClassLoader parentClassLoader // null = use current thread's context class loader
) throws CompileException, IOException {
this.setReturnType(returnType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(scanner);
}
/**
* Equivalent to
*
* ScriptEvaluator se = new ScriptEvaluator();
* se.setExtendedType(extendedType);
* se.setImplementedTypes(implementedTypes);
* se.setReturnType(returnType);
* se.setParameters(parameterNames, parameterTypes);
* se.setThrownExceptions(thrownExceptions);
* se.setParentClassLoader(parentClassLoader);
* 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,
@Nullable Class> extendedType,
Class>[] implementedTypes,
Class> returnType,
String[] parameterNames,
Class>[] parameterTypes,
Class>[] thrownExceptions,
@Nullable ClassLoader parentClassLoader // null = use current thread's context class loader
) throws CompileException, IOException {
this.setExtendedClass(extendedType);
this.setImplementedInterfaces(implementedTypes);
this.setReturnType(returnType);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(scanner);
}
/**
* Equivalent to
*
* ScriptEvaluator se = new ScriptEvaluator();
* se.setClassName(className);
* se.setExtendedType(extendedType);
* se.setImplementedTypes(implementedTypes);
* se.setStaticMethod(staticMethod);
* se.setReturnType(returnType);
* se.setMethodName(methodName);
* se.setParameters(parameterNames, parameterTypes);
* se.setThrownExceptions(thrownExceptions);
* se.setParentClassLoader(parentClassLoader);
* 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,
@Nullable Class> extendedType,
Class>[] implementedTypes,
boolean staticMethod,
Class> returnType,
String methodName,
String[] parameterNames,
Class>[] parameterTypes,
Class>[] thrownExceptions,
@Nullable ClassLoader parentClassLoader // null = use current thread's context class loader
) throws CompileException, IOException {
this.setClassName(className);
this.setExtendedClass(extendedType);
this.setImplementedInterfaces(implementedTypes);
this.setStaticMethod(staticMethod);
this.setReturnType(returnType);
this.setMethodName(methodName);
this.setParameters(parameterNames, parameterTypes);
this.setThrownExceptions(thrownExceptions);
this.setParentClassLoader(parentClassLoader);
this.cook(scanner);
}
/**
* Constructs a script evaluator with all the default settings.
*/
public
ScriptEvaluator() {}
/**
* Constructs a script evaluator with the given number of scripts.
*
* The argument of all following invocations of
* {@link #setMethodNames(String[])},
* {@link #setOverrideMethod(boolean[])},
* {@link #setParameters(String[][], Class[][])},
* {@link #setReturnTypes(Class[])},
* {@link #setStaticMethod(boolean[])},
* {@link #setThrownExceptions(Class[][])},
* {@link #cook(Parser[])},
* {@link #cook(Reader[])},
* {@link #cook(Scanner[])},
* {@link #cook(String[])},
* {@link #cook(String[], Reader[])} and
* {@link #cook(String[], String[])}
* must be arrays with exactly that length.
*
*
* If a different constructor is used, then the first invocation of one of the above method implicitly sets the
* script count.
*
*/
public ScriptEvaluator(int count) { this.setScriptCount(count); }
@Override public void
setClassName(String className) { this.cbe.setClassName(className); }
@Override public void
setImplementedInterfaces(Class>[] implementedTypes) { this.cbe.setImplementedInterfaces(implementedTypes); }
@Override public void
setExtendedClass(@Nullable Class> extendedType) { this.cbe.setExtendedClass(extendedType); }
@Override public void
setDefaultReturnType(Class> defaultReturnType) { this.defaultReturnType = defaultReturnType; }
@Override public Class>
getDefaultReturnType() { return this.defaultReturnType; }
// ================= SINGLE SCRIPT CONFIGURATION SETTERS =================
@Override public void
setOverrideMethod(boolean overrideMethod) { this.setOverrideMethod(new boolean[] { overrideMethod }); }
@Override public void
setStaticMethod(boolean staticMethod) { this.setStaticMethod(new boolean[] { staticMethod }); }
@Override public void
setReturnType(@Nullable Class> returnType) { this.setReturnTypes(new Class[] { returnType }); }
@Override public void
setMethodName(@Nullable 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 });
}
// ================= MULTIPLE SCRIPT CONFIGURATION SETTERS =================
@Override public void
setOverrideMethod(boolean[] overrideMethod) {
this.setScriptCount(overrideMethod.length);
for (int i = 0; i < overrideMethod.length; i++) this.getScript(i).overrideMethod = overrideMethod[i];
}
@Override public void
setStaticMethod(boolean[] staticMethod) {
this.setScriptCount(staticMethod.length);
for (int i = 0; i < staticMethod.length; i++) this.getScript(i).staticMethod = staticMethod[i];
}
@Override public void
setReturnTypes(Class>[] returnTypes) {
this.setScriptCount(returnTypes.length);
for (int i = 0; i < returnTypes.length; i++) {
this.getScript(i).returnType = returnTypes[i];
}
}
@Override public void
setMethodNames(String[] methodNames) {
this.setScriptCount(methodNames.length);
for (int i = 0; i < methodNames.length; i++) this.getScript(i).methodName = methodNames[i];
}
@Override public void
setParameters(String[][] parameterNames, Class>[][] parameterTypes) {
this.setScriptCount(parameterNames.length);
this.setScriptCount(parameterTypes.length);
for (int i = 0; i < parameterNames.length; i++) {
final Script script = this.getScript(i);
script.parameterNames = (String[]) parameterNames[i].clone();
script.parameterTypes = (Class[]) parameterTypes[i].clone();
}
}
@Override public void
setThrownExceptions(Class>[][] thrownExceptions) {
this.setScriptCount(thrownExceptions.length);
for (int i = 0; i < thrownExceptions.length; i++) this.getScript(i).thrownExceptions = thrownExceptions[i];
}
// ---------------------------------------------------------------
@Override public void
cook(@Nullable String fileName, Reader reader) throws CompileException, IOException {
this.cook(new Scanner(fileName, reader));
}
/**
* 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.
*
*/
@Override public final void
cook(String[] fileNames, Reader[] readers) throws CompileException, IOException {
this.setScriptCount(fileNames.length);
this.setScriptCount(readers.length);
Scanner[] scanners = new Scanner[readers.length];
for (int i = 0; i < readers.length; ++i) scanners[i] = new Scanner(fileNames[i], readers[i]);
this.cook(scanners);
}
/**
* 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
* fileName 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 preceding {@code set...()} had an array size different from that of
* {@code scanners}
*/
public final void
cook(Scanner... scanners) throws CompileException, IOException {
this.setScriptCount(scanners.length);
Parser[] parsers = new Parser[scanners.length];
for (int i = 0; i < scanners.length; ++i) {
parsers[i] = new Parser(scanners[i]);
parsers[i].setSourceVersion(this.sourceVersion);
parsers[i].setWarningHandler(this.warningHandler);
}
this.cook(parsers);
}
/**
* @see #cook(Scanner[])
*/
public final void
cook(Parser[] parsers) throws CompileException, IOException {
int count = parsers.length;
this.setScriptCount(count);
final Parser parser = count == 1 ? parsers[0] : null;
// Create compilation unit.
Java.AbstractCompilationUnit.ImportDeclaration[]
importDeclarations = this.parseImports(parser);
Java.BlockStatement[][] statementss = new Java.BlockStatement[count][];
Java.MethodDeclarator[][] localMethodss = new Java.MethodDeclarator[count][];
// Create methods with one block each.
for (int i = 0; i < count; ++i) {
// Create the statements of the method.
List statements = new ArrayList<>();
List localMethods = new ArrayList<>();
this.makeStatements(i, parsers[i], statements, localMethods);
statementss[i] = (BlockStatement[]) statements.toArray(new Java.BlockStatement[statements.size()]);
localMethodss[i] = (MethodDeclarator[]) localMethods.toArray(new Java.MethodDeclarator[localMethods.size()]);
}
this.cook(
parsers.length >= 1 ? parsers[0].getScanner().getFileName() : null, // fileName
importDeclarations,
statementss,
localMethodss
);
}
void
cook(
@Nullable String fileName,
ImportDeclaration[] importDeclarations,
Java.BlockStatement[][] statementss,
Java.MethodDeclarator[][] localMethodss
) throws CompileException {
int count = statementss.length;
Collection methodDeclarators = new ArrayList<>();
for (int i = 0; i < count; i++) {
Script es = this.getScript(i);
Java.BlockStatement[] statements = statementss[i];
Java.MethodDeclarator[] localMethods = localMethodss[i];
final Location loc = statements.length == 0 ? Location.NOWHERE : statements[0].getLocation();
Class> rt = es.returnType;
if (rt == null) rt = this.getDefaultReturnType();
methodDeclarators.add(this.makeMethodDeclaration(
loc, // location
( // annotations
es.overrideMethod
? new Java.Annotation[] { new Java.MarkerAnnotation(this.classToType(loc, Override.class)) }
: new Java.Annotation[0]
),
es.staticMethod, // staticMethod
rt, // returnType
es.methodName, // methodName
es.parameterTypes, // parameterTypes
es.parameterNames, // parameterNames
es.thrownExceptions, // thrownExceptions
statements // statements
));
// Also add the "local methods" that a script my declare.
for (MethodDeclarator lm : localMethods) methodDeclarators.add(lm);
}
this.cook(new Java.CompilationUnit(fileName, importDeclarations), methodDeclarators);
}
public final void
cook(CompilationUnit compilationUnit, Collection methodDeclarators)
throws CompileException {
// Create class declaration.
final AbstractClassDeclaration
cd = this.cbe.addPackageMemberClassDeclaration(
((MethodDeclarator) methodDeclarators.iterator().next()).getLocation(),
compilationUnit
);
for (MethodDeclarator md : methodDeclarators) cd.addDeclaredMethod(md);
this.cook(compilationUnit);
}
Java.AbstractCompilationUnit.ImportDeclaration[]
parseImports(@Nullable Parser parser) throws CompileException, IOException {
return this.cbe.makeImportDeclarations(parser);
}
@Override public Method[]
getResult() { return this.getMethods(); }
/**
* @return The generated methods
* @throws IllegalStateException The {@link ScriptEvaluator} has not yet be cooked
*/
Method[]
getMethods() {
Method[] result = this.getMethodsCache;
if (result != null) return result;
final Class> c = this.getClazz();
// Find the script methods by name and parameter types.
assert this.scripts != null;
int count = this.scripts.length;
// Clear the generated methods.
result = new Method[count];
// "Class.getDeclaredMethod(name, parameterTypes)" is slow when the class declares MANY methods (say, in
// the thousands). So let's use "Class.getDeclaredMethods()" instead.
// Create a (temporary) mapping of method key to method index.
Map dms = new HashMap<>(2 * count);
for (int i = 0; i < count; ++i) {
Script es = this.getScript(i);
Integer prev = (Integer) dms.put(ScriptEvaluator.methodKey(es.methodName, es.parameterTypes), i);
assert prev == null;
}
// Now invoke "Class.getDeclaredMethods()" and filter "our" methods from the result.
for (Method m : c.getDeclaredMethods()) {
Integer idx = (Integer) dms.get(ScriptEvaluator.methodKey(m.getName(), m.getParameterTypes()));
if (idx == null) continue;
assert result[idx] == null;
result[idx] = m;
}
// Verify that the class declared "all our" methods.
for (int i = 0; i < count; ++i) {
if (result[i] == null) {
throw new InternalCompilerException(
"SNO: Generated class does not declare method \""
+ this.getScript(i).methodName
+ "\" (index "
+ i
+ ")"
);
}
}
return (this.getMethodsCache = result);
}
@Nullable private Method[] getMethodsCache;
@Nullable protected Type
optionalClassToType(Location loc, @Nullable Class> clazz) { return this.cbe.optionalClassToType(loc, clazz); }
protected Type
classToType(Location loc, Class> clazz) { return this.cbe.classToType(loc, clazz); }
protected Type[]
classesToTypes(Location location, Class>[] classes) { return this.cbe.classesToTypes(location, classes); }
/**
* Compiles the given compilationUnit, defines it into a {@link ClassLoader}, loads the generated class,
* gets the script methods from that class, and makes them available through {@link #getMethod(int)}.
*/
protected void
cook(CompilationUnit compilationUnit) throws CompileException {
this.cbe.cook(compilationUnit);
}
private static Object
methodKey(String methodName, Class>[] parameterTypes) {
return Arrays.asList(ScriptEvaluator.cat(methodName, parameterTypes, Object.class));
}
/**
* @return A copy of the followingElements, prepended with the firstElement
*/
private static T[]
cat(T firstElement, T[] followingElements, Class componentType) {
@SuppressWarnings("unchecked") T[]
result = (T[]) Array.newInstance(componentType, 1 + followingElements.length);
result[0] = firstElement;
System.arraycopy(followingElements, 0, result, 1, followingElements.length);
return result;
}
@Override @Nullable public Object
evaluate(@Nullable Object... arguments) throws InvocationTargetException { return this.evaluate(0, arguments); }
@Override @Nullable public Object
evaluate(int idx, @Nullable Object[] arguments) throws InvocationTargetException {
Method method = this.getMethod(idx);
try {
return method.invoke(null, arguments);
} catch (IllegalAccessException ex) {
throw new InternalCompilerException(ex.toString(), ex);
}
}
@Override public Method
getMethod() { return this.getMethod(0); }
@Override public Method
getMethod(int idx) { return this.getMethods()[idx]; }
@Override public Class>
getClazz() { return this.cbe.getClazz(); }
@Override public Map
getBytecodes() { return this.cbe.getBytecodes(); }
/**
* @return The return type of the indexed script; {@code null} means "use the {@link #setDefaultReturnType(Class)
* default return type}"
*/
@Nullable protected final Class>
getReturnType(int index) { return this.getScript(index).returnType; }
/**
* Parses statements from the parser until end-of-input.
*
* @param resultStatements Is filled with the generated statements
* @param resultMethods Is filled with any local methods that the script declares
*/
protected void
makeStatements(
int idx,
Parser parser,
List resultStatements,
List resultMethods
) throws CompileException, IOException {
while (!parser.peek(TokenType.END_OF_INPUT)) {
ScriptEvaluator.parseScriptStatement(parser, resultStatements, resultMethods);
}
}
/**
*
* ScriptStatement :=
* Statement (1)
* | 'class' ... (2)
* | [ Modifiers ] 'void' Identifier MethodDeclarationRest (3a)
* | Modifiers Type Identifier MethodDeclarationRest ';' (3b)
* | Expression Identifier MethodDeclarationRest (3c) (5)
* | Modifiers Type VariableDeclarators ';' (4a)
* | Expression VariableDeclarators ';' (4b) (5)
* | Expression ';'
*
* (1): Includes the "labeled statement".
* (2): Local class declaration.
* (3a), (3b), (3c): Local method declaration statement.
* (4) Local variable declaration statement.
* (5) "Expression" must pose a type.
*
* @param localMethods Filled with the methods that the script declares
*/
private static void
parseScriptStatement(
Parser parser,
List mainStatements,
List localMethods
) throws CompileException, IOException {
// Statement?
if (
(parser.peek(TokenType.IDENTIFIER) && parser.peekNextButOne(":"))
|| parser.peek(
"if", "for", "while", "do", "try", "switch", "synchronized", // SUPPRESS CHECKSTYLE Wrap:2
"return", "throw", "break", "continue", "assert",
"{", ";"
) != -1
) {
mainStatements.add(parser.parseStatement());
return;
}
// Local class declaration?
if (parser.peekRead("class")) {
final LocalClassDeclaration lcd = (LocalClassDeclaration) parser.parseClassDeclarationRest(
null, // docComment
new Modifier[0], // modifiers
ClassDeclarationContext.BLOCK // context
);
mainStatements.add(new LocalClassDeclarationStatement(lcd));
return;
}
Modifier[] modifiers = parser.parseModifiers();
// "void" method declaration (without type parameters).
if (parser.peekRead("void")) {
String name = parser.read(TokenType.IDENTIFIER);
localMethods.add(parser.parseMethodDeclarationRest(
null, // docComment
modifiers, // modifiers
null, // typeParameters
new PrimitiveType(parser.location(), Primitive.VOID), // type
name, // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
if (modifiers.length > 0) {
Type methodOrVariableType = parser.parseType();
// Modifiers Type Identifier MethodDeclarationRest ';'
if (parser.peek(TokenType.IDENTIFIER) && parser.peekNextButOne("(")) {
localMethods.add(parser.parseMethodDeclarationRest(
null, // docComment
modifiers, // modifiers
null, // typeParameters
methodOrVariableType, // type
parser.read(TokenType.IDENTIFIER), // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
// Modifiers Type VariableDeclarators ';'
mainStatements.add(new LocalVariableDeclarationStatement(
parser.location(), // location
modifiers, // modifiers
methodOrVariableType, // type
parser.parseVariableDeclarators() // variableDeclarators
));
parser.read(";");
return;
}
// It's either a non-final local variable declaration or an expression statement, or a non-void method
// declaration. We can only tell after parsing an expression.
Atom a = parser.parseExpressionOrType();
// Expression ';'
if (parser.peekRead(";")) {
mainStatements.add(new ExpressionStatement(a.toRvalueOrCompileException()));
return;
}
Type methodOrVariableType = a.toTypeOrCompileException();
// [ Modifiers ] Expression identifier MethodDeclarationRest
if (parser.peek(TokenType.IDENTIFIER) && parser.peekNextButOne("(")) {
localMethods.add(parser.parseMethodDeclarationRest(
null, // docComment
modifiers, // modifiers
null, // typeParameters
methodOrVariableType, // type
parser.read(TokenType.IDENTIFIER), // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
// [ Modifiers ] Expression VariableDeclarators ';'
mainStatements.add(new LocalVariableDeclarationStatement(
a.getLocation(), // location
modifiers, // modifiers
methodOrVariableType, // type
parser.parseVariableDeclarators() // variableDeclarators
));
parser.read(";");
}
private Java.MethodDeclarator
makeMethodDeclaration(
Location location,
Java.Annotation[] annotations,
boolean staticMethod,
Class> returnType,
String methodName,
Class>[] parameterTypes,
String[] parameterNames,
Class>[] thrownExceptions,
Java.BlockStatement[] statements
) {
if (parameterNames.length != parameterTypes.length) {
throw new InternalCompilerException(
"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
Java.accessModifiers(location, "final"), // finaL
this.classToType(location, parameterTypes[i]), // type
parameterNames[i] // name
);
}
Modifier[] modifiers;
{
List l = new ArrayList<>();
l.addAll(Arrays.asList(annotations));
l.add(new Java.AccessModifier("public", location));
if (staticMethod) l.add(new Java.AccessModifier("static", location));
modifiers = (Java.Modifier[]) l.toArray(new Java.Modifier[l.size()]);
}
return new Java.MethodDeclarator(
location, // location
null, // docComment
modifiers, // modifiers
null, // typeParameters
this.classToType(location, returnType), // type
methodName, // name
fps, // formalParameters
this.classesToTypes(location, thrownExceptions), // thrownExceptions
null, // defaultValue
Arrays.asList(statements) // statements
);
}
/**
* @deprecated Use {@link #createFastEvaluator(Scanner, Class, String[])} 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 #createFastEvaluator(Scanner, Class, String[])} instead
*/
@Deprecated public static Object
createFastScriptEvaluator(
Scanner scanner,
Class> interfaceToImplement,
String[] parameterNames,
@Nullable ClassLoader parentClassLoader
) throws CompileException, IOException {
ScriptEvaluator se = new ScriptEvaluator();
se.setParentClassLoader(parentClassLoader);
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
}
/**
* @deprecated Use {@link #createFastEvaluator(Scanner, Class, String[])} instead
*/
@Deprecated public static Object
createFastScriptEvaluator(
Scanner scanner,
String className,
@Nullable Class> extendedType,
Class> interfaceToImplement,
String[] parameterNames,
@Nullable ClassLoader parentClassLoader
) throws CompileException, IOException {
ScriptEvaluator se = new ScriptEvaluator();
se.setClassName(className);
se.setExtendedClass(extendedType);
se.setParentClassLoader(parentClassLoader);
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
}
/**
*
* {@link ScriptEvaluator} se = new {@link ScriptEvaluator#ScriptEvaluator() ScriptEvaluator}();
* se.{@link #setDefaultImports(String[]) setDefaultImports}.(defaultImports);
* se.{@link #setClassName(String) setClassName}.(className);
* se.{@link #setExtendedClass(Class) setExtendedClass}.(extendedClass);
* se.{@link #setParentClassLoader(ClassLoader) setParentClassLoader}(parentClassLoader);
* 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[] defaultImports,
String className,
@Nullable Class> extendedClass,
Class> interfaceToImplement,
String[] parameterNames,
@Nullable ClassLoader parentClassLoader
) throws CompileException, IOException {
ScriptEvaluator se = new ScriptEvaluator();
se.setDefaultImports(defaultImports);
se.setClassName(className);
se.setExtendedClass(extendedClass);
se.setParentClassLoader(parentClassLoader);
return se.createFastEvaluator(scanner, interfaceToImplement, parameterNames);
}
@Override public void
setDefaultImports(String... defaultImports) { this.cbe.setDefaultImports(defaultImports); }
@Override public String[]
getDefaultImports() { return this.cbe.getDefaultImports(); }
@Override public T
createFastEvaluator(Reader reader, Class interfaceToImplement, String[] parameterNames)
throws CompileException, IOException {
return this.createFastEvaluator(new Scanner(null, reader), interfaceToImplement, parameterNames);
}
@Override public T
createFastEvaluator(String script, Class interfaceToImplement, String[] parameterNames) throws CompileException {
try {
return this.createFastEvaluator(
new StringReader(script),
interfaceToImplement,
parameterNames
);
} catch (IOException ex) {
throw new InternalCompilerException("IOException despite StringReader", ex);
}
}
/**
* Notice: This method is not declared in {@link IScriptEvaluator}, and is hence only available in this
* implementation of {@code 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 T
createFastEvaluator(Scanner scanner, Class> 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.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 {
@SuppressWarnings("unchecked") T result = (T) c.newInstance();
return result;
} 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);
}
}
/**
* Guesses 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.
*
* -
* If any component starts with an upper-case letter, then ambiguous name is assumed to be a type name.
*
* -
* 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.)
*
* -
* Otherwise, the first component of the ambiguous name is assumed to be a parameter name.
*
*
*
* @see Scanner#Scanner(String, Reader)
*/
public static String[]
guessParameterNames(Scanner scanner) throws CompileException, IOException {
Parser parser = new Parser(scanner);
// Impossible, because this method is STATIC:
//parser.setSourceVersion(this.sourceVersion);
//parser.setWarningHandler(this.warningHandler);
// 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.peek(TokenType.END_OF_INPUT)) block.addStatement(parser.parseBlockStatement());
// Traverse the block for ambiguous names and guess which of them are parameter names.
final Set localVariableNames = new HashSet<>();
final Set parameterNames = new HashSet<>();
new AbstractTraverser() {
@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]);
}
}.visitBlockStatement(block);
return (String[]) parameterNames.toArray(new String[parameterNames.size()]);
}
}