/** * 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); } }