/**
* Copyright (C) 2010 dennis zhuang (killme2008@gmail.com)
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
**/
package com.googlecode.aviator;
import java.io.OutputStream;
import java.math.MathContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
import com.googlecode.aviator.asm.Opcodes;
import com.googlecode.aviator.code.CodeGenerator;
import com.googlecode.aviator.code.OptimizeCodeGenerator;
import com.googlecode.aviator.code.asm.ASMCodeGenerator;
import com.googlecode.aviator.exception.CompileExpressionErrorException;
import com.googlecode.aviator.exception.ExpressionRuntimeException;
import com.googlecode.aviator.lexer.ExpressionLexer;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.parser.AviatorClassLoader;
import com.googlecode.aviator.parser.ExpressionParser;
import com.googlecode.aviator.runtime.function.math.MathAbsFunction;
import com.googlecode.aviator.runtime.function.math.MathCosFunction;
import com.googlecode.aviator.runtime.function.math.MathLog10Function;
import com.googlecode.aviator.runtime.function.math.MathLogFunction;
import com.googlecode.aviator.runtime.function.math.MathPowFunction;
import com.googlecode.aviator.runtime.function.math.MathRoundFunction;
import com.googlecode.aviator.runtime.function.math.MathSinFunction;
import com.googlecode.aviator.runtime.function.math.MathSqrtFunction;
import com.googlecode.aviator.runtime.function.math.MathTanFunction;
import com.googlecode.aviator.runtime.function.seq.SeqCompsitePredFunFunction;
import com.googlecode.aviator.runtime.function.seq.SeqCompsitePredFunFunction.LogicOp;
import com.googlecode.aviator.runtime.function.seq.SeqCountFunction;
import com.googlecode.aviator.runtime.function.seq.SeqEveryFunction;
import com.googlecode.aviator.runtime.function.seq.SeqFilterFunction;
import com.googlecode.aviator.runtime.function.seq.SeqIncludeFunction;
import com.googlecode.aviator.runtime.function.seq.SeqMakePredicateFunFunction;
import com.googlecode.aviator.runtime.function.seq.SeqMapFunction;
import com.googlecode.aviator.runtime.function.seq.SeqNotAnyFunction;
import com.googlecode.aviator.runtime.function.seq.SeqReduceFunction;
import com.googlecode.aviator.runtime.function.seq.SeqSomeFunction;
import com.googlecode.aviator.runtime.function.seq.SeqSortFunction;
import com.googlecode.aviator.runtime.function.string.StringContainsFunction;
import com.googlecode.aviator.runtime.function.string.StringEndsWithFunction;
import com.googlecode.aviator.runtime.function.string.StringIndexOfFunction;
import com.googlecode.aviator.runtime.function.string.StringJoinFunction;
import com.googlecode.aviator.runtime.function.string.StringLengthFunction;
import com.googlecode.aviator.runtime.function.string.StringReplaceAllFunction;
import com.googlecode.aviator.runtime.function.string.StringReplaceFirstFunction;
import com.googlecode.aviator.runtime.function.string.StringSplitFunction;
import com.googlecode.aviator.runtime.function.string.StringStartsWithFunction;
import com.googlecode.aviator.runtime.function.string.StringSubStringFunction;
import com.googlecode.aviator.runtime.function.system.BinaryFunction;
import com.googlecode.aviator.runtime.function.system.Date2StringFunction;
import com.googlecode.aviator.runtime.function.system.DoubleFunction;
import com.googlecode.aviator.runtime.function.system.LongFunction;
import com.googlecode.aviator.runtime.function.system.NowFunction;
import com.googlecode.aviator.runtime.function.system.PrintFunction;
import com.googlecode.aviator.runtime.function.system.PrintlnFunction;
import com.googlecode.aviator.runtime.function.system.RandomFunction;
import com.googlecode.aviator.runtime.function.system.StrFunction;
import com.googlecode.aviator.runtime.function.system.String2DateFunction;
import com.googlecode.aviator.runtime.function.system.SysDateFunction;
import com.googlecode.aviator.runtime.type.AviatorBoolean;
import com.googlecode.aviator.runtime.type.AviatorFunction;
import com.googlecode.aviator.runtime.type.AviatorNil;
/**
* Avaitor Expression evaluator
*
* @author dennis
*
*/
public final class AviatorEvaluator {
// The classloader to define generated class
@Deprecated
private static AviatorClassLoader aviatorClassLoader;
/**
* Optimized for compile speed
*/
public static final int COMPILE = 0;
/**
* Optimized for execute speed,this is the default option
*/
public static final int EVAL = 1;
/**
* Aviator version
*/
public static final String VERSION = "2.1.1";
/**
* Generated java class version,default 1.6
*/
public static int BYTECODE_VER = Opcodes.V1_6;
private static OutputStream traceOutputStream = System.out;
private static final ConcurrentHashMap<Options, Object> options = new ConcurrentHashMap<Options, Object>();
/**
* Configure whether to trace code generation
*
* @deprecated please use {@link #setOption(Options, Object)}
* @param t
* true is to trace,default is false.
*/
public static void setTrace(boolean t) {
setOption(Options.TRACE, t);
}
/**
* Adds a evaluator option
*
* @since 2.3.4
* @see Options
* @param opt
* @param val
*/
public static void setOption(Options opt, Object val) {
if (opt == null || val == null) {
throw new IllegalArgumentException("Option and value should not be null.");
}
if (!opt.isValidValue(val)) {
throw new IllegalArgumentException("Invalid value for option:" + opt.name());
}
options.put(opt, val);
}
/**
* Returns the current evaluator option value, returns null if missing.
*
* @param opt
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getOption(Options opt) {
Object val = options.get(opt);
if (val == null) {
val = opt.getDefaultValue();
}
return (T) val;
}
/**
* Get current trace output stream,default is System.out
*
* @return
*/
public static OutputStream getTraceOutputStream() {
return traceOutputStream;
}
/**
* Returns current math context for decimal.
*
* @since 2.3.0
* @deprecated Please use {@link #getOption(Options)}
* @return
*/
public static MathContext getMathContext() {
return getOption(Options.MATH_CONTEXT);
}
/**
* Set math context for decimal.
*
* @param mathContext
* @deprecated please use {@link #setOption(Options, Object)}
* @since 2.3.0
*/
public static void setMathContext(MathContext mathContext) {
if (mathContext == null) {
throw new IllegalArgumentException("null mathContext");
}
setOption(Options.MATH_CONTEXT, mathContext);
}
/**
* Set trace output stream
*
* @param traceOutputStream
*/
public static void setTraceOutputStream(OutputStream traceOutputStream) {
AviatorEvaluator.traceOutputStream = traceOutputStream;
}
static {
aviatorClassLoader = AccessController.doPrivileged(new PrivilegedAction<AviatorClassLoader>() {
@Override
public AviatorClassLoader run() {
return new AviatorClassLoader(AviatorEvaluator.class.getClassLoader());
}
});
}
public final static Map<String, Object> FUNC_MAP = new HashMap<String, Object>();
static {
// Load internal functions
// load sys lib
addFunction(new SysDateFunction());
addFunction(new PrintlnFunction());
addFunction(new PrintFunction());
addFunction(new RandomFunction());
addFunction(new NowFunction());
addFunction(new LongFunction());
addFunction(new DoubleFunction());
addFunction(new StrFunction());
addFunction(new Date2StringFunction());
addFunction(new String2DateFunction());
addFunction(new BinaryFunction(OperatorType.ADD));
addFunction(new BinaryFunction(OperatorType.SUB));
addFunction(new BinaryFunction(OperatorType.MULT));
addFunction(new BinaryFunction(OperatorType.DIV));
addFunction(new BinaryFunction(OperatorType.MOD));
addFunction(new BinaryFunction(OperatorType.NEG));
addFunction(new BinaryFunction(OperatorType.NOT));
// load string lib
addFunction(new StringContainsFunction());
addFunction(new StringIndexOfFunction());
addFunction(new StringStartsWithFunction());
addFunction(new StringEndsWithFunction());
addFunction(new StringSubStringFunction());
addFunction(new StringLengthFunction());
addFunction(new StringSplitFunction());
addFunction(new StringJoinFunction());
addFunction(new StringReplaceFirstFunction());
addFunction(new StringReplaceAllFunction());
// load math lib
addFunction(new MathAbsFunction());
addFunction(new MathRoundFunction());
addFunction(new MathPowFunction());
addFunction(new MathSqrtFunction());
addFunction(new MathLog10Function());
addFunction(new MathLogFunction());
addFunction(new MathSinFunction());
addFunction(new MathCosFunction());
addFunction(new MathTanFunction());
// seq lib
addFunction(new SeqMapFunction());
addFunction(new SeqReduceFunction());
addFunction(new SeqFilterFunction());
addFunction(new SeqSortFunction());
addFunction(new SeqIncludeFunction());
addFunction(new SeqCountFunction());
addFunction(new SeqEveryFunction());
addFunction(new SeqNotAnyFunction());
addFunction(new SeqSomeFunction())
;
addFunction(new SeqMakePredicateFunFunction("seq.eq", OperatorType.EQ));
addFunction(new SeqMakePredicateFunFunction("seq.neq", OperatorType.NEQ));
addFunction(new SeqMakePredicateFunFunction("seq.lt", OperatorType.LT));
addFunction(new SeqMakePredicateFunFunction("seq.le", OperatorType.LE));
addFunction(new SeqMakePredicateFunFunction("seq.gt", OperatorType.GT));
addFunction(new SeqMakePredicateFunFunction("seq.ge", OperatorType.GE));
addFunction(new SeqCompsitePredFunFunction("seq.and", LogicOp.AND));
addFunction(new SeqCompsitePredFunFunction("seq.or", LogicOp.OR));
addFunction(new SeqMakePredicateFunFunction("seq.true", OperatorType.EQ, AviatorBoolean.TRUE));
addFunction(new SeqMakePredicateFunFunction("seq.false", OperatorType.EQ, AviatorBoolean.FALSE));
addFunction(new SeqMakePredicateFunFunction("seq.nil", OperatorType.EQ, AviatorNil.NIL));
addFunction(new SeqMakePredicateFunFunction("seq.exists", OperatorType.NEQ, AviatorNil.NIL));
// load custom functions.
CustomFunctionLoader.load();
}
/**
* Compiled Expression cache
*/
private final static ConcurrentHashMap<String/* text expression */, FutureTask<Expression>/*
* Compiled
* expression
* task
*/> cacheExpressions =
new ConcurrentHashMap<String, FutureTask<Expression>>();
/**
* set optimize level,default AviatorEvaluator.EVAL
*
* @see #COMPILE
* @see #EVAL
* @deprecated please use {@link #setOption(Options, Object)}
*
* @param value
*/
public static void setOptimize(int value) {
if (value != COMPILE && value != EVAL) {
throw new IllegalArgumentException("Invlaid optimize option value");
}
setOption(Options.OPTIMIZE_LEVEL, value);
}
public static void setBYTECODE_VER(int nversion) {
BYTECODE_VER = nversion;
}
private AviatorEvaluator() {
}
/**
* Clear all cached compiled expression
*/
public static void clearExpressionCache() {
cacheExpressions.clear();
}
/**
* Returns classloader
*
* @return
*/
public static AviatorClassLoader getAviatorClassLoader() {
return getAviatorClassLoader(false);
}
/**
* Returns classloader
*
* @return
*/
public static AviatorClassLoader getAviatorClassLoader(boolean cached) {
if (cached)
return aviatorClassLoader;
else
return new AviatorClassLoader(Thread.currentThread().getContextClassLoader());
}
/**
* Add a aviator function,it's not thread-safe.
*
* @param function
*/
public static void addFunction(AviatorFunction function) {
final String name = function.getName();
if (FUNC_MAP.containsKey(name)) {
System.out.println("[Aviator WARN] The function '" + name + "' is already exists, but you replace it.");
}
FUNC_MAP.put(name, function);
}
/**
* Remove a aviator function by name,it's not thread-safe.
*
* @param name
* @return
*/
public static AviatorFunction removeFunction(String name) {
return (AviatorFunction) FUNC_MAP.remove(name);
}
/**
* get a aviator function by name,throw exception if null
*
* @param name
* @return
*/
public static AviatorFunction getFunction(String name) {
final AviatorFunction function = (AviatorFunction) FUNC_MAP.get(name);
if (function == null) {
throw new ExpressionRuntimeException("Could not find function named '" + name + "'");
}
return function;
}
/**
* Check if the function is existed in aviator
*
* @param name
* @return
*/
public static boolean containsFunction(String name) {
return FUNC_MAP.containsKey(name);
}
/**
* Remove a aviator function
*
* @param function
* @return
*/
public static AviatorFunction removeFunction(AviatorFunction function) {
return removeFunction(function.getName());
}
/**
* Configure user defined classloader
*
* @deprecated
* @param aviatorClassLoader
*/
public static void setAviatorClassLoader(AviatorClassLoader aviatorClassLoader) {
// AviatorEvaluator.aviatorClassLoader = aviatorClassLoader;
}
/**
* Returns a compiled expression in cache
*
* @param expression
* @return
*/
public static Expression getCachedExpression(String expression) {
FutureTask<Expression> task = cacheExpressions.get(expression);
if (task != null) {
return getCompiledExpression(expression, task);
}
else {
return null;
}
}
/**
* Compile a text expression to Expression object
*
* @param expression
* text expression
* @param cached
* Whether to cache the compiled result,make true to cache it.
* @return
*/
public static Expression compile(final String expression, final boolean cached) {
if (expression == null || expression.trim().length() == 0) {
throw new CompileExpressionErrorException("Blank expression");
}
if (cached) {
FutureTask<Expression> task = cacheExpressions.get(expression);
if (task != null) {
return getCompiledExpression(expression, task);
}
task = new FutureTask<Expression>(new Callable<Expression>() {
@Override
public Expression call() throws Exception {
return innerCompile(expression, cached);
}
});
FutureTask<Expression> existedTask = cacheExpressions.putIfAbsent(expression, task);
if (existedTask == null) {
existedTask = task;
existedTask.run();
}
return getCompiledExpression(expression, existedTask);
}
else {
return innerCompile(expression, cached);
}
}
private static Expression getCompiledExpression(final String expression, FutureTask<Expression> task) {
try {
return task.get();
}
catch (Exception e) {
cacheExpressions.remove(expression);
throw new CompileExpressionErrorException("Compile expression failure:" + expression, e);
}
}
private static Expression innerCompile(final String expression, boolean cached) {
ExpressionLexer lexer = new ExpressionLexer(expression);
CodeGenerator codeGenerator = newCodeGenerator(cached);
ExpressionParser parser = new ExpressionParser(lexer, codeGenerator);
return parser.parse();
}
private static int getOptimizeLevel() {
return getOption(Options.OPTIMIZE_LEVEL);
}
private static CodeGenerator newCodeGenerator(boolean cached) {
switch (getOptimizeLevel()) {
case COMPILE:
ASMCodeGenerator asmCodeGenerator =
new ASMCodeGenerator(getAviatorClassLoader(cached), traceOutputStream,
(Boolean) getOption(Options.TRACE));
asmCodeGenerator.start();
return asmCodeGenerator;
case EVAL:
return new OptimizeCodeGenerator(getAviatorClassLoader(cached), traceOutputStream,
(Boolean) getOption(Options.TRACE));
default:
throw new IllegalArgumentException("Unknow option " + getOptimizeLevel());
}
}
/**
* Compile a text expression to Expression Object without caching
*
* @param expression
* @return
*/
public static Expression compile(String expression) {
return compile(expression, false);
}
/**
* Execute a text expression with values that are variables order in the
* expression.It only runs in EVAL mode,and it will cache the compiled
* expression.
*
* @param expression
* @param values
* @return
*/
public static Object exec(String expression, Object... values) {
if (getOptimizeLevel() != EVAL) {
throw new IllegalStateException("Aviator evaluator is not in EVAL mode.");
}
Expression compiledExpression = compile(expression, true);
if (compiledExpression != null) {
List<String> vars = compiledExpression.getVariableNames();
if (!vars.isEmpty()) {
int valLen = values == null ? 0 : values.length;
if (valLen != vars.size()) {
throw new IllegalArgumentException("Expect " + vars.size() + " values,but has " + valLen);
}
Map<String, Object> env = new HashMap<String, Object>();
int i = 0;
for (String var : vars) {
env.put(var, values[i++]);
}
return compiledExpression.execute(env);
}
else {
return compiledExpression.execute();
}
}
else {
throw new ExpressionRuntimeException("Null compiled expression for " + expression);
}
}
/**
* Execute a text expression with environment
*
* @param expression
* text expression
* @param env
* Binding variable environment
* @param cached
* Whether to cache the compiled result,make true to cache it.
*/
public static Object execute(String expression, Map<String, Object> env, boolean cached) {
Expression compiledExpression = compile(expression, cached);
if (compiledExpression != null) {
return compiledExpression.execute(env);
}
else {
throw new ExpressionRuntimeException("Null compiled expression for " + expression);
}
}
/**
* Execute a text expression without caching
*
* @param expression
* @param env
* @return
*/
public static Object execute(String expression, Map<String, Object> env) {
return execute(expression, env, false);
}
/**
* Invalidate expression cache
*
* @param expression
*/
public static void invalidateCache(String expression) {
cacheExpressions.remove(expression);
}
/**
* Execute a text expression without caching and env map.
*
* @param expression
* @return
*/
public static Object execute(String expression) {
return execute(expression, (Map<String, Object>) null);
}
}