/* * Copyright 2016 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.lang; import com.noga.njexl.lang.extension.SetOperations; import com.noga.njexl.lang.extension.TypeUtility; import com.noga.njexl.lang.extension.iterators.RangeIterator; import com.noga.njexl.lang.extension.oop.ScriptClass; import com.noga.njexl.lang.extension.oop.ScriptClassInstance; import com.noga.njexl.lang.extension.oop.ScriptMethod; import com.noga.njexl.lang.internal.logging.Log; import com.noga.njexl.lang.introspection.*; import com.noga.njexl.lang.parser.*; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Executable; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Eventing; import com.noga.njexl.lang.introspection.JexlPropertySet; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * An interpreter of JEXL syntax. * * @since 2.0 */ public class Interpreter implements ParserVisitor { HashMap<String, Script> imports; public HashMap<String, Script> imports() { return imports; } public ScriptClass resolveJexlClassName(String name) { String[] arr = name.split(":"); if (arr.length > 1) { // namespaced String s = arr[0]; name = arr[1]; Script script = imports.get(s); if (script == null) { return null; } Map<String, ScriptClass> map = script.classes(); if (map.containsKey(name)) { return map.get(name); } // you wanted specific, hence, I can not load you return null; } // Try, is that a Java object loaded? if (context.has(name)) { Object o = context.get(name); if ( !(o instanceof ScriptClass) ){ return new ScriptClass(this, name, o , current.name()); } } for (String s : imports.keySet()) { Script script = imports.get(s); Map<String, ScriptClass> map = script.classes(); if (map.containsKey(name)) { return map.get(name); } } return null; } protected Script resolveScriptForFunction(String prefix, String name) { Script script = imports.get(prefix); if (script != null) { if (script.methods().containsKey(name)) { return script; } } // else do some more for (String key : imports.keySet()) { script = imports.get(key); Map methods = script.methods(); if (methods.containsKey(name)) { return script; } } // now, is this name of a method passed as an arg? if (context.has(name)) { Object fo = context.get(name); if (fo != null) { String m = fo.toString(); String[] arr = m.split(":"); if (arr.length > 1) { prefix = arr[0]; name = arr[1]; } else { name = m; } return resolveScriptForFunction(prefix, name); } } return null; } protected JexlEngine jexlEngine; /** * The logger. */ protected final Log logger; /** * The uberspect. */ public final Uberspect uberspect; /** * The arithmetic handler. */ public final JexlArithmetic arithmetic; /** * The map of registered functions. */ protected final Map<String, Object> functions; public void addFunctionNamespace(String ns, Object o){ functions.put(ns,o) ; } public void removeFunctionNamespace(String ns){ functions.remove(ns) ; } /** * The map of registered functions. */ protected Map<String, Object> functors; /** * The context to store/retrieve variables. */ protected JexlContext context; /** * Strict interpreter flag. Do not modify; will be made final/private in a later version. */ protected boolean strict; /** * Silent intepreter flag. Do not modify; will be made final/private in a later version. */ protected boolean silent; /** * Cache executors. */ protected final boolean cache; /** * Registers or arguments. */ protected Object[] registers = null; /** * Parameter names if any. * Intended for use in debugging; not currently used externally. * * @since 2.1 */ @SuppressWarnings("unused") private String[] parameters = null; /** * Cancellation support. * * @see #isCancelled() * @since 2.1 */ private volatile boolean cancelled = false; /** * Empty parameters for method matching. */ protected static final Object[] EMPTY_PARAMS = new Object[0]; /** * Creates an interpreter. * * @param jexl the engine creating this interpreter * @param aContext the context to evaluate expression * @param strictFlag whether this interpreter runs in strict mode * @param silentFlag whether this interpreter runs in silent mode * @since 2.1 */ public Interpreter(JexlEngine jexl, JexlContext aContext, boolean strictFlag, boolean silentFlag) { this.jexlEngine = jexl; this.logger = jexl.logger; this.uberspect = jexl.uberspect; this.arithmetic = jexl.arithmetic; if (jexl.shareImports) { this.functions = jexl.functions; } else { // why this? Because use *clone* not the same object this.functions = new HashMap<>(jexl.functions); } this.strict = strictFlag; this.silent = silentFlag; this.cache = jexl.cache != null; this.context = aContext != null ? aContext : JexlEngine.EMPTY_CONTEXT; this.functors = null; imports = new HashMap<>(); imports.putAll(jexlEngine.imports); } /** * Copy constructor. * * @param base the base to copy * @since 2.1 */ protected Interpreter(Interpreter base) { this.jexlEngine = base.jexlEngine; this.logger = base.logger; this.uberspect = base.uberspect; this.arithmetic = base.arithmetic; // use *clone* not the actual this.functions = new HashMap<>(base.functions); this.strict = base.strict; this.silent = base.silent; this.cache = base.cache; this.context = base.context; this.functors = base.functors; this.imports = base.imports; // otherwise global variables won't be shared! this.registers = base.registers; } /** * Sets whether this interpreter considers unknown variables, methods and constructors as errors. * * @param flag true for strict, false for lenient * @since 2.1 * @deprecated Do not use; will be removed in a later version */ // TODO why add a method and then deprecate it? @Deprecated public void setStrict(boolean flag) { this.strict = flag; } /** * Sets whether this interpreter throws JexlException when encountering errors. * * @param flag true for silent, false for verbose * @deprecated Do not use; will be removed in a later version */ @Deprecated public void setSilent(boolean flag) { this.silent = flag; } /** * Checks whether this interpreter considers unknown variables, methods and constructors as errors. * * @return true if strict, false otherwise * @since 2.1 */ public boolean isStrict() { return this.strict; } /** * Checks whether this interpreter throws JexlException when encountering errors. * * @return true if silent, false otherwise */ public boolean isSilent() { return this.silent; } /** * Interpret the given script/expression. * <p> * If the underlying JEXL engine is silent, errors will be logged through its logger as info. * </p> * * @param node the script or expression to interpret. * @return the result of the interpretation. * @throws JexlException if any error occurs during interpretation. */ public Object interpret(JexlNode node) { try { TypeUtility.introspector = (UberspectImpl)uberspect ; return node.jjtAccept(this, null); } catch (JexlException.Return xreturn) { Object value = xreturn.getValue(); return value; } catch (JexlException xjexl) { if (silent) { logger.warn(xjexl.getMessage(), xjexl.getCause()); return null; } throw xjexl; } finally { functors = null; parameters = null; registers = null; } } /** * Gets the context. * * @return the {@link JexlContext} used for evaluation. * @since 2.1 */ public JexlContext getContext() { return context; } /** * Sets the context : needed but dangerous idea * * @param context the context in which it would wrok */ public void setContext(JexlContext context) { this.context = context; } /** * Gets the uberspect. * * @return an {@link Uberspect} */ protected Uberspect getUberspect() { return uberspect; } /** * Sets this interpreter parameters and arguments. * * @param frame the calling frame * @since 2.1 */ protected void setFrame(JexlEngine.Frame frame) { if (frame != null) { this.parameters = frame.getParameters(); this.registers = frame.getRegisters(); } else { this.parameters = null; this.registers = null; } } /** * Finds the node causing a NPE for diadic operators. * * @param xrt the RuntimeException * @param node the parent node * @param left the left argument * @param right the right argument * @return the left, right or parent node */ protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) { if (xrt instanceof ArithmeticException && JexlException.NULL_OPERAND == xrt.getMessage()) { if (left == null) { return node.jjtGetChild(0); } if (right == null) { return node.jjtGetChild(1); } } return node; } public boolean errorOnUndefinedVariable = true; /** * Triggered when variable can not be resolved. * * @param xjexl the JexlException ("undefined variable " + variable) * @return throws JexlException if strict, null otherwise */ protected Object unknownVariable(JexlException xjexl) { if (errorOnUndefinedVariable) { throw xjexl; } if (!silent) { logger.warn(xjexl.getMessage()); } return null; } /** * Triggered when method, function or constructor invocation fails. * * @param xjexl the JexlException wrapping the original error * @return throws JexlException if strict, null otherwise */ protected Object invocationFailed(JexlException xjexl) { if (strict || xjexl instanceof JexlException.Return) { throw xjexl; } if (!silent) { logger.warn(xjexl.getMessage(), xjexl.getCause()); } return null; } /** * Checks whether this interpreter execution was cancelled due to thread interruption. * * @return true if cancelled, false otherwise * @since 2.1 */ protected boolean isCancelled() { if (cancelled | Thread.interrupted()) { cancelled = true; } return cancelled; } boolean isEventing; String eventingPattern; Eventing eventing; /** * Resolves a namespace, eventually allocating an instance using context as constructor argument. * The lifetime of such instances span the current expression or script evaluation. * * @param prefix the prefix name (may be null for global namespace) * @param node the AST node * @return the namespace instance */ protected Object resolveNamespace(String prefix, JexlNode node) { // just resolve the prefix and all eventingPattern = ""; isEventing = false; if (prefix != null) { isEventing = Eventing.EVENTS.matcher(prefix).matches(); if (isEventing) { eventingPattern = prefix.substring(0, 2); prefix = prefix.substring(2); } } Object namespace = null; // check whether this namespace is a functor if (functors != null) { namespace = functors.get(prefix); if (namespace != null) { return namespace; } } // check if namespace if a resolver if (context instanceof NamespaceResolver) { namespace = ((NamespaceResolver) context).resolveNamespace(prefix); } if (namespace == null) { String methodName = ""; if (prefix == null) { methodName = node.jjtGetChild(0).image; } else { methodName = node.jjtGetChild(1).image; } namespace = resolveScriptForFunction(prefix, methodName); if (namespace != null) { return namespace; } namespace = functions.get(prefix); if (prefix != null && namespace == null) { namespace = resolveJexlClassName(prefix); if ( namespace != null ) return namespace ; throw new JexlException(node, "no such function namespace " + prefix); } if (context.has(methodName)) { Object r = context.get(methodName); if (r instanceof ScriptMethod) { return current; } } } // allow namespace to be instantiated as functor with context if possible, not an error otherwise if (namespace instanceof Class<?>) { Object[] args = new Object[]{context}; JexlMethod ctor = uberspect.getConstructorMethod(namespace, args, node); if (ctor != null) { try { namespace = ctor.invoke(namespace, args); if (functors == null) { functors = new HashMap<String, Object>(); } functors.put(prefix, namespace); } catch (Exception xinst) { throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst); } } } return namespace; } @Override public Object visit(ASTImportStatement node, Object data) { String from = node.jjtGetChild(0).image; String as = node.jjtGetChild(1).image; try { if (functions.containsKey(as) || imports.containsKey(as)) { throw new Exception(String.format("[%s] is already taken as import name!", as)); } try { Class<?> c = Class.forName(from); functions.put(as, c); context.set(as, c); return c; } catch (Exception e) { try { //perhaps a field ? String actClass = from.substring(0, from.lastIndexOf(".")); Object c = null; if ( context.has(actClass)){ c = context.get(actClass); }else{ c = Class.forName(actClass); } String fieldName = from.substring(from.lastIndexOf(".") + 1); Object o = null; if ( c instanceof Executable ){ o = ((Executable)c).get(fieldName); }else { try { JexlPropertyGet pg = uberspect.getPropertyGet(c, fieldName, null); o = pg.invoke(null); }catch (Exception ex){ if ( fieldName.equals("class")){ o = c ; } } } try { functions.put(as, o); context.set(as, o); return o; } catch (Exception set) { throw new Exception("Exception setting up functions and context -->" + " am I passed unmodifiable stupidity?"); } } catch (Exception ee) { // safely ignore this... } } Script base; if (data == null) { // I am the starting interpreter, base = current; } else { base = (Script) data; } Script freshlyImported = jexlEngine.importScript(from, as, base); freshlyImported.setup(context); imports.put(as, freshlyImported); functions.put(as, freshlyImported); context.set(as, freshlyImported); return freshlyImported; } catch (Exception e) { JexlException jexlException = new JexlException(node, "Import Failed!", e); return invocationFailed(jexlException); } } @Override public Object visit(ASTParamDef node, Object data) { return null; } public static class NamedArgs { public final String name; public final Object value; public NamedArgs(String n, Object v) { name = n; value = v; } } @Override public Object visit(ASTArgDef node, Object data) { int numChild = node.jjtGetNumChildren(); JexlNode valNode = node.jjtGetChild(numChild-1); if (numChild == 1) { return valNode.jjtAccept(this, data); } String name = node.jjtGetChild(0).image ; Object val = valNode.jjtAccept(this,data); NamedArgs na = new NamedArgs(name, val); return na; } @Override public Object visit(ASTMethodDef node, Object data) { JexlNode n = node.jjtGetChild(0); if ( n instanceof ASTIdentifier ){ ScriptMethod method = current.methods().get(n.image); if ( method == null ){ //named inner method? ScriptMethod inner = new ScriptMethod(node,context); context.set(inner.name,inner); return inner; } return method ; } // anonymous functions should not be there inside a main script? return new ScriptMethod(node, context); } @Override public Object visit(ASTExtendsDef node, Object data) { return null; } @Override public Object visit(ASTClassDef node, Object data) { return null; } /** * {@inheritDoc} */ public Object visit(ASTAdditiveNode node, Object data) { /** * The pattern for exception mgmt is to let the child*.jjtAccept * out of the try/catch loop so that if one fails, the ex will * traverse up to the interpreter. * In cases where this is not convenient/possible, JexlException must * be caught explicitly and rethrown. */ JexlNode leftNode = node.jjtGetChild(0); Object left = leftNode.jjtAccept(this, data); for (int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) { Object right = node.jjtGetChild(c).jjtAccept(this, data); try { JexlNode op = node.jjtGetChild(c - 1); if (op instanceof ASTAdditiveOperator) { String which = op.image; switch(which) { case "+" : left = arithmetic.add(left, right); continue; case "-" : left = arithmetic.subtract(left, right); continue; case "+=" : left = arithmetic.addMutable(left, right); //assign assignToNode(-1, node, leftNode, left); continue; case "-=" : left = arithmetic.subtractMutable(left, right); //assign assignToNode(-1, node, leftNode, left); continue; case "*=": left = arithmetic.multiply(left, right); //assign assignToNode(-1, node, leftNode, left); continue; case "/=": left = arithmetic.divide(left, right); //assign assignToNode(-1, node, leftNode, left); continue; default: throw new UnsupportedOperationException("unknown additive operator " + which); } } throw new IllegalArgumentException("unknown non-additive operator " + op); } catch (ArithmeticException xrt) { JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "+/- error", xrt); } } return left; } /** * {@inheritDoc} */ public Object visit(ASTAdditiveOperator node, Object data) { throw new UnsupportedOperationException("Should not be called."); } /** * {@inheritDoc} */ public Object visit(ASTAndNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); try { boolean leftValue = arithmetic.toBoolean(left); if (!leftValue) { return Boolean.FALSE; } } catch (RuntimeException xrt) { throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); } Object right = node.jjtGetChild(1).jjtAccept(this, data); try { boolean rightValue = arithmetic.toBoolean(right); if (!rightValue) { return Boolean.FALSE; } } catch (ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); } return Boolean.TRUE; } /** * {@inheritDoc} */ public Object visit(ASTArrayAccess node, Object data) { // first objectNode is the identifier Object object = node.jjtGetChild(0).jjtAccept(this, data); // can have multiple nodes - either an expression, integer literal or reference int numChildren = node.jjtGetNumChildren(); for (int i = 1; i < numChildren; i++) { JexlNode nindex = node.jjtGetChild(i); if (nindex instanceof JexlNode.Literal<?>) { /* BUG fix -- x[0] with x=null would not throw exception. Now, it does. */ if (object == null) { throw new JexlException(node, "object is null"); } object = nindex.jjtAccept(this, object); } else { Object index = nindex.jjtAccept(this, null); object = getAttribute(object, index, nindex); } } return object; } public Object visit(ASTArrayRange node, Object data) { // There should be a single map entry hence : Object start = node.jjtGetChild(0).jjtAccept(this, data); Object end = node.jjtGetChild(1).jjtAccept(this, data); try { if (node.jjtGetNumChildren() == 3) { Object step = node.jjtGetChild(2).jjtAccept(this, data); return TypeUtility.range(end, start, step); } return TypeUtility.range(end, start); } catch (Exception e) { throw new JexlException(node, "Invalid Range!", e); } } /** * {@inheritDoc} */ public Object visit(ASTArrayLiteral node, Object data) { Object literal = node.getLiteral(); if (literal == null) { int childCount = node.jjtGetNumChildren(); Object[] array = new Object[childCount]; for (int i = 0; i < childCount; i++) { Object entry = node.jjtGetChild(i).jjtAccept(this, data); array[i] = entry; } literal = arithmetic.narrowArrayType(array); node.setLiteral(literal); } return literal; } public Object assignToNode(int register, JexlNode node, JexlNode left, Object right) { // determine initial object & property: JexlNode objectNode = null; Object object = register >= 0 ? registers[register] : null; JexlNode propertyNode = null; Object property = null; boolean isVariable = true; int v = 0; StringBuilder variableName = null; // 1: follow children till penultimate, resolve dot/array int last = left.jjtGetNumChildren() - 1; // check we are not assigning a register itself boolean isRegister = last < 0 && register >= 0; // start at 1 if register for (int c = register >= 0 ? 1 : 0; c < last; ++c) { objectNode = left.jjtGetChild(c); // evaluate the property within the object object = objectNode.jjtAccept(this, object); if (object != null) { continue; } isVariable &= objectNode instanceof ASTIdentifier || (objectNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) objectNode).isInteger()); // if we get null back as a result, check for an ant variable if (isVariable) { if (v == 0) { variableName = new StringBuilder(left.jjtGetChild(0).image); v = 1; } for (; v <= c; ++v) { variableName.append('.'); variableName.append(left.jjtGetChild(v).image); } object = context.get(variableName.toString()); // disallow mixing ant & bean with same root; avoid ambiguity if (object != null) { isVariable = false; } } else { throw new JexlException(objectNode, "illegal assignment form"); } } // 2: last objectNode will perform assignement in all cases propertyNode = isRegister ? null : left.jjtGetChild(last); boolean antVar = false; if (propertyNode instanceof ASTIdentifier) { ASTIdentifier identifier = (ASTIdentifier) propertyNode; register = identifier.getRegister(); if (register >= 0) { isRegister = true; } else { property = identifier.image; antVar = true; } } else if (propertyNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) propertyNode).isInteger()) { property = ((ASTNumberLiteral) propertyNode).getLiteral(); antVar = true; } else if (propertyNode instanceof ASTArrayAccess) { // first objectNode is the identifier objectNode = propertyNode; ASTArrayAccess narray = (ASTArrayAccess) objectNode; Object nobject = narray.jjtGetChild(0).jjtAccept(this, object); if (nobject == null) { throw new JexlException(objectNode, "array element is null"); } else { object = nobject; } // can have multiple nodes - either an expression, integer literal or // reference last = narray.jjtGetNumChildren() - 1; for (int i = 1; i < last; i++) { objectNode = narray.jjtGetChild(i); if (objectNode instanceof JexlNode.Literal<?>) { object = objectNode.jjtAccept(this, object); } else { Object index = objectNode.jjtAccept(this, null); object = getAttribute(object, index, objectNode); } } property = narray.jjtGetChild(last).jjtAccept(this, null); } else if (!isRegister) { throw new JexlException( objectNode != null ? objectNode : node, // ensure we have non null markdown! "illegal assignment form"); } // deal with ant variable; set context if (isRegister) { registers[register] = right; return right; } else if (antVar) { if (isVariable && object == null) { if (variableName != null) { if (last > 0) { variableName.append('.'); } variableName.append(property); property = variableName.toString(); } try { context.set(String.valueOf(property), right); } catch (UnsupportedOperationException xsupport) { throw new JexlException(node, "context is readonly", xsupport); } return right; } } if (property == null) { // no property, we fail throw new JexlException(propertyNode, "property is null"); } if (object == null) { // no object, we fail throw new JexlException(objectNode, "bean is null"); } // one before last, assign setAttribute(object, property, right, propertyNode); return right; } @Override public Object visit(ASTTagContainer node, Object data) { Object o = node.jjtGetChild(0).jjtAccept(this, data); return o; } /** * {@inheritDoc} */ public Object visit(ASTAssignment node, Object data) { // left contains the reference to assign to JexlNode left = node.jjtGetChild(0); JexlNode value = node.jjtGetChild(1); if (left instanceof ASTTuple) { return tupleAssignment((ASTTuple) left, value, data); } int register = -1; if (left instanceof ASTIdentifier) { ASTIdentifier var = (ASTIdentifier) left; register = var.getRegister(); if (register < 0) { throw new JexlException(left, "unknown variable " + left.image); } } else if (!(left instanceof ASTReference)) { throw new JexlException(left, "illegal assignment form 0"); } Object right = null; if (value instanceof ASTMethodDef) { // handle anonymous function and closures ...(?) right = new ScriptMethod((ASTMethodDef) value, context ); } else { // right is the value expression to assign right = value.jjtAccept(this, data); } return assignToNode(register, node, left, right); } private Object[] tupleAssignment(ASTTuple astTuple, JexlNode value, Object data) { final int c = astTuple.jjtGetNumChildren(); JexlNode errorNode = astTuple.jjtGetChild(c - 1); final JexlNode startNode = astTuple.jjtGetChild(0); final boolean catchError = errorNode instanceof ASTTagContainer; final boolean fromRight = startNode instanceof ASTTagContainer ; JexlNode left = null; int upto = c; if (catchError) { left = astTuple.jjtGetChild(c - 1).jjtGetChild(0); if (!(left instanceof ASTReference)) { throw new JexlException(left, "illegal assignment form 0"); } errorNode = left;// re-assign upto = c - 1; } int start = 0; if ( fromRight ){ start = 1 ; left = startNode.jjtGetChild(0); } for (int i = start ; i < upto; i++) { left = astTuple.jjtGetChild(i); if (!(left instanceof ASTReference)) { throw new JexlException(left, "illegal assignment form 0"); } } Object result = null; Throwable error = null; synchronized (this) { boolean oldStrict = strict; try { strict = true; result = value.jjtAccept(this, data); } catch (Throwable t) { error = t; if (t.getCause() != null) { error = t.getCause(); } } finally { strict = oldStrict; } } JexlNode node = astTuple.jjtGetParent(); ArrayList l = new ArrayList(); if (error != null) { if ( fromRight ){ left = startNode.jjtGetChild(0); assignToNode(-1, node, left, null); l.add(null); } for (int i = start; i < upto; i++) { left = astTuple.jjtGetChild(i); assignToNode(-1, node, left, null); l.add(null); } } else { if (catchError && upto == 1) { // (o,:e) error check only - no project assignToNode(-1, node, left, result); l.add(result); } else { // project List t = TypeUtility.combine(result); int s = t.size(); if ( fromRight ){ int i = s - 1 ; int ni = upto - 1; while ( ni > 0 && i >= 0 ){ Object o = t.get(i); left = astTuple.jjtGetChild(ni); assignToNode(-1, node, left, o); l.add(0,o); i--; ni--; } if ( i >= 0 ){ left = startNode.jjtGetChild(0); Object o = t.get(i); assignToNode(-1, node, left, o); l.add(0,o); }else{ left = startNode.jjtGetChild(0); assignToNode(-1, node, left, null); l.add(0,null); for ( i = ni; i >= 1; i-- ){ left = astTuple.jjtGetChild(i); assignToNode(-1, node, left, null); l.add(0,null); } } }else { for (int i = 0; i < upto; i++) { left = astTuple.jjtGetChild(i); Object o = null; if (i < s) { o = t.get(i); } assignToNode(-1, node, left, o); l.add(o); } } } } if (catchError) { assignToNode(-1, node, errorNode, error); l.add(error); } return l.toArray(); } @Override public Object visit(ASTTuple node, Object data) { List l = TypeUtility.combine(); int c = node.jjtGetNumChildren(); for (int i = 0; i < c; i++) { Object r = node.jjtGetChild(i).jjtAccept(this, data); l.add(r); } return l.toArray(); } /** * {@inheritDoc} */ public Object visit(ASTBitwiseAndNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.bitwiseAnd(left, right); } catch (ArithmeticException xrt) { throw new JexlException(node, "& error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTBitwiseComplNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); try { return arithmetic.bitwiseComplement(left); } catch (ArithmeticException xrt) { throw new JexlException(node, "~ error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTBitwiseOrNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.bitwiseOr(left, right); } catch (ArithmeticException xrt) { throw new JexlException(node, "| error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTBitwiseXorNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.bitwiseXor(left, right); } catch (ArithmeticException xrt) { throw new JexlException(node, "^ error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTBlock node, Object data) { int numChildren = node.jjtGetNumChildren(); Object result = null; for (int i = 0; i < numChildren; i++) { result = node.jjtGetChild(i).jjtAccept(this, data); } return result; } /** * {@inheritDoc} */ public Object visit(ASTBreakStatement node, Object data) { int c = node.jjtGetNumChildren(); if (c == 0) { //raise BreakException at parent -- not it's own condition throw new JexlException.Break(node.jjtGetParent()); } // conditional break Object value = node.jjtGetChild(0).jjtAccept(this, data); boolean b = TypeUtility.castBoolean(value, false); if (b) { if (c > 1) { value = node.jjtGetChild(1).jjtAccept(this, data); throw new JexlException.Break(node.jjtGetParent(), value); } // signifies breaking on condition, not returning value throw new JexlException.Break(node.jjtGetParent()); } return data; } /** * {@inheritDoc} */ public Object visit(ASTContinueStatement node, Object data) { int c = node.jjtGetNumChildren(); if (c == 0) { //raise ContinueException at parent -- not it's own condition throw new JexlException.Continue(node.jjtGetParent()); } // conditional continue Object value = node.jjtGetChild(0).jjtAccept(this, data); boolean b = TypeUtility.castBoolean(value, false); if (b) { if (c > 1) { value = node.jjtGetChild(1).jjtAccept(this, data); throw new JexlException.Continue(node.jjtGetParent(), value); } // signifies continue on condition, not returning value throw new JexlException.Continue(node.jjtGetParent()); } return data; } /** * {@inheritDoc} */ public Object visit(ASTDivNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.divide(left, right); } catch (ArithmeticException xrt) { if (!strict) { return new Double(0.0); } JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "divide error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTEmptyFunction node, Object data) { Object o = node.jjtGetChild(0).jjtAccept(this, data); if (o == null) { return Boolean.TRUE; } if (o instanceof String && "".equals(o)) { return Boolean.TRUE; } if (o.getClass().isArray() && ((Object[]) o).length == 0) { return Boolean.TRUE; } if (o instanceof Collection<?>) { return ((Collection<?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE; } // Map isn't a collection if (o instanceof Map<?, ?>) { return ((Map<?, ?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE; } return Boolean.FALSE; } /** * {@inheritDoc} */ public Object visit(ASTDefinedFunction node, Object data) { try { JexlNode n = node.jjtGetChild(0).jjtGetChild(0); int num = n.jjtGetNumChildren(); if (n instanceof ASTIdentifier && num == 1 ) { String varName = n.image; return context.has(varName); } else { // null is defined if ( num > 0 && n.jjtGetChild(0) instanceof ASTNullLiteral) { return true; } // now here try to do slightly better Object o = node.jjtGetChild(0).jjtAccept(this, data); return (o!= null) && ( !NULL.equals(o)); } } catch (Exception e) { } return false; } /** * {@inheritDoc} */ public Object visit(ASTISANode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { if (left == null) { return (right == null) ; } if (left instanceof ScriptClassInstance) { return ((ScriptClassInstance) left).getNClass().isa(right); } if ( right instanceof String ){ if ( ((String)right).startsWith("@") && left != null ){ String type = ((String)right).substring(1); String interfaceType = type.toLowerCase(); switch ( interfaceType ){ case "err" : case "error" : return left instanceof Throwable; case "list" : return left instanceof List ; case "map" : case "dict" : return left instanceof Map ; case "set" : return left instanceof Set ; case "num" : return left instanceof Number ; case "nan" : return (left instanceof Number) && Double.isNaN( ((Number)left).doubleValue() ); case "chrono" : return JexlArithmetic.isTimeLike(left); case "z" : return JexlArithmetic.isZ(left); case "q" : return JexlArithmetic.isQ(left); case "enum" : return ( left.getClass().isEnum() ); case "array" : case "arr": return ( left.getClass().isArray() ); default: // DOS convention, should not pass less than 3 chars, ever if ( type.length() < 3 ) return false ; // try regex match really Pattern p = Pattern.compile(type, Pattern.CASE_INSENSITIVE); String name = left.getClass().getName(); Matcher m = p.matcher(name); return m.find(); } } } Class r; if (right instanceof Class) { r = (Class) right; } else { r = right.getClass(); } Class l; if (left instanceof Class) { l = (Class) left; } else { l = left.getClass(); } return r.isAssignableFrom(l); } catch (ArithmeticException xrt) { throw new JexlException(node, "isa error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTINNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return SetOperations.in(left, right); } catch (ArithmeticException xrt) { throw new JexlException(node, "in error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTAEQNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { if (left == right) { return true; } if (left == null || right == null) { return false; } if (left instanceof ScriptClassInstance && right instanceof ScriptClassInstance) { if (((ScriptClassInstance) left).getNClass().equals(((ScriptClassInstance) right).getNClass())) { return left.equals(right); } return false; } if (left.getClass().equals(right.getClass())) { return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE; } return false; } catch (ArithmeticException xrt) { throw new JexlException(node, "=== error", xrt); } } @Override public Object visit(ASTEndWithNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try{ int mySize = sizeOf(node.jjtGetChild(1),right ); if ( mySize == 0 ){ mySize = 1 ; } int containerSize = sizeOf(node.jjtGetChild(0),left ); int pos = TypeUtility.rindex(right,left); return ( containerSize - pos == mySize ); }catch (Exception e){} return false; } @Override public Object visit(ASTInOrderNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try{ int pos = TypeUtility.index(left,right); return ( pos >= 0 ); }catch (Exception e){} return false; } @Override public Object visit(ASTStartWithNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try{ int pos = TypeUtility.index(right,left); return ( 0 == pos ); }catch (Exception e){} return false; } /** * {@inheritDoc} */ public Object visit(ASTEQNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, "== error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTFalseNode node, Object data) { return Boolean.FALSE; } /** * {@inheritDoc} */ public Object visit(ASTForeachStatement node, Object data) { return node.jjtGetChild(0).jjtAccept(this, data); } /** * {@inheritDoc} */ public Object visit(ASTExpressionFor node, Object data) { if (node.jjtGetNumChildren() > 0) { return node.jjtGetChild(0).jjtAccept(this, data); } // in lieu of anything is... return true; } /** * {@inheritDoc} */ public Object visit(ASTForWithCondition node, Object data) { Object result = null; int numChild = node.jjtGetNumChildren(); /* last node is the statement to execute */ JexlNode statement = node.jjtGetChild(numChild - 1); // initialize the stuff node.jjtGetChild(0).jjtAccept(this, data); while (true) { // now the condition Object cond = node.jjtGetChild(1).jjtAccept(this, data); boolean condition = TypeUtility.castBoolean(cond, false); if (!condition) break; if (isCancelled()) { throw new JexlException.Cancel(node); } try { // execute statement result = statement.jjtAccept(this, data); } catch (JexlException.Break b) { result = b; break; } catch (JexlException.Continue c) { result = c; // even in here too .. node.jjtGetChild(2).jjtAccept(this, data); continue; } // last one node.jjtGetChild(2).jjtAccept(this, data); } return result; } /** * {@inheritDoc} */ public Object visit(ASTForWithIterator node, Object data) { Object result = null; /* first objectNode is the loop variable */ ASTReference loopReference = (ASTReference) node.jjtGetChild(0); ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0); int register = loopVariable.getRegister(); /* second objectNode is the variable to iterate */ Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data); // make sure there is a value to iterate on and a statement to execute if (iterableValue != null && node.jjtGetNumChildren() >= 3) { /* third objectNode is the statement to execute */ JexlNode statement = node.jjtGetChild(2); // get an iterator for the collection/array etc via the // introspector. Iterator<?> itemsIterator = uberspect.getIterator(iterableValue, node); if (itemsIterator != null) { while (itemsIterator.hasNext()) { if (isCancelled()) { throw new JexlException.Cancel(node); } // set loopVariable to value of iterator Object value = itemsIterator.next(); if (register < 0) { context.set(loopVariable.image, value); } else { registers[register] = value; } try { // execute statement result = statement.jjtAccept(this, data); } catch (JexlException.Break b) { result = b; break; } catch (JexlException.Continue c) { result = c; continue; } } } } return result; } /** * {@inheritDoc} */ public Object visit(ASTGENode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, ">= error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTGTNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, "> error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTERNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); if ( right == null ){ if ( left == null ) return true ; return false ; } try { // use arithmetic / pattern matching ? if (right instanceof java.util.regex.Pattern || right instanceof String) { return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE; } // left in right ? <=> right.contains(left) ? // try contains on collection if (right instanceof Set<?>) { return ((Set<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE; } // try contains on map key if (right instanceof Map<?, ?>) { return ((Map<?, ?>) right).containsKey(left) ? Boolean.TRUE : Boolean.FALSE; } if ( JexlArithmetic.areListOrArray(left,right )){ return (TypeUtility.index(left, right) >= 0) ; } // try contains on collection if (right instanceof Collection<?>) { return ((Collection<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE; } // try a contains method (duck type set) try { Object[] argv = {left}; JexlMethod vm = uberspect.getMethod(right, "contains", argv, node); if (vm != null) { return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE; } else if (arithmetic.narrowArguments(argv)) { vm = uberspect.getMethod(right, "contains", argv, node); if (vm != null) { return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE; } } } catch (InvocationTargetException e) { throw new JexlException(node, "=~ invocation error", e.getCause()); } catch (Exception e) { throw new JexlException(node, "=~ error", e); } // try iterative comparison Iterator<?> it = uberspect.getIterator(right, node); if (it != null) { while (it.hasNext()) { Object next = it.next(); if (next == left || (next != null && next.equals(left))) { return Boolean.TRUE; } } return Boolean.FALSE; } // defaults to equal return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, "=~ error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTIdentifier node, Object data) { if (isCancelled()) { throw new JexlException.Cancel(node); } String name = node.image; if (data == null) { int register = node.getRegister(); if (register >= 0) { return registers[register]; } Object value = context.get(name); if (value == null && !(node.jjtGetParent() instanceof ASTReference) && !context.has(name) && !isTernaryProtected(node)) { JexlException xjexl = new JexlException.Variable(node, name); return unknownVariable(xjexl); } if (current != null) { // last try, is that a method name? if (current.methods().containsKey(name)) { return current.methods().get(name); } } return value; } else { return getAttribute(data, name, node); } } /** * {@inheritDoc} */ public Object visit(ASTVar node, Object data) { return visit((ASTIdentifier) node, data); } /** * {@inheritDoc} */ public Object visit(ASTIfStatement node, Object data) { int n = 0; try { Object result = null; /* first objectNode is the condition */ Object expression = node.jjtGetChild(0).jjtAccept(this, data); if (arithmetic.toBoolean(expression)) { // first objectNode is true statement n = 1; result = node.jjtGetChild(1).jjtAccept(this, data); } else { // if there is a false, execute it. false statement is the second // objectNode if (node.jjtGetNumChildren() == 3) { n = 2; result = node.jjtGetChild(2).jjtAccept(this, data); } } return result; } catch (JexlException error) { throw error; } catch (ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(n), "if error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTNumberLiteral node, Object data) { if (data != null && node.isInteger()) { return getAttribute(data, node.getLiteral(), node); } return node.getLiteral(); } /** * {@inheritDoc} */ public Object visit(ASTJexlScript node, Object data) { int numChildren = node.jjtGetNumChildren(); Object result = null; for (int i = 0; i < numChildren; i++) { JexlNode child = node.jjtGetChild(i); try { result = child.jjtAccept(this, data); } catch (JexlException.Jump jump) { i = jump.location - 1; result = jump; continue; } } return result; } /** * {@inheritDoc} */ public Object visit(ASTLENode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, "<= error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTLTNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE; } catch (ArithmeticException xrt) { throw new JexlException(node, "< error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTMapEntry node, Object data) { Object key = node.jjtGetChild(0).jjtAccept(this, data); Object value = node.jjtGetChild(1).jjtAccept(this, data); return new Object[]{key, value}; } /** * {@inheritDoc} */ public Object visit(ASTMapLiteral node, Object data) { int childCount = node.jjtGetNumChildren(); Map<Object, Object> map = new HashMap<Object, Object>(); for (int i = 0; i < childCount; i++) { Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data); map.put(entry[0], entry[1]); } return map; } /** * The class to handle anonymous method calls */ public static class AnonymousParam implements Runnable { private static class AnonContext extends MapContext { JexlContext outer; AnonContext(JexlContext from){ super(); outer = from ; } void putAnons(Object[] p){ map.put(Script._CONTEXT_, p[0]); map.put(Script._ITEM_, p[1]); map.put(Script._INDEX_, p[2]); map.put(Script._PARTIAL_, p[3]); } @Override public Object get(String name) { if ( map.containsKey(name) ) { return map.get(name); } return outer.get(name); } @Override public boolean has(String name) { if ( map.containsKey(name) ) return true ; return outer.has(name); } @Override public void set(String name, Object value) { if ( outer.has(name ) && ! map.containsKey(name) ) { outer.set(name, value); return ; } map.put(name, value); } @Override public JexlContext copy() { return new AnonContext(this); } } // the underlying interpreter public Interpreter interpreter; // the method block public ASTBlock block; AnonContext anonContext; /** * Constructs a anonymous parameter * * @param i the interpreter * @param block the block */ public AnonymousParam(Interpreter i, ASTBlock block) { this.interpreter = i; this.block = block; anonContext = new AnonContext( interpreter.context ); } /** * Sets the iteration context * * @param con context of iteration, the whole object, in case of a collection the collection * @param o the individual object, elements of the collection * @param i the index - of the element in the collection */ public void setIterationContext(Object con, Object o, Object i) { setIterationContextWithPartial(con,o,i,null); } /** * Sets the iteration context with partial updated result * * @param con context of iteration, the whole object, in case of a collection the collection * @param o the individual object, elements of the collection * @param i the index - of the element in the collection * @param p the partial result as of current iteration */ public void setIterationContextWithPartial(Object con, Object o, Object i, Object p) { anonContext.putAnons( new Object[]{con,o,i,p} ); } /** * Executes the anonymous function * If it has resulted in a break; it returns the break exception * If it has resulted in a continue ; it returns the continue exception * Hence, caller must not catch these two exceptions at all * @return the result of the call */ public Object execute() { try { interpreter.context = anonContext ; Object ret = block.jjtAccept(interpreter, null); return ret; } catch (JexlException.Return r) { return r.getValue(); } catch (JexlException.Break b) { // important to return itself... return b; } catch (JexlException.Continue c) { // important to return itself... return c; } finally { interpreter.context = anonContext.outer ; } } /** * Executes the block atomically * @return the result of execute */ public synchronized Object atomicExec(){ return execute(); } /** * Gets a variable * * @param name of the variable * @return variable value on the running context */ public Object getVar(String name) { return anonContext.get(name); } @Override public void run() { Long l = Thread.currentThread().getId() ; try { synchronized (this) { // use a newer interpreter! this.interpreter = new Interpreter(interpreter); } execute(); } finally { this.interpreter = null ; // mark for gc...? System.gc(); } } } /** * Calls a method (or function). * <p> * Method resolution is a follows: * 1 - attempt to find a method in the bean passed as parameter; * 2 - if this fails, narrow the arguments and try again * 3 - if this still fails, seeks a Script or JexlMethod as a property of that bean. * </p> * * @param node the method node * @param bean the bean this method should be invoked upon * @param methodNode the node carrying the method name * @param argb the first argument index, child of the method node * @return the result of the method invocation */ private Object call(JexlNode node, Object bean, ASTIdentifier methodNode, int argb) { if (isCancelled()) { throw new JexlException.Cancel(node); } String methodName = methodNode.image; // evaluate the arguments int argc = node.jjtGetNumChildren() - argb; Object[] argv = new Object[argc]; if (argc > 0) { SimpleNode n = node.jjtGetChild(argb); if (n instanceof ASTBlock) { /* the anonymous function is passed here. Thus, we should pass this as argument */ argv[0] = new AnonymousParam(this, (ASTBlock) n); } else { argv[0] = n.jjtAccept(this, null); } for (int i = 1; i < argc; i++) { argv[i] = node.jjtGetChild(i + argb).jjtAccept(this, null); } // if __args__ expansion was used? if (n.jjtGetNumChildren() > 0 && n.jjtGetChild(0).jjtGetNumChildren() > 1) { // this means that it has __args__ = foo bar assignment if (Script.ARGS.equals(n.jjtGetChild(0).jjtGetChild(0).image) && "=".equals( n.jjtGetChild(0).jjtGetChild(1).image ) ) { argv = (Object[]) argv[0]; } } } JexlException xjexl = null; // save eventing states, when we do not have a stack boolean wasEventing = isEventing; Eventing curEventing = null; Eventing.Event event = null; if (isEventing) { eventing = getEventing(bean); curEventing = eventing; // set it here event = new Eventing.Event(eventingPattern, methodName, argv); eventing.before(event); } try { // attempt to reuse last executor cached in volatile JexlNode.value if (cache) { Object cached = node.jjtGetValue(); if (cached instanceof JexlMethod) { JexlMethod me = (JexlMethod) cached; Object eval = me.tryInvoke(methodName, bean, argv); if (!me.tryFailed(eval)) { return eval; } } } boolean cacheable = cache; if (bean instanceof Executable) { try { return ((Executable) bean).execMethod(methodName, this, argv); }catch (Throwable e){ if ( e.getCause() instanceof NoSuchMethodException ){ // continue }else{ throw e; } } } JexlMethod vm = uberspect.getMethod(bean, methodName, argv, node); // DG: If we can't find an exact match, narrow the parameters and try again if (vm == null) { /* Intercept it here, if possible */ Boolean[] success = new Boolean[1]; Object ret = TypeUtility.interceptCastingCall(methodName, argv, success); if (success[0]) { return ret; } if (arithmetic.narrowArguments(argv)) { vm = uberspect.getMethod(bean, methodName, argv, node); } if (vm == null) { Object functor = null; // could not find a method, try as a var if (bean == context) { int register = methodNode.getRegister(); if (register >= 0) { functor = registers[register]; } else { functor = context.get(methodName); } } else { JexlPropertyGet gfunctor = uberspect.getPropertyGet(bean, methodName, node); if (gfunctor != null) { functor = gfunctor.tryInvoke(bean, methodName); } } // script of jexl method will do if (functor instanceof Script) { return ((Script) functor).execute(context, argv.length > 0 ? argv : null); } else if (functor instanceof JexlMethod) { vm = (JexlMethod) functor; cacheable = false; } else if ( functor instanceof ScriptMethod ){ return ((ScriptMethod)functor).invoke(bean,this,argv); }else if ( functor instanceof Executable ){ return ((Executable)functor).execMethod(methodName,this,argv); } else { xjexl = new JexlException.Method(node, methodName); } } } if (xjexl == null) { // vm cannot be null if xjexl is null Object eval = vm.invoke(bean, argv); // cache executor in volatile JexlNode.value if (cacheable && vm.isCacheable()) { node.jjtSetValue(vm); } return eval; } } catch (InvocationTargetException e) { xjexl = new JexlException(node, "method invocation error : '" + methodName + "'", e.getCause()); } catch (JexlException.Return | JexlException.Cancel e) { throw e; } catch (Exception e) { xjexl = new JexlException(node, "method '" + methodName + "' in error", e); } finally { if (wasEventing) { curEventing.after(event); } } return invocationFailed(xjexl); } Eventing getEventing(Object object) { if (object instanceof Eventing) { return ((Eventing) object); } return Eventing.Timer.TIMER; } /** * {@inheritDoc} */ public Object visit(ASTMethodNode node, Object data) { isEventing = false; // the object to invoke the method on should be in the data argument if (data == null) { // if the method node is the first child of the (ASTReference) parent, // it is considered as calling a 'top level' function JexlNode firstChild = node.jjtGetParent().jjtGetChild(0); if (firstChild == node) { data = resolveNamespace(null, node); if (data == null) { data = context; } } else { // OK, we may have @@|$$ etc ... so? if (firstChild.image != null) { isEventing = Eventing.EVENTS.matcher(firstChild.image).matches(); if (isEventing) { eventingPattern = firstChild.image.substring(0, 2); String objName = firstChild.image.substring(2); data = context.get(objName); } } else { throw new JexlException(node, "attempting to call method on null"); } } } // objectNode 0 is the identifier (method name), the others are parameters. ASTIdentifier methodNode = (ASTIdentifier) node.jjtGetChild(0); return call(node, data, methodNode, 1); } /** * {@inheritDoc} */ public Object visit(ASTFunctionNode node, Object data) { // objectNode 0 is the prefix String prefix = node.jjtGetChild(0).image; Object namespace = resolveNamespace(prefix, node); // objectNode 1 is the identifier , the others are parameters. ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1); return call(node, namespace, functionNode, 2); } /** * {@inheritDoc} */ public Object visit(ASTConstructorNode node, Object data) { if (isCancelled()) { throw new JexlException.Cancel(node); } // first child is class or class name Object cobject = node.jjtGetChild(0).jjtAccept(this, data); // get the ctor args int argc = node.jjtGetNumChildren() - 1; Object[] argv = new Object[argc]; for (int i = 0; i < argc; i++) { argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null); } JexlException xjexl = null; try { // attempt to reuse last constructor cached in volatile JexlNode.value if (cache) { Object cached = node.jjtGetValue(); if (cached instanceof JexlMethod) { JexlMethod mctor = (JexlMethod) cached; Object eval = mctor.tryInvoke(null, cobject, argv); if (!mctor.tryFailed(eval)) { return eval; } } } if (cobject instanceof String) { ScriptClass scriptClass = resolveJexlClassName((String) cobject); if (scriptClass != null) { return scriptClass.instance(this, argv); } } if (cobject instanceof ScriptClass) { ScriptClass scriptClass = ((ScriptClass) cobject); return scriptClass.instance(this, argv); } JexlMethod ctor = uberspect.getConstructorMethod(cobject, argv, node); // DG: If we can't find an exact match, narrow the parameters and try again if (ctor == null) { if (arithmetic.narrowArguments(argv)) { ctor = uberspect.getConstructorMethod(cobject, argv, node); } if (ctor == null) { xjexl = new JexlException.Method(node, cobject.toString()); } } if (xjexl == null) { Object instance = ctor.invoke(cobject, argv); // cache executor in volatile JexlNode.value if (cache && ctor.isCacheable()) { node.jjtSetValue(ctor); } return instance; } } catch (InvocationTargetException e) { xjexl = new JexlException(node, "constructor invocation error", e.getCause()); } catch (Exception e) { xjexl = new JexlException(node, "constructor error", e); } return invocationFailed(xjexl); } /** * {@inheritDoc} */ public Object visit(ASTModNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.mod(left, right); } catch (ArithmeticException xrt) { if (!strict) { return new Double(0.0); } JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "% error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTPowNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.power(left, right); } catch (ArithmeticException xrt) { JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "** error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTMulNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.multiply(left, right); } catch (ArithmeticException xrt) { JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "* error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTNENode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE; } catch (ArithmeticException xrt) { JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "!= error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTNRNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); Object right = node.jjtGetChild(1).jjtAccept(this, data); try { if (right instanceof java.util.regex.Pattern || right instanceof String) { // use arithmetic / pattern matching return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE; } // try contains on collection if (right instanceof Set<?>) { return ((Set<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE; } // try contains on map key if (right instanceof Map<?, ?>) { return ((Map<?, ?>) right).containsKey(left) ? Boolean.FALSE : Boolean.TRUE; } // try contains on collection if (right instanceof Collection<?>) { return ((Collection<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE; } // try a contains method (duck type set) try { Object[] argv = {left}; JexlMethod vm = uberspect.getMethod(right, "contains", argv, node); if (vm != null) { return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE; } else if (arithmetic.narrowArguments(argv)) { vm = uberspect.getMethod(right, "contains", argv, node); if (vm != null) { return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE; } } } catch (InvocationTargetException e) { throw new JexlException(node, "!~ invocation error", e.getCause()); } catch (Exception e) { throw new JexlException(node, "!~ error", e); } // try iterative comparison Iterator<?> it = uberspect.getIterator(right, node.jjtGetChild(1)); if (it != null) { while (it.hasNext()) { Object next = it.next(); if (next == left || (next != null && next.equals(left))) { return Boolean.FALSE; } } return Boolean.TRUE; } // defaults to not equal return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE; } catch (ArithmeticException xrt) { throw new JexlException(node, "!~ error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTNotNode node, Object data) { Object val = node.jjtGetChild(0).jjtAccept(this, data); return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE; } /** * {@inheritDoc} */ public Object visit(ASTNullLiteral node, Object data) { return null; } /** * {@inheritDoc} */ public Object visit(ASTOrNode node, Object data) { Object left = node.jjtGetChild(0).jjtAccept(this, data); try { boolean leftValue = arithmetic.toBoolean(left); if (leftValue) { return Boolean.TRUE; } } catch (ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); } Object right = node.jjtGetChild(1).jjtAccept(this, data); try { boolean rightValue = arithmetic.toBoolean(right); if (rightValue) { return Boolean.TRUE; } } catch (ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); } return Boolean.FALSE; } /** * {@inheritDoc} */ public Object visit(ASTReference node, Object data) { // could be array access, identifier or map literal // followed by zero or more ("." and array access, method, size, // identifier or integer literal) int numChildren = node.jjtGetNumChildren(); // pass first piece of data in and loop through children Object result = null; StringBuilder variableName = null; String propertyName = null; boolean isVariable = true; int v = 0; for (int c = 0; c < numChildren; c++) { if (isCancelled()) { throw new JexlException.Cancel(node); } JexlNode theNode = node.jjtGetChild(c); // integer literals may be part of an antish var name only if no bean was found so far if (result == null && theNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) theNode).isInteger()) { isVariable &= v > 0; } else { isVariable &= (theNode instanceof ASTIdentifier); result = theNode.jjtAccept(this, result); } // if we get null back as a result, check for an ant variable if (result == null && isVariable) { if (v == 0) { variableName = new StringBuilder(node.jjtGetChild(0).image); v = 1; } for (; v <= c; ++v) { variableName.append('.'); variableName.append(node.jjtGetChild(v).image); } result = context.get(variableName.toString()); } else { propertyName = theNode.image; } } if (result == null) { if (isVariable && !isTernaryProtected(node) // variable unknow in context and not (from) a register && !(context.has(variableName.toString()) || (numChildren == 1 && node.jjtGetChild(0) instanceof ASTIdentifier && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) { JexlException xjexl = propertyName != null ? new JexlException.Property(node, propertyName) : new JexlException.Variable(node, variableName.toString()); return unknownVariable(xjexl); } } if (result instanceof Null) { return null; } return result; } /** * {@inheritDoc} * * @since 2.1 */ public Object visit(ASTReferenceExpression node, Object data) { ASTArrayAccess upper = node; return visit(upper, data); } /** * {@inheritDoc} * * @since 2.1 */ public Object visit(ASTReturnStatement node, Object data) { Object val = NULL; if (node.jjtGetNumChildren() > 0) { val = node.jjtGetChild(0).jjtAccept(this, data); } throw new JexlException.Return(node, null, val); } /** * Check if a null evaluated expression is protected by a ternary expression. * The rationale is that the ternary / elvis expressions are meant for the user to explictly take * control over the error generation; ie, ternaries can return null even if the engine in strict mode * would normally throw an exception. * * @param node the expression node * @return true if nullable variable, false otherwise */ private boolean isTernaryProtected(JexlNode node) { for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) { if (walk instanceof ASTTernaryNode) { return true; } else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) { break; } } return false; } /** * {@inheritDoc} */ public Object visit(ASTSizeFunction node, Object data) { Object val = node.jjtGetChild(0).jjtAccept(this, data); if (val == null) { return -1; // should be fine? } return Integer.valueOf(sizeOf(node, val)); } /** * {@inheritDoc} */ public Object visit(ASTSizeMethod node, Object data) { return Integer.valueOf(sizeOf(node, data)); } /** * {@inheritDoc} */ public Object visit(ASTStringLiteral node, Object data) { if (data != null) { return getAttribute(data, node.getLiteral(), node); } return node.image; } public static final Pattern CURRY_PATTERN = Pattern.compile("\\#\\{(?<expr>[^\\{^\\}]*)\\}", Pattern.MULTILINE); Script current; protected Object curry(String toBeCurried) { String ret = toBeCurried; Matcher m = CURRY_PATTERN.matcher(ret); while (m.find()) { String expression = m.group("expr"); try { Script curry = jexlEngine.createCopyScript(expression, current); Object o = curry.execute(context); //fancy to avoid null check -- 'null' comes String replaceWith = String.format("%s", o); String replaceThis = m.group(0); // because there can be strings using regex, so no regex replace ret = ret.replace(replaceThis, replaceWith); m = CURRY_PATTERN.matcher(ret); } catch (Exception e) { // return whatever we could... return ret; } } // now, in here, finally execute all .... try { Script curry = jexlEngine.createCopyScript(ret, current); Object c = curry.execute(context); return c; } catch (Exception e) { // return whatever got substituted as of now... return ret; } } /** * {@inheritDoc} */ public Object visit(ASTCurryingLiteral node, Object data) { return curry(node.image); } /** * {@inheritDoc} */ public Object visit(ASTTernaryNode node, Object data) { Object condition = node.jjtGetChild(0).jjtAccept(this, data); if (node.jjtGetNumChildren() == 3) { if (condition != null && arithmetic.toBoolean(condition)) { return node.jjtGetChild(1).jjtAccept(this, data); } else { return node.jjtGetChild(2).jjtAccept(this, data); } } if (condition != null && arithmetic.toBoolean(condition)) { return condition; } else { return node.jjtGetChild(1).jjtAccept(this, data); } } /** * {@inheritDoc} */ public Object visit(ASTNullCoalesce node, Object data) { Object condition = null ; try { condition = node.jjtGetChild(0).jjtAccept(this, data); }catch (Exception e){ // do nothing... } if (condition != null) { return condition; } else { return node.jjtGetChild(1).jjtAccept(this, data); } } /** * {@inheritDoc} */ public Object visit(ASTTrueNode node, Object data) { return Boolean.TRUE; } /** * {@inheritDoc} */ public Object visit(ASTUnarySizeNode node, Object data) { JexlNode valNode = node.jjtGetChild(0); Object val = valNode.jjtAccept(this, data); if (val == null) return 0; // null gets a modulus of 0 try { try { return sizeOf(node, val); } catch (Exception e) { if (arithmetic.compare(val, 0, ">=") < 0) { return arithmetic.negate(val); } else { return val; } } } catch (ArithmeticException xrt) { throw new JexlException(valNode, "arithmetic modulus error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTUnaryMinusNode node, Object data) { JexlNode valNode = node.jjtGetChild(0); Object val = valNode.jjtAccept(this, data); try { Object number = arithmetic.negate(val); // attempt to recoerce to literal class if (valNode instanceof ASTNumberLiteral && arithmetic.isNumberable(number)) { number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass()); } return number; } catch (ArithmeticException xrt) { throw new JexlException(valNode, "arithmetic error", xrt); } } /** * {@inheritDoc} */ public Object visit(ASTWhileStatement node, Object data) { Object result = null; /* first objectNode is the expression */ Node expressionNode = node.jjtGetChild(0); while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) { if (isCancelled()) { throw new JexlException.Cancel(node); } // execute statement if (node.jjtGetNumChildren() > 1) { try { result = node.jjtGetChild(1).jjtAccept(this, data); } catch (JexlException.Continue c) { result = c; continue; } catch (JexlException.Break b) { result = b; break; } } } return result; } /** * {@inheritDoc} */ public Object visit(ASTWhereStatement node, Object data) { /* first objectNode is the expression */ Node expressionNode = node.jjtGetChild(0); if (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) { if (isCancelled()) { throw new JexlException.Cancel(node); } // execute statement if (node.jjtGetNumChildren() > 1) { node.jjtGetChild(1).jjtAccept(this, data); } return true; } return false; } /** * Calculate the <code>size</code> of various types: Collection, Array, * Map, String, and anything that has a int size() method. * * @param node the node that gave the value to size * @param val the object to get the size of. * @return the size of val */ private int sizeOf(JexlNode node, Object val) { if (val instanceof Collection<?>) { return ((Collection<?>) val).size(); } else if (val.getClass().isArray()) { return Array.getLength(val); } else if (val instanceof Map<?, ?>) { return ((Map<?, ?>) val).size(); } else if (val instanceof String) { return ((String) val).length(); } else { // check if there is a size method on the object that returns an // integer and if so, just use it Object[] params = new Object[0]; JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node); if (vm != null && vm.getReturnType() == Integer.TYPE) { Integer result; try { result = (Integer) vm.invoke(val, params); } catch (Exception e) { throw new JexlException(node, "size() : error executing", e); } return result.intValue(); } throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null); } } /** * Gets an attribute of an object. * * @param object to retrieve value from * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or * key for a map * @return the attribute value */ public Object getAttribute(Object object, Object attribute) { return getAttribute(object, attribute, null); } /** * Shows that result was a valid null */ public static class Null { private Null(){ } } public static final Null NULL = new Null(); /** * Gets an attribute of an object. * * @param object to retrieve value from * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or * key for a map * @param node the node that evaluated as the object * @return the attribute value */ protected Object getAttribute(Object object, Object attribute, JexlNode node) { if (object == null) { throw new JexlException(node, "object is null"); } if (isCancelled()) { throw new JexlException.Cancel(node); } // attempt to reuse last executor cached in volatile JexlNode.value if (node != null && cache) { Object cached = node.jjtGetValue(); if (cached instanceof JexlPropertyGet) { JexlPropertyGet vg = (JexlPropertyGet) cached; Object value = vg.tryInvoke(object, attribute); if (!vg.tryFailed(value)) { return value; } } } JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node); if (vg != null) { try { Object value = null; Exception ex = null; try { value = vg.invoke(object); } catch (Exception e) { ex = e; } // before we do something check for once if the field value is that? if (value == null) { Field field = ((UberspectImpl) uberspect).getField(object, attribute.toString(), null); if (field != null) { JexlPropertyGet fg = new UberspectImpl.FieldPropertyGet(field); value = fg.invoke(object); vg = fg; if (value == null) { // the actual value of the property is null ! return NULL; } } else { if (ex == null) { // the actual value of the property is null! return NULL; } // null field, raise ex throw ex; } } // cache executor in volatile JexlNode.value if (node != null && cache && vg.isCacheable()) { node.jjtSetValue(vg); } return value; } catch (Exception xany) { if (node == null) { throw new RuntimeException(xany); } else { JexlException xjexl = new JexlException.Property(node, attribute.toString()); if (strict) { throw xjexl; } if (!silent) { logger.warn(xjexl.getMessage()); } } } } else { // Special casing for 'length' for Array. if (object.getClass().isArray() && attribute.equals("length")) { return Array.getLength(object); } // ok, is this a method with void input? JexlMethod jexlMethod = uberspect.getMethod(object, attribute.toString(), null, null); if (jexlMethod != null) { try { Object ret = jexlMethod.invoke(object, null); return ret; } catch (Exception e) { } } // is this a range stuff? if ( attribute instanceof RangeIterator ){ return ((RangeIterator)attribute).splice(object); } } return null; } /** * Sets an attribute of an object. * * @param object to set the value to * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or * key for a map * @param value the value to assign to the object's attribute */ public void setAttribute(Object object, Object attribute, Object value) { setAttribute(object, attribute, value, null); } /** * Sets an attribute of an object. * * @param object to set the value to * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or * key for a map * @param value the value to assign to the object's attribute * @param node the node that evaluated as the object */ protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) { if (isCancelled()) { throw new JexlException.Cancel(node); } // attempt to reuse last executor cached in volatile JexlNode.value if (node != null && cache) { Object cached = node.jjtGetValue(); if (cached instanceof JexlPropertySet) { JexlPropertySet setter = (JexlPropertySet) cached; Object eval = setter.tryInvoke(object, attribute, value); if (!setter.tryFailed(eval)) { return; } } } JexlException xjexl = null; JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node); // if we can't find an exact match, narrow the value argument and try again if (vs == null) { // replace all numbers with the smallest type that will fit Object[] narrow = {value}; if (arithmetic.narrowArguments(narrow)) { vs = uberspect.getPropertySet(object, attribute, narrow[0], node); } } if (vs != null) { try { // cache executor in volatile JexlNode.value vs.invoke(object, value); if (node != null && cache && vs.isCacheable()) { node.jjtSetValue(vs); } return; } catch (RuntimeException xrt) { if (node == null) { throw xrt; } xjexl = new JexlException(node, "set object property error", xrt); } catch (Exception xany) { if (node == null) { throw new RuntimeException(xany); } xjexl = new JexlException(node, "set object property error", xany); } }else{ Field field = ((UberspectImpl) uberspect).getField(object, attribute.toString(), null); if (field != null) { JexlPropertySet fs = new UberspectImpl.FieldPropertySet(field); try { value = fs.invoke(object, value); if (node != null && cache && fs.isCacheable()) { node.jjtSetValue(fs); } return; }catch (RuntimeException xrt) { if (node == null) { throw xrt; } xjexl = new JexlException(node, "set object property error", xrt); } catch (Exception xany){ if (node == null) { throw new RuntimeException(xany); } xjexl = new JexlException(node, "set object property error", xany); } } } if (xjexl == null) { if (node == null) { String error = "unable to set object property" + ", class: " + object.getClass().getName() + ", property: " + attribute + ", argument: " + value.getClass().getSimpleName(); throw new UnsupportedOperationException(error); } xjexl = new JexlException.Property(node, attribute.toString()); } if (strict) { throw xjexl; } if (!silent) { logger.warn(xjexl.getMessage()); } } /** * {@inheritDoc} */ @Override public Object visit(ASTCaseStatement node, Object data) { // it should never get called... return data; } /** * {@inheritDoc} */ @Override public Object visit(ASTMatchStatement node, Object data) { boolean itemExists = context.has(Script._ITEM_); Object old = context.get(Script._ITEM_); try { Object match = node.jjtGetChild(0).jjtAccept(this, data); context.set(Script._ITEM_, match); int n = node.jjtGetNumChildren(); for (int i = 1; i < n; i++) { JexlNode caseNode = node.jjtGetChild(i); Object expression = caseNode.jjtGetChild(0).jjtAccept(this, data); if (expression == null) continue; boolean bMatch = expression instanceof Boolean && (Boolean)expression ; if ( bMatch || Objects.equals( expression, match )){ return caseNode.jjtGetChild(1).jjtAccept(this,data); } } throw new UnsupportedOperationException("None of the cases matched!") ; }finally { // reset $ if ( itemExists ){ context.set(Script._ITEM_, old ); }else{ context.remove(Script._ITEM_); } } } /** * {@inheritDoc} */ public Object visit(ASTGoToStatement node, Object data) { boolean jump = true; if (node.jjtGetNumChildren() > 1) { Object o = node.jjtGetChild(1).jjtAccept(this, data); jump = TypeUtility.castBoolean(o, false); } if (!jump) return new JexlException.Jump(node); String label = node.jjtGetChild(0).image; // find ASTLabelledStatement --> and fire it... Map<String, Integer> jumps = current.jumps(); Integer loc = jumps.get(label); if (loc == null) { throw new JexlException(node, "Invalid Jump Label : " + label); } throw new JexlException.Jump(node, loc); } /** * {@inheritDoc} */ public Object visit(ASTLabelledStatement node, Object data) { return node.jjtGetChild(1).jjtAccept(this, data); } /** * {@inheritDoc} */ public Object visit(ASTAtomicStatement node, Object data) { JexlNode n = node.jjtGetChild(0); synchronized (n) { JexlContext old = context.copy() ; try { return n.jjtAccept(this, data); }catch (Throwable t){ // revert in case of error context = old ; return t; } } } /** * {@inheritDoc} */ public Object visit(ASTClockStatement node, Object data) { JexlNode n = node.jjtGetChild(0); Object ret = null; long start = System.nanoTime() ; long end ; try { ret = n.jjtAccept(this, data); end = System.nanoTime() ; }catch (Throwable t){ ret = t.getCause() ; if ( ret == null ){ ret = t ; } end = System.nanoTime() ; } Object[] results = new Object[]{ end - start, ret } ; return results ; } /** * Unused, satisfy ParserVisitor interface. * * @param node a node * @param data the data * @return does not return */ public Object visit(SimpleNode node, Object data) { throw new UnsupportedOperationException("Not supported yet."); } /** * Unused, should throw in Parser. * * @param node a node * @param data the data * @return does not return */ public Object visit(ASTAmbiguous node, Object data) { throw new UnsupportedOperationException("unexpected type of node"); } }