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

org.apache.commons.jexl3.internal.Interpreter Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
//CSOFF: FileLength
package org.apache.commons.jexl3.internal;

import java.util.Iterator;
import java.util.concurrent.Callable;

import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.JxltEngine;

import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;

import org.apache.commons.jexl3.parser.ASTAddNode;
import org.apache.commons.jexl3.parser.ASTAndNode;
import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
import org.apache.commons.jexl3.parser.ASTAnnotation;
import org.apache.commons.jexl3.parser.ASTArguments;
import org.apache.commons.jexl3.parser.ASTArrayAccess;
import org.apache.commons.jexl3.parser.ASTArrayLiteral;
import org.apache.commons.jexl3.parser.ASTAssignment;
import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
import org.apache.commons.jexl3.parser.ASTBlock;
import org.apache.commons.jexl3.parser.ASTBreak;
import org.apache.commons.jexl3.parser.ASTConstructorNode;
import org.apache.commons.jexl3.parser.ASTContinue;
import org.apache.commons.jexl3.parser.ASTDivNode;
import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
import org.apache.commons.jexl3.parser.ASTEQNode;
import org.apache.commons.jexl3.parser.ASTERNode;
import org.apache.commons.jexl3.parser.ASTEWNode;
import org.apache.commons.jexl3.parser.ASTEmptyFunction;
import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
import org.apache.commons.jexl3.parser.ASTFalseNode;
import org.apache.commons.jexl3.parser.ASTForeachStatement;
import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.ASTGENode;
import org.apache.commons.jexl3.parser.ASTGTNode;
import org.apache.commons.jexl3.parser.ASTIdentifier;
import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
import org.apache.commons.jexl3.parser.ASTIdentifierAccessJxlt;
import org.apache.commons.jexl3.parser.ASTIfStatement;
import org.apache.commons.jexl3.parser.ASTJexlLambda;
import org.apache.commons.jexl3.parser.ASTJexlScript;
import org.apache.commons.jexl3.parser.ASTJxltLiteral;
import org.apache.commons.jexl3.parser.ASTLENode;
import org.apache.commons.jexl3.parser.ASTLTNode;
import org.apache.commons.jexl3.parser.ASTMapEntry;
import org.apache.commons.jexl3.parser.ASTMapLiteral;
import org.apache.commons.jexl3.parser.ASTMethodNode;
import org.apache.commons.jexl3.parser.ASTModNode;
import org.apache.commons.jexl3.parser.ASTMulNode;
import org.apache.commons.jexl3.parser.ASTNENode;
import org.apache.commons.jexl3.parser.ASTNEWNode;
import org.apache.commons.jexl3.parser.ASTNRNode;
import org.apache.commons.jexl3.parser.ASTNSWNode;
import org.apache.commons.jexl3.parser.ASTNotNode;
import org.apache.commons.jexl3.parser.ASTNullLiteral;
import org.apache.commons.jexl3.parser.ASTNullpNode;
import org.apache.commons.jexl3.parser.ASTNumberLiteral;
import org.apache.commons.jexl3.parser.ASTOrNode;
import org.apache.commons.jexl3.parser.ASTRangeNode;
import org.apache.commons.jexl3.parser.ASTReference;
import org.apache.commons.jexl3.parser.ASTReferenceExpression;
import org.apache.commons.jexl3.parser.ASTRegexLiteral;
import org.apache.commons.jexl3.parser.ASTReturnStatement;
import org.apache.commons.jexl3.parser.ASTSWNode;
import org.apache.commons.jexl3.parser.ASTSetAddNode;
import org.apache.commons.jexl3.parser.ASTSetAndNode;
import org.apache.commons.jexl3.parser.ASTSetDivNode;
import org.apache.commons.jexl3.parser.ASTSetLiteral;
import org.apache.commons.jexl3.parser.ASTSetModNode;
import org.apache.commons.jexl3.parser.ASTSetMultNode;
import org.apache.commons.jexl3.parser.ASTSetOrNode;
import org.apache.commons.jexl3.parser.ASTSetSubNode;
import org.apache.commons.jexl3.parser.ASTSetXorNode;
import org.apache.commons.jexl3.parser.ASTSizeFunction;
import org.apache.commons.jexl3.parser.ASTStringLiteral;
import org.apache.commons.jexl3.parser.ASTSubNode;
import org.apache.commons.jexl3.parser.ASTTernaryNode;
import org.apache.commons.jexl3.parser.ASTTrueNode;
import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
import org.apache.commons.jexl3.parser.ASTVar;
import org.apache.commons.jexl3.parser.ASTWhileStatement;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.Node;

/**
 * An interpreter of JEXL syntax.
 *
 * @since 2.0
 */
public class Interpreter extends InterpreterBase {
    /** Frame height. */
    protected int fp = 0;
    /** Symbol values. */
    protected final Frame frame;
    /** Block micro-frames. */
    protected LexicalFrame block = null;

    /**
     * The thread local interpreter.
     */
    protected static final java.lang.ThreadLocal INTER =
                       new java.lang.ThreadLocal();

    /**
     * Creates an interpreter.
     * @param engine   the engine creating this interpreter
     * @param aContext the evaluation context, global variables, methods and functions
     * @param opts     the evaluation options, flags modifying evaluation behavior
     * @param eFrame   the evaluation frame, arguments and local variables
     */
    protected Interpreter(final Engine engine, final JexlOptions opts, final JexlContext aContext, final Frame eFrame) {
        super(engine, opts, aContext);
        this.frame = eFrame;
    }

    /**
     * Copy constructor.
     * @param ii  the interpreter to copy
     * @param jexla the arithmetic instance to use (or null)
     */
    protected Interpreter(final Interpreter ii, final JexlArithmetic jexla) {
        super(ii, jexla);
        frame = ii.frame;
        block = ii.block != null? new LexicalFrame(ii.block) : null;
    }

