
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 java.util.function.Consumer;
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.*;
/**
* 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 arithmetic.controlReturn(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 {
// clean functors at top level
if (fp == 0) {
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(findNullOperand(node, left, right), "+ 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(findNullOperand(node, left, right), "- 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) {
throw new JexlException(findNullOperand(node, left, right), "* 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;
}
throw new JexlException(findNullOperand(node, left, right), "/ 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;
}
throw new JexlException(findNullOperand(node, left, right), "% 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(findNullOperand(node, left, right), "& 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(findNullOperand(node, left, right), "| 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(findNullOperand(node, left, right), "^ error", xrt);
}
}
@Override
protected Object visit(final ASTShiftLeftNode 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.SHIFTLEFT, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftLeft(left, right);
} catch (final ArithmeticException xrt) {
throw new JexlException(findNullOperand(node, left, right), "<< error", xrt);
}
}
@Override
protected Object visit(final ASTShiftRightNode 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.SHIFTRIGHT, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRight(left, right);
} catch (final ArithmeticException xrt) {
throw new JexlException(findNullOperand(node, left, right), ">> error", xrt);
}
}
@Override
protected Object visit(final ASTShiftRightUnsignedNode 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.SHIFTRIGHTU, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRightUnsigned(left, right);
} catch (final ArithmeticException xrt) {
throw new JexlException(findNullOperand(node, left, right), ">> 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(findNullOperand(node, left, right), "== 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) {
throw new JexlException(findNullOperand(node, left, right), "!= 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(findNullOperand(node, left, right), ">= 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(findNullOperand(node, left, right), "> 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(findNullOperand(node, left, right), "<= 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(findNullOperand(node, left, right), "< 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);
// note the arguments inversion between 'in'/'matches' and 'contains'
// if x in y then y contains x
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);
// note the arguments inversion between (not) 'in'/'matches' and (not) 'contains'
// if x not-in y then y not-contains x
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) {
throw new JexlException(findNullOperand(node, left, right), ".. 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);
}
}
private boolean testPredicate(final JexlNode node, final Object condition) {
final Object predicate = operators.tryOverload(node, JexlOperator.CONDITION, condition);
return arithmetic.testPredicate(predicate != JexlEngine.TRY_FAILED? predicate : condition);
}
@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 JexlNode testNode = node.jjtGetChild(ifElse);
final Object condition = testNode.jjtAccept(this, null);
if (testPredicate(testNode, 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() && !node.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 (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) {
return node.getLoopForm() == 0 ? forIterator(node, data) : forLoop(node, data);
}
private Object forIterator(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 = loopVariable.isLexical() || 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) {
return null;
}
/* last child node is the statement to execute */
final int numChildren = node.jjtGetNumChildren();
final JexlNode statement = numChildren >= 3 ? node.jjtGetChild(numChildren - 1) : 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) {
return 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;
}
private Object forLoop(final ASTForeachStatement node, final Object data) {
Object result = null;
int nc;
final int form = node.getLoopForm();
final LexicalFrame locals;
/* first child node might be the loop variable */
if ((form & 1) != 0) {
nc = 1;
final JexlNode init = node.jjtGetChild(0);
ASTVar loopVariable = null;
if (init instanceof ASTAssignment) {
final JexlNode child = init.jjtGetChild(0);
if (child instanceof ASTVar) {
loopVariable = (ASTVar) child;
}
} else if (init instanceof ASTVar){
loopVariable = (ASTVar) init;
}
if (loopVariable != null) {
final boolean lexical = loopVariable.isLexical() || options.isLexical();
locals = lexical ? new LexicalFrame(frame, block) : null;
if (locals != null) {
block = locals;
}
} else {
locals = null;
}
// initialize after eventual creation of local lexical frame
init.jjtAccept(this, data);
// other inits
for (JexlNode moreAssignment = node.jjtGetChild(nc);
moreAssignment instanceof ASTAssignment;
moreAssignment = node.jjtGetChild(++nc)) {
moreAssignment.jjtAccept(this, data);
}
} else {
locals = null;
nc = 0;
}
try {
// the loop condition
final JexlNode predicate = (form & 2) != 0? node.jjtGetChild(nc++) : null;
// the loop step
final JexlNode step = (form & 4) != 0? node.jjtGetChild(nc++) : null;
// last child is body
final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
// while(predicate())...
while (predicate == null || testPredicate(predicate, predicate.jjtAccept(this, data))) {
cancelCheck(node);
// the body
if (statement != null) {
try {
// execute statement
result = statement.jjtAccept(this, data);
} catch (final JexlException.Break stmtBreak) {
break;
} catch (final JexlException.Continue stmtContinue) {
//continue;
}
}
// the step
if (step != null) {
step.jjtAccept(this, data);
}
}
} finally {
// restore lexical frame
if (locals != null) {
block = block.pop();
}
}
return result;
}
@Override
protected Object visit(final ASTWhileStatement node, final Object data) {
Object result = null;
/* first objectNode is the condition */
final JexlNode condition = node.jjtGetChild(0);
while (testPredicate(condition, 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 JexlNode 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 (testPredicate(condition, 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();
// if empty script, nothing to evaluate
final int numChildren = script.jjtGetNumChildren();
if (numChildren == 0) {
return null;
}
block = new LexicalFrame(frame, block).defineArgs();
try {
final JexlNode body = script instanceof ASTJexlLambda
? script.jjtGetChild(numChildren - 1)
: script;
return interpret(body);
} finally {
block = block.pop();
}
}
@Override
protected Object visit(final ASTJexlScript script, final Object data) {
if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
final Closure closure = new Closure(this, (ASTJexlLambda) script);
// if the function is named, assign in the local frame
final JexlNode child0 = script.jjtGetChild(0);
if (child0 instanceof ASTVar) {
final ASTVar var = (ASTVar) child0;
this.visit(var, data);
final int symbol = var.getSymbol();
frame.set(symbol, closure);
// make the closure accessible to itself, ie capture the currently set variable after frame creation
closure.setCaptured(symbol, closure);
}
return closure;
}
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;
}
@Override
protected Object visit(final ASTQualifiedIdentifier node, final Object data) {
final String name = node.getName();
// try with local solver
String fqcn = fqcnSolver.resolveClassName(name);
if (fqcn != null) {
return fqcn;
}
// context may be solving class name ?
if (context instanceof JexlContext.ClassNameResolver) {
final JexlContext.ClassNameResolver resolver = (JexlContext.ClassNameResolver) context;
fqcn = resolver.resolveClassName(name);
if (fqcn != null) {
return fqcn;
}
}
return name;
}
/**
* 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(context, frame, options);
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) && options.isAntish();
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());
continue;
// skip the first node case since it was trialed in jjtAccept above and returned null
}
// 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 = c == 0 && numChildren > 1 ? node.jjtGetChild(1) : 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 && !isStrictOperand(node)) {
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);
}
@Override
protected Object visit(final ASTSetShiftLeftNode node, final Object data) {
return executeAssign(node, JexlOperator.SELF_SHIFTLEFT, data);
}
@Override
protected Object visit(final ASTSetShiftRightNode node, final Object data) {
return executeAssign(node, JexlOperator.SELF_SHIFTRIGHT, data);
}
@Override
protected Object visit(final ASTSetShiftRightUnsignedNode node, final Object data) {
return executeAssign(node, JexlOperator.SELF_SHIFTRIGHTU, data);
}
@Override
protected Object visit(final ASTGetDecrementNode node, final Object data) {
return executeAssign(node, JexlOperator.GET_AND_DECREMENT, data);
}
@Override
protected Object visit(final ASTGetIncrementNode node, final Object data) {
return executeAssign(node, JexlOperator.GET_AND_INCREMENT, data);
}
@Override
protected Object visit(final ASTDecrementGetNode node, final Object data) {
return executeAssign(node, JexlOperator.DECREMENT_AND_GET, data);
}
@Override
protected Object visit(final ASTIncrementGetNode node, final Object data) {
return executeAssign(node, JexlOperator.INCREMENT_AND_GET, 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);
final ASTIdentifier var;
Object object = null;
final int symbol;
// check var decl with assign is ok
if (left instanceof ASTIdentifier) {
var = (ASTIdentifier) left;
symbol = var.getSymbol();
if (symbol >= 0 && (var.isLexical() || options.isLexical())) {
if (var instanceof ASTVar) {
if (!defineVariable((ASTVar) var, block)) {
return redefinedVariable(var, var.getName());
}
} else if (var.isShaded() && (var.isLexical() || options.isLexicalShade())) {
return undefinedVariable(var, var.getName());
}
}
} else {
var = null;
symbol = -1;
}
boolean antish = options.isAntish();
// 0: determine initial object & property:
final int last = left.jjtGetNumChildren() - 1;
// right is the value expression to assign
final Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
// actual value to return, right in most cases
Object actual = right;
// a (var?) v = ... expression
if (var != null) {
if (symbol >= 0) {
// check we are not assigning a symbol itself
if (last < 0) {
if (assignop == null) {
// make the closure accessible to itself, ie capture the currently set variable after frame creation
if (right instanceof Closure) {
((Closure) right).setCaptured(symbol, right);
}
frame.set(symbol, right);
} else {
// go through potential overload
final Object self = getVariable(frame, block, var);
final Consumer