com.hazelcast.org.codehaus.commons.compiler.IScriptEvaluator 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.org.codehaus.commons.compiler;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.hazelcast.org.codehaus.commons.compiler.lang.ClassLoaders;
import com.hazelcast.org.codehaus.commons.nullanalysis.Nullable;
/**
* An engine that executes a script in JVM bytecode.
*
* The syntax of the script to compile is a sequence of import declarations (not allowed if you compile many scripts
* at a time, see below) followed by a sequence of statements, as defined in the Java Language Specification, Java SE 7 Edition, sections 7.5 and
* 14.
*
*
* An implementation may or may not implement the concept of "local methods", i.e. method declarations being freely
* intermixed with statements.
*
*
* Example:
*
*
* import java.text.*;
*
* System.out.println("HELLO");
* System.out.println(new DecimalFormat("####,###.##").format(a));
*
*
* (Notice that this expression refers to a parameter "a", as explained below.)
*
*
* The script may complete abnormally, e.g. through a RETURN statement:
*
*
* if (a == null) {
* System.out.println("Oops!");
* return;
* }
*
*
* Optionally, the script may be declared with a non-void return type. In this case, the last statement of the script
* must be a RETURN statement (or a THROW statement), and all RETURN statements in the script must return a value
* with the given type.
*
*
* The script evaluator is implemented by creating and compiling a temporary compilation unit defining one class with
* one method the body of which consists of the statements of the script.
*
*
* To set up a {@link IScriptEvaluator} object, proceed as follows:
*
*
* - Create an {@link IScriptEvaluator}-implementing class.
* -
* Configure the {@link IScriptEvaluator} by calling any of the following methods:
*
* - {@link #setReturnType(Class)}
*
- {@link #setParameters(String[], Class[])}
*
- {@link #setThrownExceptions(Class[])}
*
- {@link ISimpleCompiler#setParentClassLoader(ClassLoader)}
*
- {@link IClassBodyEvaluator#setDefaultImports(String[])}
*
*
* -
* Call any of the {@link com.hazelcast.org.codehaus.commons.compiler.Cookable#cook(Reader)} methods to scan, parse, compile and
* load the script into the JVM.
*
*
*
* After the {@link IScriptEvaluator} object is created, the script can be executed as often with different parameter
* values (see {@link #evaluate(Object[])}). This execution is very fast, compared to the compilation.
*
*
* Less common methods exist that allow for the specification of the name of the generated class, the class it
* extends, the interfaces it implements, the name of the method that executes the script, the exceptions that this
* method (i.e. the script) is allowed to throw, and the {@link ClassLoader} that is used to define the generated
* class and to load classes referenced by the script.
*
*
* If you want to compile many scripts at the same time, you have the option to cook an array of scripts in
* one {@link IScriptEvaluator} by using the following methods:
*
*
* - {@link #setMethodNames(String[])}
*
- {@link #setParameters(String[][], Class[][])}
*
- {@link #setReturnTypes(Class[])}
*
- {@link #setStaticMethod(boolean[])}
*
- {@link #setThrownExceptions(Class[][])}
*
- {@link #cook(Reader)}
*
- {@link #evaluate(int, Object[])}
*
*
* Notice that these methods have array parameters in contrast to their one-script brethren.
*
*/
public
interface IScriptEvaluator extends ICookable, IMultiCookable {
/**
* The name of the generated method(s), if no custom method name is configured with {@link
* #setMethodNames(String[])}.
*
* The {@code '*'} in this string is replaced with the method index, starting at 0.
*
*/
String DEFAULT_METHOD_NAME = "eval*";
/**
* The return type for any script for which no return type is explicitly configured.
*/
Class> DEFAULT_RETURN_TYPE = void.class;
/**
* The "parent class loader" is used to load referenced classes. Useful values are:
*
* {@code System.getSystemClassLoader()}
* The running JVM's class path
*
* {@code Thread.currentThread().getContextClassLoader()} or {@code null}
* The class loader effective for the invoking thread
*
* {@link ClassLoaders#BOOTCLASSPATH_CLASS_LOADER}
* The running JVM's boot class path
*
*
* The parent class loader defaults to the current thread's context class loader.
*
*/
void setParentClassLoader(@Nullable ClassLoader parentClassLoader);
/**
* Determines what kind of debugging information is included in the generates classes. The default is typically
* "{@code -g:none}".
*/
void setDebuggingInformation(boolean debugSource, boolean debugLines, boolean debugVars);
/**
* By default, {@link CompileException}s are thrown on compile errors, but an application my install its own
* {@link ErrorHandler}.
*
* Be aware that a single problem during compilation often causes a bunch of compile errors, so a good {@link
* ErrorHandler} counts errors and throws a {@link CompileException} when a limit is reached.
*
*
* If the given {@link ErrorHandler} throws {@link CompileException}s, then the compilation is terminated and
* the exception is propagated.
*
*
* If the given {@link ErrorHandler} does not throw {@link CompileException}s, then the compiler may or may not
* continue compilation, but must eventually throw a {@link CompileException}.
*
*
* In other words: The {@link ErrorHandler} may throw a {@link CompileException} or not, but the compiler must
* definitely throw a {@link CompileException} if one or more compile errors have occurred.
*
*
* @param compileErrorHandler {@code null} to restore the default behavior (throwing a {@link CompileException}
*/
void setCompileErrorHandler(@Nullable ErrorHandler compileErrorHandler);
/**
* By default, warnings are discarded, but an application my install a custom {@link WarningHandler}.
*
* @param warningHandler {@code null} to indicate that no warnings be issued
*/
void setWarningHandler(@Nullable WarningHandler warningHandler);
/** @see IClassBodyEvaluator#setClassName(String) */
void setClassName(String className);
/** @see IClassBodyEvaluator#setImplementedInterfaces(Class[]) */
void setImplementedInterfaces(Class>[] implementedInterfaces);
/** @see IClassBodyEvaluator#setExtendedClass(Class) */
void setExtendedClass(Class> extendedClass);
/**
* When this {@link IScriptEvaluator} is coooked, then the defaultReturnType applies to all scripts for
* which no explicit return type was configured.
*
* The initial default return type (if you want, the "default-default" return type) is {@code void.class}.
*
*
* @see #setReturnType(Class)
* @see #setReturnTypes(Class[])
*/
void setDefaultReturnType(Class> defaultReturnType);
/**
* @return The default return type that was previously configured with {@link #setDefaultReturnType(Class)}, or
* {@link #DEFAULT_RETURN_TYPE}
*/
Class> getDefaultReturnType();
/**
* Defines whether the generated method overrides a methods declared in a supertype.
*/
void setOverrideMethod(boolean overrideMethod);
/**
* Defines whether the generated method should be STATIC or not. Defaults to {@code true}.
*/
void setStaticMethod(boolean staticMethod);
/**
* Defines the return type of the generated method. Value {@code null} means "use the default return type".
*
* @see #setDefaultReturnType(Class)
*/
void setReturnType(Class> returnType);
/**
* Defines the name of the generated method. {@code null} means use a reasonable {@value #DEFAULT_METHOD_NAME}.
*/
void setMethodName(@Nullable String methodName);
/**
* Defines the names and types of the parameters of the generated method.
*
* names{@code .length} and types{@code .length} must be equal. This invariant may be
* checked immediately, or later when the script is cooked.
*
*
* The parameters can be of primitive type, e.g. {@code double.class}.
*
*
* The default is to have zero parameters.
*
*/
void setParameters(String[] names, Class>[] types);
/**
* Defines the exceptions that the generated method may throw.
*/
void setThrownExceptions(Class>[] thrownExceptions);
/**
* Calls the script with concrete parameter values.
*
* Each argument must have the same type as specified through the {@code parameterTypes} parameter of {@link
* #setParameters(String[], Class[])}.
*
*
* Arguments of primitive type must passed with their wrapper class objects.
*
*
* The object returned has the class as specified through {@link #setReturnType(Class)}.
*
*
* This method is thread-safe.
*
*
* @param arguments The actual parameter values
* @throws IllegalStateException This IScriptEvaluator is not yet cooked
*/
@Nullable Object evaluate(@Nullable Object[] arguments) throws InvocationTargetException;
/**
* @return The generated and loaded {@link java.lang.reflect.Method}
* @throws IllegalStateException This IScriptEvaluator is not yet cooked
*/
Method getMethod();
/**
* Same as {@link #setOverrideMethod(boolean)}, but for multiple scripts.
*/
void setOverrideMethod(boolean[] overrideMethod);
/**
* Same as {@link #setStaticMethod(boolean)}, but for multiple scripts.
*/
void setStaticMethod(boolean[] staticMethod);
/**
* Configures the return types of the generated methods. If an element of the array is {@code null}, then use
* the "default return type" for that script.
*
* @param returnTypes The methods' return types; {@code null} elements mean "use the default return
* type"
* @see #setDefaultReturnType(Class)
* @see #getDefaultReturnType()
*/
void setReturnTypes(Class>[] returnTypes);
/**
* Same as {@link #setMethodName(String)}, but for multiple scripts.
*
* Define the names of the generated methods. By default the methods have distinct and implementation-specific
* names.
*
*
* If two scripts have the same name, then they must have different parameter types (see {@link
* #setParameters(String[][], Class[][])}).
*
*/
void setMethodNames(String[] methodNames);
/**
* Same as {@link #setParameters(String[], Class[])}, but for multiple scripts.
*/
void setParameters(String[][] names, Class>[][] types);
/**
* Same as {@link #setThrownExceptions(Class[])}, but for multiple scripts.
*/
void setThrownExceptions(Class>[][] thrownExceptions);
/**
* Same as {@link #cook(Reader)}, but for multiple scripts.
*/
@Override void
cook(Reader... readers) throws CompileException, IOException;
/**
* Same as {@link #cook(String, Reader)}, but cooks a set of scripts into one class. Notice that
* if any of the scripts causes trouble, the entire compilation will fail. If you
* need to report which of the scripts causes the exception, you may want to use the
* {@code fileNames} parameter to distinguish between the individual token sources.
*
* Iff the number of scanners is one, then that single script may contain leading IMPORT directives.
*
s
*
* @throws IllegalStateException if any of the preceding {@code set...()} had an array
* size different from that of {@code scanners}
*/
@Override void
cook(String[] fileNames, Reader[] readers) throws CompileException, IOException;
/**
* Same as {@link #cook(String)}, but for multiple scripts.
*/
@Override void
cook(String[] strings) throws CompileException;
/**
* Same as {@link #cook(String, String)}, but for multiple scripts.
*/
@Override void
cook(String[] fileNames, String[] strings) throws CompileException;
/**
* Same as {@link #evaluate(Object[])}, but for multiple scripts.
*/
@Nullable Object evaluate(int idx, @Nullable Object[] arguments) throws InvocationTargetException;
/**
* Same as {@link #getMethod()}, but for multiple scripts.
*/
Method getMethod(int idx);
/**
* @param script Contains the sequence of script tokens
* @see #createFastEvaluator(Reader, Class, String[])
*/
T
createFastEvaluator(
String script,
Class interfaceToImplement,
String[] parameterNames
) throws CompileException;
/**
* If the parameter and return types of the expression are known at compile time, then a "fast" script evaluator
* can be instantiated through this method.
*
* Script evaluation is faster than through {@link #evaluate(Object[])}, because it is not done through
* reflection but through direct method invocation.
*
*
* Example:
*
*
* public interface Foo {
* int bar(int a, int b);
* }
* ...
* IScriptEvaluator se = {@link CompilerFactoryFactory}.{@link
* CompilerFactoryFactory#getDefaultCompilerFactory getDefaultCompilerFactory}().{@link
* ICompilerFactory#newScriptEvaluator newScriptEvaluator}();
*
* // Optionally configure the SE her:
* se.{@link #setClassName(String) setClassName}("Bar");
* se.{@link #setDefaultImports(String[]) setDefaultImports}(new String[] { "java.util.*" });
* se.{@link #setExtendedClass(Class) setExtendedClass}(SomeOtherClass.class);
* se.{@link #setParentClassLoader(ClassLoader) setParentClassLoader}(someClassLoader);
*
* Foo f = (Foo) se.{@link #createFastEvaluator(String, Class, String[]) createFastScriptEvaluator}(
* "return a - b;",
* Foo.class,
* new String[] { "a", "b" }
* );
* System.out.println("1 - 2 = " + f.bar(1, 2));
*
*
* All other configuration (implemented type, static method, return type, method name, parameter names and types,
* thrown exceptions) are predetermined by the {@code interfaceToImplement}.
*
*
* Notice: The {@code interfaceToImplement} must either be declared {@code public}, or with package scope in the
* same package as the generated class (see {@link #setClassName(String)}).
*
s
*
* @param reader Produces the stream of script tokens
* @param interfaceToImplement Must declare exactly one method
* @param parameterNames The names of the parameters of that method
* @return An object that implements the given interface
*/
T
createFastEvaluator(
Reader reader,
Class interfaceToImplement,
String[] parameterNames
) throws CompileException, IOException;
/** @see IClassBodyEvaluator#setDefaultImports(String...) */
void setDefaultImports(String... defaultImports);
/** @see IClassBodyEvaluator#getDefaultImports() */
String[] getDefaultImports();
/** @see IClassBodyEvaluator#getClazz() */
Class> getClazz();
/**
* @return The generated and loaded methods that implement the cooked scripts
* @throws IllegalStateException This {@link IScriptEvaluator} is not yet cooked
*/
Method[] getResult();
}