    /**
     * Swaps the current thread local interpreter.
     * @param inter the interpreter or null
     * @return the previous thread local interpreter
     */
    protected Interpreter putThreadInterpreter(final Interpreter inter) {
        final Interpreter pinter = INTER.get();
        INTER.set(inter);
        return pinter;
    }

    /**
     * Interpret the given script/expression.
     * 

* If the underlying JEXL engine is silent, errors will be logged through * its logger as warning. * @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(final JexlNode node) { JexlContext.ThreadLocal tcontext = null; JexlEngine tjexl = null; Interpreter tinter = null; try { tinter = putThreadInterpreter(this); if (tinter != null) { fp = tinter.fp + 1; } if (context instanceof JexlContext.ThreadLocal) { tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) context); } tjexl = jexl.putThreadEngine(jexl); if (fp > jexl.stackOverflow) { throw new JexlException.StackOverflow(node.jexlInfo(), "jexl (" + jexl.stackOverflow + ")", null); } cancelCheck(node); return node.jjtAccept(this, null); } catch(final StackOverflowError xstack) { final JexlException xjexl = new JexlException.StackOverflow(node.jexlInfo(), "jvm", xstack); if (!isSilent()) { throw xjexl.clean(); } if (logger.isWarnEnabled()) { logger.warn(xjexl.getMessage(), xjexl.getCause()); } } catch (final JexlException.Return xreturn) { return xreturn.getValue(); } catch (final JexlException.Cancel xcancel) { // cancelled |= Thread.interrupted(); cancelled.weakCompareAndSet(false, Thread.interrupted()); if (isCancellable()) { throw xcancel.clean(); } } catch (final JexlException xjexl) { if (!isSilent()) { throw xjexl.clean(); } if (logger.isWarnEnabled()) { logger.warn(xjexl.getMessage(), xjexl.getCause()); } } finally { synchronized(this) { if (functors != null) { for (final Object functor : functors.values()) { closeIfSupported(functor); } functors.clear(); functors = null; } } jexl.putThreadEngine(tjexl); if (context instanceof JexlContext.ThreadLocal) { jexl.putThreadLocal(tcontext); } if (tinter != null) { fp = tinter.fp - 1; } putThreadInterpreter(tinter); } return 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(final Object object, final Object attribute) { return getAttribute(object, attribute, 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(final Object object, final Object attribute, final Object value) { setAttribute(object, attribute, value, null); } @Override protected Object visit(final ASTAddNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.ADD, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.add(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "+ error", xrt); } } @Override protected Object visit(final ASTSubNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.subtract(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "- error", xrt); } } @Override protected Object visit(final ASTMulNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.multiply(left, right); } catch (final ArithmeticException xrt) { final JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "* error", xrt); } } @Override protected Object visit(final ASTDivNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.DIVIDE, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.divide(left, right); } catch (final ArithmeticException xrt) { if (!arithmetic.isStrict()) { return 0.0d; } final JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "/ error", xrt); } } @Override protected Object visit(final ASTModNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.MOD, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.mod(left, right); } catch (final ArithmeticException xrt) { if (!arithmetic.isStrict()) { return 0.0d; } final JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "% error", xrt); } } @Override protected Object visit(final ASTBitwiseAndNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.AND, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.and(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "& error", xrt); } } @Override protected Object visit(final ASTBitwiseOrNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.OR, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.or(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "| error", xrt); } } @Override protected Object visit(final ASTBitwiseXorNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.XOR, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.xor(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "^ error", xrt); } } @Override protected Object visit(final ASTEQNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.equals(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "== error", xrt); } } @Override protected Object visit(final ASTNENode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right); return result != JexlEngine.TRY_FAILED ? !arithmetic.toBoolean(result) : !arithmetic.equals(left, right); } catch (final ArithmeticException xrt) { final JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, "!= error", xrt); } } @Override protected Object visit(final ASTGENode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.GTE, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.greaterThanOrEqual(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, ">= error", xrt); } } @Override protected Object visit(final ASTGTNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.GT, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.greaterThan(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "> error", xrt); } } @Override protected Object visit(final ASTLENode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.LTE, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.lessThanOrEqual(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "<= error", xrt); } } @Override protected Object visit(final ASTLTNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.LT, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.lessThan(left, right); } catch (final ArithmeticException xrt) { throw new JexlException(node, "< error", xrt); } } @Override protected Object visit(final ASTSWNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return operators.startsWith(node, "^=", left, right); } @Override protected Object visit(final ASTNSWNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return !operators.startsWith(node, "^!", left, right); } @Override protected Object visit(final ASTEWNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return operators.endsWith(node, "$=", left, right); } @Override protected Object visit(final ASTNEWNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return !operators.endsWith(node, "$!", left, right); } @Override protected Object visit(final ASTERNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return operators.contains(node, "=~", right, left); } @Override protected Object visit(final ASTNRNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); return !operators.contains(node, "!~", right, left); } @Override protected Object visit(final ASTRangeNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { return arithmetic.createRange(left, right); } catch (final ArithmeticException xrt) { final JexlNode xnode = findNullOperand(xrt, node, left, right); throw new JexlException(xnode, ".. error", xrt); } } @Override protected Object visit(final ASTUnaryMinusNode node, final Object data) { // use cached value if literal final Object value = node.jjtGetValue(); if (value != null && !(value instanceof JexlMethod)) { return value; } final JexlNode valNode = node.jjtGetChild(0); final Object val = valNode.jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.NEGATE, val); if (result != JexlEngine.TRY_FAILED) { return result; } Object number = arithmetic.negate(val); // attempt to recoerce to literal class // cache if number literal and negate is idempotent if (number instanceof Number && valNode instanceof ASTNumberLiteral) { number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass()); if (arithmetic.isNegateStable()) { node.jjtSetValue(number); } } return number; } catch (final ArithmeticException xrt) { throw new JexlException(valNode, "- error", xrt); } } @Override protected Object visit(final ASTUnaryPlusNode node, final Object data) { // use cached value if literal final Object value = node.jjtGetValue(); if (value != null && !(value instanceof JexlMethod)) { return value; } final JexlNode valNode = node.jjtGetChild(0); final Object val = valNode.jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.POSITIVIZE, val); if (result != JexlEngine.TRY_FAILED) { return result; } final Object number = arithmetic.positivize(val); if (valNode instanceof ASTNumberLiteral && number instanceof Number && arithmetic.isPositivizeStable()) { node.jjtSetValue(number); } return number; } catch (final ArithmeticException xrt) { throw new JexlException(valNode, "- error", xrt); } } @Override protected Object visit(final ASTBitwiseComplNode node, final Object data) { final Object arg = node.jjtGetChild(0).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.COMPLEMENT, arg); return result != JexlEngine.TRY_FAILED ? result : arithmetic.complement(arg); } catch (final ArithmeticException xrt) { throw new JexlException(node, "~ error", xrt); } } @Override protected Object visit(final ASTNotNode node, final Object data) { final Object val = node.jjtGetChild(0).jjtAccept(this, data); try { final Object result = operators.tryOverload(node, JexlOperator.NOT, val); return result != JexlEngine.TRY_FAILED ? result : arithmetic.not(val); } catch (final ArithmeticException xrt) { throw new JexlException(node, "! error", xrt); } } @Override protected Object visit(final ASTIfStatement node, final Object data) { final int n = 0; final int numChildren = node.jjtGetNumChildren(); try { Object result = null; // pairs of { conditions , 'then' statement } for(int ifElse = 0; ifElse < (numChildren - 1); ifElse += 2) { final Object condition = node.jjtGetChild(ifElse).jjtAccept(this, null); if (arithmetic.toBoolean(condition)) { // first objectNode is true statement return node.jjtGetChild(ifElse + 1).jjtAccept(this, null); } } // if odd... if ((numChildren & 1) == 1) { // if there is an else, there are an odd number of children in the statement and it is the last child, // execute it. result = node.jjtGetChild(numChildren - 1).jjtAccept(this, null); } return result; } catch (final ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(n), "if error", xrt); } } @Override protected Object visit(final ASTVar node, final Object data) { final int symbol = node.getSymbol(); // if we have a var, we have a scope thus a frame if (!options.isLexical()) { if (frame.has(symbol)) { return frame.get(symbol); } } else if (!defineVariable(node, block)) { return redefinedVariable(node, node.getName()); } frame.set(symbol, null); return null; } @Override protected Object visit(final ASTBlock node, final Object data) { final int cnt = node.getSymbolCount(); if (!options.isLexical() || cnt <= 0) { return visitBlock(node, data); } try { block = new LexicalFrame(frame, block); return visitBlock(node, data); } finally { block = block.pop(); } } /** * Base visitation for blocks. * @param node the block * @param data the usual data * @return the result of the last expression evaluation */ private Object visitBlock(final ASTBlock node, final Object data) { final int numChildren = node.jjtGetNumChildren(); Object result = null; for (int i = 0; i < numChildren; i++) { cancelCheck(node); result = node.jjtGetChild(i).jjtAccept(this, data); } return result; } @Override protected Object visit(final ASTReturnStatement node, final Object data) { final Object val = node.jjtGetChild(0).jjtAccept(this, data); cancelCheck(node); throw new JexlException.Return(node, null, val); } @Override protected Object visit(final ASTContinue node, final Object data) { throw new JexlException.Continue(node); } @Override protected Object visit(final ASTBreak node, final Object data) { throw new JexlException.Break(node); } @Override protected Object visit(final ASTForeachStatement node, final Object data) { Object result = null; /* first objectNode is the loop variable */ final ASTReference loopReference = (ASTReference) node.jjtGetChild(0); final ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0); final int symbol = loopVariable.getSymbol(); final boolean lexical = options.isLexical();// && node.getSymbolCount() > 0; final LexicalFrame locals = lexical? new LexicalFrame(frame, block) : null; final boolean loopSymbol = symbol >= 0 && loopVariable instanceof ASTVar; if (lexical) { // create lexical frame // it may be a local previously declared if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) { return redefinedVariable(node, loopVariable.getName()); } block = locals; } Object forEach = null; try { /* second objectNode is the variable to iterate */ final Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data); // make sure there is a value to iterate upon if (iterableValue != null) { /* third objectNode is the statement to execute */ final JexlNode statement = node.jjtGetNumChildren() >= 3 ? node.jjtGetChild(2) : null; // get an iterator for the collection/array etc via the introspector. forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue); final Iterator itemsIterator = forEach instanceof Iterator ? (Iterator) forEach : uberspect.getIterator(iterableValue); if (itemsIterator != null) { int cnt = 0; while (itemsIterator.hasNext()) { cancelCheck(node); // reset loop variable if (lexical && cnt++ > 0) { // clean up but remain current block.pop(); // unlikely to fail if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) { return redefinedVariable(node, loopVariable.getName()); } } // set loopVariable to value of iterator final Object value = itemsIterator.next(); if (symbol < 0) { setContextVariable(node, loopVariable.getName(), value); } else { frame.set(symbol, value); } if (statement != null) { try { // execute statement result = statement.jjtAccept(this, data); } catch (final JexlException.Break stmtBreak) { break; } catch (final JexlException.Continue stmtContinue) { //continue; } } } } } } finally { // closeable iterator handling closeIfSupported(forEach); // restore lexical frame if (lexical) { block = block.pop(); } } return result; } @Override protected Object visit(final ASTWhileStatement node, final Object data) { Object result = null; /* first objectNode is the condition */ final Node condition = node.jjtGetChild(0); while (arithmetic.toBoolean(condition.jjtAccept(this, data))) { cancelCheck(node); if (node.jjtGetNumChildren() > 1) { try { // execute statement result = node.jjtGetChild(1).jjtAccept(this, data); } catch (final JexlException.Break stmtBreak) { break; } catch (final JexlException.Continue stmtContinue) { //continue; } } } return result; } @Override protected Object visit(final ASTDoWhileStatement node, final Object data) { Object result = null; final int nc = node.jjtGetNumChildren(); /* last objectNode is the condition */ final Node condition = node.jjtGetChild(nc - 1); do { cancelCheck(node); if (nc > 1) { try { // execute statement result = node.jjtGetChild(0).jjtAccept(this, data); } catch (final JexlException.Break stmtBreak) { break; } catch (final JexlException.Continue stmtContinue) { //continue; } } } while (arithmetic.toBoolean(condition.jjtAccept(this, data))); return result; } @Override protected Object visit(final ASTAndNode node, final 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. */ final Object left = node.jjtGetChild(0).jjtAccept(this, data); try { final boolean leftValue = arithmetic.toBoolean(left); if (!leftValue) { return Boolean.FALSE; } } catch (final ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); } final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final boolean rightValue = arithmetic.toBoolean(right); if (!rightValue) { return Boolean.FALSE; } } catch (final ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); } return Boolean.TRUE; } @Override protected Object visit(final ASTOrNode node, final Object data) { final Object left = node.jjtGetChild(0).jjtAccept(this, data); try { final boolean leftValue = arithmetic.toBoolean(left); if (leftValue) { return Boolean.TRUE; } } catch (final ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); } final Object right = node.jjtGetChild(1).jjtAccept(this, data); try { final boolean rightValue = arithmetic.toBoolean(right); if (rightValue) { return Boolean.TRUE; } } catch (final ArithmeticException xrt) { throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); } return Boolean.FALSE; } @Override protected Object visit(final ASTNullLiteral node, final Object data) { return null; } @Override protected Object visit(final ASTTrueNode node, final Object data) { return Boolean.TRUE; } @Override protected Object visit(final ASTFalseNode node, final Object data) { return Boolean.FALSE; } @Override protected Object visit(final ASTNumberLiteral node, final Object data) { if (data != null && node.isInteger()) { return getAttribute(data, node.getLiteral(), node); } return node.getLiteral(); } @Override protected Object visit(final ASTStringLiteral node, final Object data) { if (data != null) { return getAttribute(data, node.getLiteral(), node); } return node.getLiteral(); } @Override protected Object visit(final ASTRegexLiteral node, final Object data) { return node.getLiteral(); } @Override protected Object visit(final ASTArrayLiteral node, final Object data) { final int childCount = node.jjtGetNumChildren(); final JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount); boolean extended = false; for (int i = 0; i < childCount; i++) { cancelCheck(node); final JexlNode child = node.jjtGetChild(i); if (child instanceof ASTExtendedLiteral) { extended = true; } else { final Object entry = node.jjtGetChild(i).jjtAccept(this, data); ab.add(entry); } } return ab.create(extended); } @Override protected Object visit(final ASTExtendedLiteral node, final Object data) { return node; } @Override protected Object visit(final ASTSetLiteral node, final Object data) { final int childCount = node.jjtGetNumChildren(); final JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount); for (int i = 0; i < childCount; i++) { cancelCheck(node); final Object entry = node.jjtGetChild(i).jjtAccept(this, data); mb.add(entry); } return mb.create(); } @Override protected Object visit(final ASTMapLiteral node, final Object data) { final int childCount = node.jjtGetNumChildren(); final JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount); for (int i = 0; i < childCount; i++) { cancelCheck(node); final Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data); mb.put(entry[0], entry[1]); } return mb.create(); } @Override protected Object visit(final ASTMapEntry node, final Object data) { final Object key = node.jjtGetChild(0).jjtAccept(this, data); final Object value = node.jjtGetChild(1).jjtAccept(this, data); return new Object[]{key, value}; } @Override protected Object visit(final ASTTernaryNode node, final Object data) { Object condition; try { condition = node.jjtGetChild(0).jjtAccept(this, data); } catch(final JexlException xany) { if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) { throw xany; } condition = null; } // ternary as in "x ? y : z" if (node.jjtGetNumChildren() == 3) { if (condition != null && arithmetic.toBoolean(condition)) { return node.jjtGetChild(1).jjtAccept(this, data); } return node.jjtGetChild(2).jjtAccept(this, data); } // elvis as in "x ?: z" if (condition != null && arithmetic.toBoolean(condition)) { return condition; } return node.jjtGetChild(1).jjtAccept(this, data); } @Override protected Object visit(final ASTNullpNode node, final Object data) { Object lhs; try { lhs = node.jjtGetChild(0).jjtAccept(this, data); } catch(final JexlException xany) { if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) { throw xany; } lhs = null; } // null elision as in "x ?? z" return lhs != null? lhs : node.jjtGetChild(1).jjtAccept(this, data); } @Override protected Object visit(final ASTSizeFunction node, final Object data) { try { final Object val = node.jjtGetChild(0).jjtAccept(this, data); return operators.size(node, val); } catch(final JexlException xany) { return 0; } } @Override protected Object visit(final ASTEmptyFunction node, final Object data) { try { final Object value = node.jjtGetChild(0).jjtAccept(this, data); return operators.empty(node, value); } catch(final JexlException xany) { return true; } } /** * Runs a node. * @param node the node * @param data the usual data * @return the return value */ protected Object visitLexicalNode(final JexlNode node, final Object data) { block = new LexicalFrame(frame, null); try { return node.jjtAccept(this, data); } finally { block = block.pop(); } } /** * Runs a closure. * @param closure the closure * @param data the usual data * @return the closure return value */ protected Object runClosure(final Closure closure, final Object data) { final ASTJexlScript script = closure.getScript(); block = new LexicalFrame(frame, block).defineArgs(); try { final JexlNode body = script.jjtGetChild(script.jjtGetNumChildren() - 1); return interpret(body); } finally { block = block.pop(); } } @Override protected Object visit(final ASTJexlScript script, final Object data) { if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) { return new Closure(this, (ASTJexlLambda) script); } block = new LexicalFrame(frame, block).defineArgs(); try { final int numChildren = script.jjtGetNumChildren(); Object result = null; for (int i = 0; i < numChildren; i++) { final JexlNode child = script.jjtGetChild(i); result = child.jjtAccept(this, data); cancelCheck(child); } return result; } finally { block = block.pop(); } } @Override protected Object visit(final ASTReferenceExpression node, final Object data) { return node.jjtGetChild(0).jjtAccept(this, data); } @Override protected Object visit(final ASTIdentifier identifier, final Object data) { cancelCheck(identifier); return data != null ? getAttribute(data, identifier.getName(), identifier) : getVariable(frame, block, identifier); } @Override protected Object visit(final ASTArrayAccess node, final Object data) { // first objectNode is the identifier Object object = data; // can have multiple nodes - either an expression, integer literal or reference final int numChildren = node.jjtGetNumChildren(); for (int i = 0; i < numChildren; i++) { final JexlNode nindex = node.jjtGetChild(i); if (object == null) { return unsolvableProperty(nindex, stringifyProperty(nindex), false, null); } final Object index = nindex.jjtAccept(this, null); cancelCheck(node); object = getAttribute(object, index, nindex); } return object; } /** * Evaluates an access identifier based on the 2 main implementations; * static (name or numbered identifier) or dynamic (jxlt). * @param node the identifier access node * @return the evaluated identifier */ private Object evalIdentifier(final ASTIdentifierAccess node) { if (!(node instanceof ASTIdentifierAccessJxlt)) { return node.getIdentifier(); } final ASTIdentifierAccessJxlt accessJxlt = (ASTIdentifierAccessJxlt) node; final String src = node.getName(); Throwable cause = null; TemplateEngine.TemplateExpression expr = (TemplateEngine.TemplateExpression) accessJxlt.getExpression(); try { if (expr == null) { final TemplateEngine jxlt = jexl.jxlt(); expr = jxlt.parseExpression(node.jexlInfo(), src, frame != null ? frame.getScope() : null); accessJxlt.setExpression(expr); } if (expr != null) { final Object name = expr.evaluate(frame, context); if (name != null) { final Integer id = ASTIdentifierAccess.parseIdentifier(name.toString()); return id != null ? id : name; } } } catch (final JxltEngine.Exception xjxlt) { cause = xjxlt; } return node.isSafe() ? null : unsolvableProperty(node, src, true, cause); } @Override protected Object visit(final ASTIdentifierAccess node, final Object data) { if (data == null) { return null; } final Object id = evalIdentifier(node); return getAttribute(data, id, node); } @Override protected Object visit(final ASTReference node, final Object data) { cancelCheck(node); final int numChildren = node.jjtGetNumChildren(); final JexlNode parent = node.jjtGetParent(); // pass first piece of data in and loop through children Object object = null; JexlNode objectNode = null; JexlNode ptyNode = null; StringBuilder ant = null; boolean antish = !(parent instanceof ASTReference); int v = 1; main: for (int c = 0; c < numChildren; c++) { objectNode = node.jjtGetChild(c); if (objectNode instanceof ASTMethodNode) { antish = false; if (object == null) { // we may be performing a method call on an antish var if (ant != null) { final JexlNode child = objectNode.jjtGetChild(0); if (child instanceof ASTIdentifierAccess) { final int alen = ant.length(); ant.append('.'); ant.append(((ASTIdentifierAccess) child).getName()); object = context.get(ant.toString()); if (object != null) { object = visit((ASTMethodNode) objectNode, object, context); continue; } // remove method name from antish ant.delete(alen, ant.length()); ptyNode = objectNode; } } break; } } else if (objectNode instanceof ASTArrayAccess) { antish = false; if (object == null) { ptyNode = objectNode; break; } } // attempt to evaluate the property within the object (visit(ASTIdentifierAccess node)) object = objectNode.jjtAccept(this, object); cancelCheck(node); if (object != null) { // disallow mixing antish variable & bean with same root; avoid ambiguity antish = false; } else if (antish) { // create first from first node if (ant == null) { // if we still have a null object, check for an antish variable final JexlNode first = node.jjtGetChild(0); if (!(first instanceof ASTIdentifier)) { // not an identifier, not antish ptyNode = objectNode; break main; } final ASTIdentifier afirst = (ASTIdentifier) first; ant = new StringBuilder(afirst.getName()); // skip the else...* // *... and continue if (!options.isAntish()) { antish = false; continue; } // skip the first node case since it was trialed in jjtAccept above and returned null if (c == 0) { continue; } } // catch up to current node for (; v <= c; ++v) { final JexlNode child = node.jjtGetChild(v); if (!(child instanceof ASTIdentifierAccess)) { // not an identifier, not antish ptyNode = objectNode; break main; } final ASTIdentifierAccess achild = (ASTIdentifierAccess) child; if (achild.isSafe() || achild.isExpression()) { break main; } ant.append('.'); ant.append(achild.getName()); } // solve antish object = context.get(ant.toString()); } else if (c != numChildren - 1) { // only the last one may be null ptyNode = objectNode; break; // } } // dealing with null if (object == null) { if (ptyNode != null) { if (ptyNode.isSafeLhs(isSafe())) { return null; } if (ant != null) { final String aname = ant.toString(); final boolean defined = isVariableDefined(frame, block, aname); return unsolvableVariable(node, aname, !defined); } return unsolvableProperty(node, stringifyProperty(ptyNode), ptyNode == objectNode, null); } if (antish) { if (node.isSafeLhs(isSafe())) { return null; } final String aname = ant != null ? ant.toString() : "?"; final boolean defined = isVariableDefined(frame, block, aname); // defined but null; arg of a strict operator? if (defined && (!arithmetic.isStrict() || !node.jjtGetParent().isStrictOperator())) { return null; } return unsolvableVariable(node, aname, !defined); } } return object; } @Override protected Object visit(final ASTAssignment node, final Object data) { return executeAssign(node, null, data); } @Override protected Object visit(final ASTSetAddNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_ADD, data); } @Override protected Object visit(final ASTSetSubNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_SUBTRACT, data); } @Override protected Object visit(final ASTSetMultNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_MULTIPLY, data); } @Override protected Object visit(final ASTSetDivNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_DIVIDE, data); } @Override protected Object visit(final ASTSetModNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_MOD, data); } @Override protected Object visit(final ASTSetAndNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_AND, data); } @Override protected Object visit(final ASTSetOrNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_OR, data); } @Override protected Object visit(final ASTSetXorNode node, final Object data) { return executeAssign(node, JexlOperator.SELF_XOR, data); } /** * Executes an assignment with an optional side-effect operator. * @param node the node * @param assignop the assignment operator or null if simply assignment * @param data the data * @return the left hand side */ protected Object executeAssign(final JexlNode node, final JexlOperator assignop, final Object data) { // CSOFF: MethodLength cancelCheck(node); // left contains the reference to assign to final JexlNode left = node.jjtGetChild(0); ASTIdentifier var = null; Object object = null; int symbol = -1; // check var decl with assign is ok if (left instanceof ASTIdentifier) { var = (ASTIdentifier) left; symbol = var.getSymbol(); if (symbol >= 0 && options.isLexical()) { if (var instanceof ASTVar) { if (!defineVariable((ASTVar) var, block)) { return redefinedVariable(var, var.getName()); } } else if (options.isLexicalShade() && var.isShaded()) { return undefinedVariable(var, var.getName()); } } } boolean antish = options.isAntish(); // 0: determine initial object & property: final int last = left.jjtGetNumChildren() - 1; // right is the value expression to assign Object right = node.jjtGetChild(1).jjtAccept(this, data); // a (var?) v = ... expression if (var != null) { if (symbol >= 0) { // check we are not assigning a symbol itself if (last < 0) { if (assignop != null) { final Object self = getVariable(frame, block, var); right = operators.tryAssignOverload(node, assignop, self, right); if (right == JexlOperator.ASSIGN) { return self; } } frame.set(symbol, right); // make the closure accessible to itself, ie capture the currently set variable after frame creation if (right instanceof Closure) { ((Closure) right).setCaptured(symbol, right); } return right; // 1 } object = getVariable(frame, block, var); // top level is a symbol, can not be an antish var antish = false; } else { // check we are not assigning direct global if (last < 0) { if (assignop != null) { final Object self = context.get(var.getName()); right = operators.tryAssignOverload(node, assignop, self, right); if (right == JexlOperator.ASSIGN) { return self; } } setContextVariable(node, var.getName(), right); return right; // 2 } object = context.get(var.getName()); // top level accesses object, can not be an antish var if (object != null) { antish = false; } } } else if (!(left instanceof ASTReference)) { throw new JexlException(left, "illegal assignment form 0"); } // 1: follow children till penultimate, resolve dot/array JexlNode objectNode = null; StringBuilder ant = null; int v = 1; // start at 1 if symbol main: for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) { objectNode = left.jjtGetChild(c); object = objectNode.jjtAccept(this, object); if (object != null) { // disallow mixing antish variable & bean with same root; avoid ambiguity antish = false; } else if (antish) { // initialize if first time if (ant == null) { final JexlNode first = left.jjtGetChild(0); final ASTIdentifier firstId = first instanceof ASTIdentifier ? (ASTIdentifier) first : null; if ((firstId == null) || (firstId.getSymbol() >= 0)) { // ant remains null, object is null, stop solving antish = false; break main; } ant = new StringBuilder(firstId.getName()); } // catch up to current child for (; v <= c; ++v) { final JexlNode child = left.jjtGetChild(v); final ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess ? (ASTIdentifierAccess) child : null; // remain antish only if unsafe navigation if ((aid == null) || aid.isSafe() || aid.isExpression()) { antish = false; break main; } ant.append('.'); ant.append(aid.getName()); } // solve antish object = context.get(ant.toString()); } else { throw new JexlException(objectNode, "illegal assignment form"); } } // 2: last objectNode will perform assignement in all cases Object property = null; JexlNode propertyNode = left.jjtGetChild(last); final ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess ? (ASTIdentifierAccess) propertyNode : null; if (propertyId != null) { // deal with creating/assignining antish variable if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) { if (last > 0) { ant.append('.'); } ant.append(propertyId.getName()); if (assignop != null) { final Object self = context.get(ant.toString()); right = operators.tryAssignOverload(node, assignop, self, right); if (right == JexlOperator.ASSIGN) { return self; } } setContextVariable(propertyNode, ant.toString(), right); return right; // 3 } // property of an object ? property = evalIdentifier(propertyId); } else if (propertyNode instanceof ASTArrayAccess) { // can have multiple nodes - either an expression, integer literal or reference final int numChildren = propertyNode.jjtGetNumChildren() - 1; for (int i = 0; i < numChildren; i++) { final JexlNode nindex = propertyNode.jjtGetChild(i); final Object index = nindex.jjtAccept(this, null); object = getAttribute(object, index, nindex); } propertyNode = propertyNode.jjtGetChild(numChildren); property = propertyNode.jjtAccept(this, null); } else { throw new JexlException(objectNode, "illegal assignment form"); } // we may have a null property as in map[null], no check needed. // we can not *have* a null object though. if (object == null) { // no object, we fail return unsolvableProperty(objectNode, ".", true, null); } // 3: one before last, assign if (assignop != null) { final Object self = getAttribute(object, property, propertyNode); right = operators.tryAssignOverload(node, assignop, self, right); if (right == JexlOperator.ASSIGN) { return self; } } setAttribute(object, property, right, propertyNode); return right; // 4 } @Override protected Object[] visit(final ASTArguments node, final Object data) { final int argc = node.jjtGetNumChildren(); final Object[] argv = new Object[argc]; for (int i = 0; i < argc; i++) { argv[i] = node.jjtGetChild(i).jjtAccept(this, data); } return argv; } @Override protected Object visit(final ASTMethodNode node, final Object data) { return visit(node, null, data); } /** * Execute a method call, ie syntactically written as name.call(...). * @param node the actual method call node * @param object non null when name.call is an antish variable * @param data the context * @return the method call result */ private Object visit(final ASTMethodNode node, Object object, final Object data) { // left contains the reference to the method final JexlNode methodNode = node.jjtGetChild(0); Object method; // 1: determine object and method or functor if (methodNode instanceof ASTIdentifierAccess) { method = methodNode; if (object == null) { object = data; if (object == null) { // no object, we fail return node.isSafeLhs(isSafe()) ? null : unsolvableMethod(methodNode, ".(...)"); } } else { // edge case of antish var used as functor method = object; } } else { method = methodNode.jjtAccept(this, data); } Object result = method; for (int a = 1; a < node.jjtGetNumChildren(); ++a) { if (result == null) { // no method, we fail// variable unknown in context and not a local return node.isSafeLhs(isSafe()) ? null : unsolvableMethod(methodNode, ".(...)"); } final ASTArguments argNode = (ASTArguments) node.jjtGetChild(a); result = call(node, object, result, argNode); object = result; } return result; } @Override protected Object visit(final ASTFunctionNode node, final Object data) { final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0); final String nsid = functionNode.getNamespace(); final Object namespace = (nsid != null)? resolveNamespace(nsid, node) : context; final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1); return call(node, namespace, functionNode, argNode); } /** * Calls a method (or function). *

* Method resolution is a follows: * 1 - attempt to find a method in the target passed as parameter; * 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target; * 3 - if this fails, narrow the arguments and try again 1 * 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument; *

* *duck-callable: an object where a "call" function exists * * @param node the method node * @param target the target of the method, what it should be invoked upon * @param functor the object carrying the method or function or the method identifier * @param argNode the node carrying the arguments * @return the result of the method invocation */ protected Object call(final JexlNode node, final Object target, Object functor, final ASTArguments argNode) { cancelCheck(node); // evaluate the arguments final Object[] argv = visit(argNode, null); // get the method name if identifier final int symbol; final String methodName; boolean cacheable = cache; boolean isavar = false; if (functor instanceof ASTIdentifier) { // function call, target is context or namespace (if there was one) final ASTIdentifier methodIdentifier = (ASTIdentifier) functor; symbol = methodIdentifier.getSymbol(); methodName = methodIdentifier.getName(); functor = null; // is it a global or local variable ? if (target == context) { if (frame != null && frame.has(symbol)) { functor = frame.get(symbol); isavar = functor != null; } else if (context.has(methodName)) { functor = context.get(methodName); isavar = functor != null; } // name is a variable, can't be cached cacheable &= !isavar; } } else if (functor instanceof ASTIdentifierAccess) { // a method call on target methodName = ((ASTIdentifierAccess) functor).getName(); symbol = -1; functor = null; cacheable = true; } else if (functor != null) { // ...(x)(y) symbol = -1 - 1; // -2; methodName = null; cacheable = false; } else if (!node.isSafeLhs(isSafe())) { return unsolvableMethod(node, "?(...)"); } else { // safe lhs return null; } // solving the call site final CallDispatcher call = new CallDispatcher(node, cacheable); try { // do we have a cached version method/function name ? final Object eval = call.tryEval(target, methodName, argv); if (JexlEngine.TRY_FAILED != eval) { return eval; } boolean functorp = false; boolean narrow = false; // pseudo loop to try acquiring methods without and with argument narrowing while (true) { call.narrow = narrow; // direct function or method call if (functor == null || functorp) { // try a method or function from context if (call.isTargetMethod(target, methodName, argv)) { return call.eval(methodName); } if (target == context) { // solve 'null' namespace final Object namespace = resolveNamespace(null, node); if (namespace != null && namespace != context && call.isTargetMethod(namespace, methodName, argv)) { return call.eval(methodName); } // do not try context function since this was attempted // 10 lines above...; solve as an arithmetic function if (call.isArithmeticMethod(methodName, argv)) { return call.eval(methodName); } // could not find a method, try as a property of a non-context target (performed once) } else { // try prepending target to arguments and look for // applicable method in context... final Object[] pargv = functionArguments(target, narrow, argv); if (call.isContextMethod(methodName, pargv)) { return call.eval(methodName); } // ...or arithmetic if (call.isArithmeticMethod(methodName, pargv)) { return call.eval(methodName); } // the method may also be a functor stored in a property of the target if (!narrow) { final JexlPropertyGet get = uberspect.getPropertyGet(target, methodName); if (get != null) { functor = get.tryInvoke(target, methodName); functorp = functor != null; } } } } // this may happen without the above when we are chaining call like x(a)(b) // or when a var/symbol or antish var is used as a "function" name if (functor != null) { // lambda, script or jexl method will do if (functor instanceof JexlScript) { return ((JexlScript) functor).execute(context, argv); } if (functor instanceof JexlMethod) { return ((JexlMethod) functor).invoke(target, argv); } final String mCALL = "call"; // may be a generic callable, try a 'call' method if (call.isTargetMethod(functor, mCALL, argv)) { return call.eval(mCALL); } // functor is a var, may be method is a global one ? if (isavar && target == context) { if (call.isContextMethod(methodName, argv)) { return call.eval(methodName); } if (call.isArithmeticMethod(methodName, argv)) { return call.eval(methodName); } } // try prepending functor to arguments and look for // context or arithmetic function called 'call' final Object[] pargv = functionArguments(functor, narrow, argv); if (call.isContextMethod(mCALL, pargv)) { return call.eval(mCALL); } if (call.isArithmeticMethod(mCALL, pargv)) { return call.eval(mCALL); } } // if we did not find an exact method by name and we haven't tried yet, // attempt to narrow the parameters and if this succeeds, try again in next loop if (narrow || !arithmetic.narrowArguments(argv)) { break; } narrow = true; // continue; } } catch (JexlException.Method xmethod) { // ignore and handle at end; treat as an inner discover that fails } catch (final JexlException.TryFailed xany) { throw invocationException(node, methodName, xany); } catch (final JexlException xthru) { throw xthru; } catch (final Exception xany) { throw invocationException(node, methodName, xany); } // we have either evaluated and returned or no method was found return node.isSafeLhs(isSafe()) ? null : unsolvableMethod(node, methodName, argv); } @Override protected Object visit(final ASTConstructorNode node, final Object data) { if (isCancelled()) { throw new JexlException.Cancel(node); } // first child is class or class name final Object target = node.jjtGetChild(0).jjtAccept(this, data); // get the ctor args final 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, data); } try { final boolean cacheable = cache; // attempt to reuse last funcall cached in volatile JexlNode.value if (cacheable) { final Object cached = node.jjtGetValue(); if (cached instanceof Funcall) { final Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv); if (JexlEngine.TRY_FAILED != eval) { return eval; } } } boolean narrow = false; JexlMethod ctor = null; Funcall funcall = null; while (true) { // try as stated ctor = uberspect.getConstructor(target, argv); if (ctor != null) { if (cacheable && ctor.isCacheable()) { funcall = new Funcall(ctor, narrow); } break; } // try with prepending context as first argument final Object[] nargv = callArguments(context, narrow, argv); ctor = uberspect.getConstructor(target, nargv); if (ctor != null) { if (cacheable && ctor.isCacheable()) { funcall = new ContextualCtor(ctor, narrow); } argv = nargv; break; } // if we did not find an exact method by name and we haven't tried yet, // attempt to narrow the parameters and if this succeeds, try again in next loop if (!narrow && arithmetic.narrowArguments(argv)) { narrow = true; continue; } // we are done trying break; } // we have either evaluated and returned or might have found a ctor if (ctor != null) { final Object eval = ctor.invoke(target, argv); // cache executor in volatile JexlNode.value if (funcall != null) { node.jjtSetValue(funcall); } return eval; } final String tstr = target != null ? target.toString() : "?"; return unsolvableMethod(node, tstr, argv); } catch (final JexlException.Method xmethod) { throw xmethod; } catch (final Exception xany) { final String tstr = target != null ? target.toString() : "?"; throw invocationException(node, tstr, xany); } } @Override protected Object visit(final ASTJxltLiteral node, final Object data) { TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue(); if (tp == null) { final TemplateEngine jxlt = jexl.jxlt(); JexlInfo info = node.jexlInfo(); if (this.block != null) { info = new JexlNode.Info(node, info); } tp = jxlt.parseExpression(info, node.getLiteral(), frame != null ? frame.getScope() : null); node.jjtSetValue(tp); } if (tp != null) { return tp.evaluate(frame, context); } return null; } @Override protected Object visit(final ASTAnnotation node, final Object data) { throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported."); } @Override protected Object visit(final ASTAnnotatedStatement node, final Object data) { return processAnnotation(node, 0, data); } /** * An annotated call. */ public class AnnotatedCall implements Callable { /** The statement. */ private final ASTAnnotatedStatement stmt; /** The child index. */ private final int index; /** The data. */ private final Object data; /** Tracking whether we processed the annotation. */ private boolean processed = false; /** * Simple ctor. * @param astmt the statement * @param aindex the index * @param adata the data */ AnnotatedCall(final ASTAnnotatedStatement astmt, final int aindex, final Object adata) { stmt = astmt; index = aindex; data = adata; } @Override public Object call() throws Exception { processed = true; try { return processAnnotation(stmt, index, data); } catch (JexlException.Return | JexlException.Break | JexlException.Continue xreturn) { return xreturn; } } /** * @return whether the statement has been processed */ public boolean isProcessed() { return processed; } /** * @return the actual statement. */ public Object getStatement() { return stmt; } } /** * Processes an annotated statement. * @param stmt the statement * @param index the index of the current annotation being processed * @param data the contextual data * @return the result of the statement block evaluation */ protected Object processAnnotation(final ASTAnnotatedStatement stmt, final int index, final Object data) { // are we evaluating the block ? final int last = stmt.jjtGetNumChildren() - 1; if (index == last) { final JexlNode cblock = stmt.jjtGetChild(last); // if the context has changed, might need a new interpreter final JexlArithmetic jexla = arithmetic.options(context); if (jexla == arithmetic) { return cblock.jjtAccept(Interpreter.this, data); } if (!arithmetic.getClass().equals(jexla.getClass())) { logger.warn("expected arithmetic to be " + arithmetic.getClass().getSimpleName() + ", got " + jexla.getClass().getSimpleName() ); } final Interpreter ii = new Interpreter(Interpreter.this, jexla); final Object r = cblock.jjtAccept(ii, data); if (ii.isCancelled()) { Interpreter.this.cancel(); } return r; } // tracking whether we processed the annotation final AnnotatedCall jstmt = new AnnotatedCall(stmt, index + 1, data); // the annotation node and name final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index); final String aname = anode.getName(); // evaluate the arguments final Object[] argv = anode.jjtGetNumChildren() > 0 ? visit((ASTArguments) anode.jjtGetChild(0), null) : null; // wrap the future, will recurse through annotation processor Object result; try { result = processAnnotation(aname, argv, jstmt); // not processing an annotation is an error if (!jstmt.isProcessed()) { return annotationError(anode, aname, null); } } catch (final JexlException xany) { throw xany; } catch (final Exception xany) { return annotationError(anode, aname, xany); } // the caller may return a return, break or continue if (result instanceof JexlException) { throw (JexlException) result; } return result; } /** * Delegates the annotation processing to the JexlContext if it is an AnnotationProcessor. * @param annotation the annotation name * @param args the annotation arguments * @param stmt the statement / block that was annotated * @return the result of statement.call() * @throws Exception if anything goes wrong */ protected Object processAnnotation(final String annotation, final Object[] args, final Callable stmt) throws Exception { return context instanceof JexlContext.AnnotationProcessor ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt) : stmt.call(); } }