All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.codehaus.janino.ScriptEvaluator Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version

/*
 * 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.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.org.codehaus.commons.compiler.CompileException;
import com.hazelcast.org.codehaus.commons.compiler.Cookable;
import com.hazelcast.org.codehaus.commons.compiler.ErrorHandler;
import com.hazelcast.org.codehaus.commons.compiler.IScriptEvaluator;
import com.hazelcast.org.codehaus.commons.compiler.InternalCompilerException;
import com.hazelcast.org.codehaus.commons.compiler.Location;
import com.hazelcast.org.codehaus.commons.compiler.MultiCookable;
import com.hazelcast.org.codehaus.commons.compiler.WarningHandler;
import com.hazelcast.org.codehaus.commons.nullanalysis.Nullable;
import com.hazelcast.org.codehaus.janino.Java.AbstractClassDeclaration;
import com.hazelcast.org.codehaus.janino.Java.AbstractCompilationUnit.ImportDeclaration;
import com.hazelcast.org.codehaus.janino.Java.Atom;
import com.hazelcast.org.codehaus.janino.Java.BlockStatement;
import com.hazelcast.org.codehaus.janino.Java.CompilationUnit;
import com.hazelcast.org.codehaus.janino.Java.ExpressionStatement;
import com.hazelcast.org.codehaus.janino.Java.LocalClassDeclaration;
import com.hazelcast.org.codehaus.janino.Java.LocalClassDeclarationStatement;
import com.hazelcast.org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import com.hazelcast.org.codehaus.janino.Java.MethodDeclarator;
import com.hazelcast.org.codehaus.janino.Java.Modifier;
import com.hazelcast.org.codehaus.janino.Java.Primitive;
import com.hazelcast.org.codehaus.janino.Java.PrimitiveType;
import com.hazelcast.org.codehaus.janino.Java.Type;
import com.hazelcast.org.codehaus.janino.Java.VariableDeclarator;
import com.hazelcast.org.codehaus.janino.Parser.ClassDeclarationContext;
import com.hazelcast.org.codehaus.janino.Parser.MethodDeclarationContext;
import com.hazelcast.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()]); // SUPPRESS CHECKSTYLE LineLength } 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|LineLength: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 com.hazelcast.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()]); } }