blob: 3c2d03cef980d8c3bf0307b3f75cbb1f27466da3 [file] [log] [blame]
/*
* 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.
*/
package org.apache.commons.jexl3.internal;
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.JexlOperator;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlPropertySet;
import org.apache.commons.jexl3.introspection.JexlUberspect.PropertyResolver;
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.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.ASTEmptyMethod;
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.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.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.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.ASTSizeMethod;
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.ASTVar;
import org.apache.commons.jexl3.parser.ASTWhileStatement;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.Node;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* An interpreter of JEXL syntax.
*
* @since 2.0
*/
public class Interpreter extends InterpreterBase {
/** The operators evaluation delegate. */
protected final Operators operators;
/** Cache executors. */
protected final boolean cache;
/** Symbol values. */
protected final Scope.Frame frame;
/** The context to store/retrieve variables. */
protected final JexlContext.NamespaceResolver ns;
/** The map of 'prefix:function' to object resolving as namespaces. */
protected final Map<String, Object> functions;
/** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
protected Map<String, Object> functors;
/**
* Creates an interpreter.
* @param engine the engine creating this interpreter
* @param aContext the context to evaluate expression
* @param eFrame the interpreter evaluation frame
*/
protected Interpreter(Engine engine, JexlContext aContext, Scope.Frame eFrame) {
super(engine, aContext);
this.operators = new Operators(this);
this.cache = jexl.cache != null;
this.frame = eFrame;
if (this.context instanceof JexlContext.NamespaceResolver) {
ns = ((JexlContext.NamespaceResolver) context);
} else {
ns = Engine.EMPTY_NS;
}
this.functions = jexl.functions;
this.functors = null;
}
/**
* Interpret the given script/expression.
* <p>
* 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(JexlNode node) {
JexlContext.ThreadLocal local = null;
try {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
if (context instanceof JexlContext.ThreadLocal) {
local = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
}
return node.jjtAccept(this, null);
} catch (JexlException.Return xreturn) {
return xreturn.getValue();
} catch (JexlException.Cancel xcancel) {
cancelled |= Thread.interrupted();
if (isCancellable()) {
throw xcancel.clean();
}
} catch (JexlException xjexl) {
if (!isSilent()) {
throw xjexl.clean();
}
logger.warn(xjexl.getMessage(), xjexl.getCause());
} finally {
synchronized(this) {
if (functors != null) {
if (AUTOCLOSEABLE != null) {
for (Object functor : functors.values()) {
closeIfSupported(functor);
}
}
functors.clear();
functors = null;
}
}
if (context instanceof JexlContext.ThreadLocal) {
jexl.putThreadLocal(local);
}
}
return null;
}
/**
* Resolves a namespace, eventually allocating an instance using context as constructor argument.
* <p>
* The lifetime of such instances span the current expression or script evaluation.</p>
* @param prefix the prefix name (may be null for global namespace)
* @param node the AST node
* @return the namespace instance
*/
protected Object resolveNamespace(String prefix, JexlNode node) {
Object namespace;
// check whether this namespace is a functor
synchronized (this) {
if (functors != null) {
namespace = functors.get(prefix);
if (namespace != null) {
return namespace;
}
}
}
// check if namespace is a resolver
namespace = ns.resolveNamespace(prefix);
if (namespace == null) {
namespace = functions.get(prefix);
if (prefix != null && namespace == null) {
throw new JexlException(node, "no such function namespace " + prefix, null);
}
}
// allow namespace to instantiate a functor with context if possible, not an error otherwise
Object functor = null;
if (namespace instanceof JexlContext.NamespaceFunctor) {
functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
} else if (namespace instanceof Class<?>) {
Object[] args = new Object[]{context};
JexlMethod ctor = uberspect.getConstructor(namespace, args);
if (ctor != null) {
try {
functor = ctor.invoke(namespace, args);
} catch (Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
}
// got a functor, store it and return it
if (functor != null) {
synchronized (this) {
if (functors == null) {
functors = new HashMap<String, Object>();
}
functors.put(prefix, functor);
}
return functor;
} else {
return namespace;
}
}
@Override
protected Object visit(ASTAddNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.ADD, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.add(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "+ error", xrt);
}
}
@Override
protected Object visit(ASTSubNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.subtract(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "- error", xrt);
}
}
@Override
protected Object visit(ASTMulNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.multiply(left, right);
} catch (ArithmeticException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "* error", xrt);
}
}
@Override
protected Object visit(ASTDivNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.DIVIDE, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.divide(left, right);
} catch (ArithmeticException xrt) {
if (!arithmetic.isStrict()) {
return 0.0d;
}
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "/ error", xrt);
}
}
@Override
protected Object visit(ASTModNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.MOD, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.mod(left, right);
} catch (ArithmeticException xrt) {
if (!arithmetic.isStrict()) {
return 0.0d;
}
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "% error", xrt);
}
}
@Override
protected Object visit(ASTBitwiseAndNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.AND, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.and(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "& error", xrt);
}
}
@Override
protected Object visit(ASTBitwiseOrNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.OR, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.or(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "| error", xrt);
}
}
@Override
protected Object visit(ASTBitwiseXorNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.XOR, left, right);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.xor(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "^ error", xrt);
}
}
@Override
protected Object visit(ASTEQNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
return result != JexlEngine.TRY_FAILED
? result
: arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, "== error", xrt);
}
}
@Override
protected Object visit(ASTNENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
return result != JexlEngine.TRY_FAILED
? arithmetic.toBoolean(result) ? Boolean.FALSE : Boolean.TRUE
: arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
} catch (ArithmeticException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "!= error", xrt);
}
}
@Override
protected Object visit(ASTGENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.GTE, left, right);
return result != JexlEngine.TRY_FAILED
? result
: arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, ">= error", xrt);
}
}
@Override
protected Object visit(ASTGTNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.GT, left, right);
return result != JexlEngine.TRY_FAILED
? result
: arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, "> error", xrt);
}
}
@Override
protected Object visit(ASTLENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.LTE, left, right);
return result != JexlEngine.TRY_FAILED
? result
: arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, "<= error", xrt);
}
}
@Override
protected Object visit(ASTLTNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.LT, left, right);
return result != JexlEngine.TRY_FAILED
? result
: arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, "< error", xrt);
}
}
@Override
protected Object visit(ASTSWNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.startsWith(node, "^=", left, right) ? Boolean.TRUE : Boolean.FALSE;
}
@Override
protected Object visit(ASTNSWNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.startsWith(node, "^!", left, right) ? Boolean.FALSE : Boolean.TRUE;
}
@Override
protected Object visit(ASTEWNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.endsWith(node, "$=", left, right) ? Boolean.TRUE : Boolean.FALSE;
}
@Override
protected Object visit(ASTNEWNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.endsWith(node, "$!", left, right) ? Boolean.FALSE : Boolean.TRUE;
}
@Override
protected Object visit(ASTERNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.contains(node, "=~", right, left) ? Boolean.TRUE : Boolean.FALSE;
}
@Override
protected Object visit(ASTNRNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
return operators.contains(node, "!~", right, left) ? Boolean.FALSE : Boolean.TRUE;
}
@Override
protected Object visit(ASTRangeNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.createRange(left, right);
} catch (ArithmeticException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, ".. error", xrt);
}
}
@Override
protected Object visit(ASTUnaryMinusNode node, Object data) {
JexlNode valNode = node.jjtGetChild(0);
Object val = valNode.jjtAccept(this, data);
try {
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
if (valNode instanceof ASTNumberLiteral && number instanceof Number) {
number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
}
return number;
} catch (ArithmeticException xrt) {
throw new JexlException(valNode, "- error", xrt);
}
}
@Override
protected Object visit(ASTBitwiseComplNode node, Object data) {
Object arg = node.jjtGetChild(0).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.COMPLEMENT, arg);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.complement(arg);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "~ error", xrt);
}
}
@Override
protected Object visit(ASTNotNode node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
try {
Object result = operators.tryOverload(node, JexlOperator.NOT, val);
return result != JexlEngine.TRY_FAILED ? result : arithmetic.not(val);
} catch (ArithmeticException xrt) {
throw new JexlException(node, "! error", xrt);
}
}
@Override
protected Object visit(ASTIfStatement node, Object data) {
int n = 0;
try {
Object result = null;
// first objectNode is the condition
Object expression = node.jjtGetChild(0).jjtAccept(this, null);
if (arithmetic.toBoolean(expression)) {
// first objectNode is true statement
n = 1;
result = node.jjtGetChild(n).jjtAccept(this, null);
} else {
// if there is a false, execute it. false statement is the second
// objectNode
if (node.jjtGetNumChildren() == 3) {
n = 2;
result = node.jjtGetChild(n).jjtAccept(this, null);
}
}
return result;
} catch (ArithmeticException xrt) {
throw new JexlException(node.jjtGetChild(n), "if error", xrt);
}
}
@Override
protected Object visit(ASTBlock node, Object data) {
int numChildren = node.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
result = node.jjtGetChild(i).jjtAccept(this, data);
}
return result;
}
@Override
protected Object visit(ASTReturnStatement node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
throw new JexlException.Return(node, null, val);
}
@Override
protected Object visit(ASTContinue node, Object data) {
throw new JexlException.Continue(node);
}
@Override
protected Object visit(ASTBreak node, Object data) {
throw new JexlException.Break(node);
}
@Override
protected Object visit(ASTForeachStatement node, Object data) {
Object result = null;
/* first objectNode is the loop variable */
ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
int symbol = loopVariable.getSymbol();
/* second objectNode is the variable to iterate */
Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
// make sure there is a value to iterate on and a statement to execute
if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
/* third objectNode is the statement to execute */
JexlNode statement = node.jjtGetChild(2);
// get an iterator for the collection/array etc via the introspector.
Object forEach = null;
try {
forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
Iterator<?> itemsIterator = forEach instanceof Iterator
? (Iterator<?>) forEach
: uberspect.getIterator(iterableValue);
if (itemsIterator != null) {
while (itemsIterator.hasNext()) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
// set loopVariable to value of iterator
Object value = itemsIterator.next();
if (symbol < 0) {
context.set(loopVariable.getName(), value);
} else {
frame.set(symbol, value);
}
try {
// execute statement
result = statement.jjtAccept(this, data);
} catch (JexlException.Break stmtBreak) {
break;
} catch (JexlException.Continue stmtContinue) {
//continue;
}
}
}
} finally {
// closeable iterator handling
closeIfSupported(forEach);
}
}
return result;
}
@Override
protected Object visit(ASTWhileStatement node, Object data) {
Object result = null;
/* first objectNode is the expression */
Node expressionNode = node.jjtGetChild(0);
while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
if (node.jjtGetNumChildren() > 1) {
try {
// execute statement
result = node.jjtGetChild(1).jjtAccept(this, data);
} catch (JexlException.Break stmtBreak) {
break;
} catch (JexlException.Continue stmtContinue) {
//continue;
}
}
}
return result;
}
@Override
protected Object visit(ASTAndNode node, Object data) {
/**
* The pattern for exception mgmt is to let the child*.jjtAccept out of the try/catch loop so that if one fails,
* the ex will traverse up to the interpreter. In cases where this is not convenient/possible, JexlException
* must be caught explicitly and rethrown.
*/
Object left = node.jjtGetChild(0).jjtAccept(this, data);
try {
boolean leftValue = arithmetic.toBoolean(left);
if (!leftValue) {
return Boolean.FALSE;
}
} catch (ArithmeticException xrt) {
throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
}
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
boolean rightValue = arithmetic.toBoolean(right);
if (!rightValue) {
return Boolean.FALSE;
}
} catch (ArithmeticException xrt) {
throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
}
return Boolean.TRUE;
}
@Override
protected Object visit(ASTOrNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
try {
boolean leftValue = arithmetic.toBoolean(left);
if (leftValue) {
return Boolean.TRUE;
}
} catch (ArithmeticException xrt) {
throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
}
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
boolean rightValue = arithmetic.toBoolean(right);
if (rightValue) {
return Boolean.TRUE;
}
} catch (ArithmeticException xrt) {
throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
}
return Boolean.FALSE;
}
@Override
protected Object visit(ASTNullLiteral node, Object data) {
return null;
}
@Override
protected Object visit(ASTTrueNode node, Object data) {
return Boolean.TRUE;
}
@Override
protected Object visit(ASTFalseNode node, Object data) {
return Boolean.FALSE;
}
@Override
protected Object visit(ASTNumberLiteral node, Object data) {
if (data != null && node.isInteger()) {
return getAttribute(data, node.getLiteral(), node);
}
return node.getLiteral();
}
@Override
protected Object visit(ASTStringLiteral node, Object data) {
if (data != null) {
return getAttribute(data, node.getLiteral(), node);
}
return node.getLiteral();
}
@Override
protected Object visit(ASTArrayLiteral node, Object data) {
int childCount = node.jjtGetNumChildren();
JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
if (ab != null) {
boolean extended = false;
for (int i = 0; i < childCount; i++) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
JexlNode child = node.jjtGetChild(i);
if (child instanceof ASTExtendedLiteral) {
extended = true;
} else {
Object entry = node.jjtGetChild(i).jjtAccept(this, data);
ab.add(entry);
}
}
return ab.create(extended);
} else {
return null;
}
}
@Override
protected Object visit(ASTExtendedLiteral node, Object data) {
return node;
}
@Override
protected Object visit(ASTSetLiteral node, Object data) {
int childCount = node.jjtGetNumChildren();
JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
if (mb != null) {
for (int i = 0; i < childCount; i++) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
Object entry = node.jjtGetChild(i).jjtAccept(this, data);
mb.add(entry);
}
return mb.create();
} else {
return null;
}
}
@Override
protected Object visit(ASTMapLiteral node, Object data) {
int childCount = node.jjtGetNumChildren();
JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
if (mb != null) {
for (int i = 0; i < childCount; i++) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
mb.put(entry[0], entry[1]);
}
return mb.create();
} else {
return null;
}
}
@Override
protected Object visit(ASTMapEntry node, Object data) {
Object key = node.jjtGetChild(0).jjtAccept(this, data);
Object value = node.jjtGetChild(1).jjtAccept(this, data);
return new Object[]{key, value};
}
@Override
protected Object visit(ASTTernaryNode node, Object data) {
Object condition = node.jjtGetChild(0).jjtAccept(this, data);
if (node.jjtGetNumChildren() == 3) {
if (condition != null && arithmetic.toBoolean(condition)) {
return node.jjtGetChild(1).jjtAccept(this, data);
} else {
return node.jjtGetChild(2).jjtAccept(this, data);
}
}
if (condition != null && arithmetic.toBoolean(condition)) {
return condition;
} else {
return node.jjtGetChild(1).jjtAccept(this, data);
}
}
@Override
protected Object visit(ASTSizeFunction node, Object data) {
try {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
return operators.size(node, val);
} catch(JexlException xany) {
return 0;
}
}
@Override
protected Object visit(ASTSizeMethod node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
return operators.size(node, val);
}
@Override
protected Object visit(ASTEmptyFunction node, Object data) {
try {
Object value = node.jjtGetChild(0).jjtAccept(this, data);
return operators.empty(node, value);
} catch(JexlException xany) {
return true;
}
}
@Override
protected Object visit(ASTEmptyMethod node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
return operators.empty(node, val);
}
@Override
protected Object visit(ASTJexlScript node, Object data) {
if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
return new Closure(this, (ASTJexlLambda) node);
} else {
final int numChildren = node.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
JexlNode child = node.jjtGetChild(i);
result = child.jjtAccept(this, data);
if (isCancelled()) {
throw new JexlException.Cancel(child);
}
}
return result;
}
}
@Override
protected Object visit(ASTVar node, Object data) {
return visit((ASTIdentifier) node, data);
}
@Override
protected Object visit(ASTReferenceExpression node, Object data) {
return node.jjtGetChild(0).jjtAccept(this, data);
}
@Override
protected Object visit(ASTIdentifier node, Object data) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
String name = node.getName();
if (data == null) {
int symbol = node.getSymbol();
if (symbol >= 0) {
return frame.get(symbol);
}
Object value = context.get(name);
if (value == null
&& !(node.jjtGetParent() instanceof ASTReference)
&& !context.has(name)
&& !isTernaryProtected(node)) {
return unsolvableVariable(node, name, true);
}
return value;
} else {
return getAttribute(data, name, node);
}
}
@Override
protected Object visit(ASTArrayAccess node, Object data) {
// first objectNode is the identifier
Object object = data;
// can have multiple nodes - either an expression, integer literal or reference
int numChildren = node.jjtGetNumChildren();
for (int i = 0; i < numChildren; i++) {
JexlNode nindex = node.jjtGetChild(i);
if (object == null) {
return null;
}
Object index = nindex.jjtAccept(this, null);
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
object = getAttribute(object, index, nindex);
}
return object;
}
/**
* Check if a null evaluated expression is protected by a ternary expression.
* <p>
* The rationale is that the ternary / elvis expressions are meant for the user to explictly take control
* over the error generation; ie, ternaries can return null even if the engine in isStrict mode
* would normally throw an exception.
* </p>
* @param node the expression node
* @return true if nullable variable, false otherwise
*/
protected boolean isTernaryProtected(JexlNode node) {
for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
if (walk instanceof ASTTernaryNode) {
return true;
} else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
break;
}
}
return false;
}
/**
* Checks whether a reference child node holds a local variable reference.
* @param node the reference node
* @param which the child we are checking
* @return true if child is local variable, false otherwise
*/
protected boolean isLocalVariable(ASTReference node, int which) {
return (node.jjtGetNumChildren() > which
&& node.jjtGetChild(which) instanceof ASTIdentifier
&& ((ASTIdentifier) node.jjtGetChild(which)).getSymbol() >= 0);
}
@Override
protected Object visit(ASTIdentifierAccess node, Object data) {
return data != null ? getAttribute(data, node.getIdentifier(), node) : null;
}
@Override
protected Object visit(ASTReference node, Object data) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
final int numChildren = node.jjtGetNumChildren();
JexlNode parent = node.jjtGetParent();
// pass first piece of data in and loop through children
Object object = null;
JexlNode objectNode;
StringBuilder ant = null;
boolean antish = !(parent instanceof ASTReference);
boolean pty = true;
int v = 1;
main:
for (int c = 0; c < numChildren; c++) {
objectNode = node.jjtGetChild(c);
if (objectNode instanceof ASTMethodNode) {
if (object == null) {
break;
} else {
antish = false;
}
}
// attempt to evaluate the property within the object (visit(ASTIdentifierAccess node))
object = objectNode.jjtAccept(this, object);
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
if (object != null) {
// disallow mixing antish variable & bean with same root; avoid ambiguity
antish = false;
} else if (antish) { // if we still have a null object, check for an antish variable
if (ant == null) {
JexlNode first = node.jjtGetChild(0);
if (first instanceof ASTIdentifier) {
if (((ASTIdentifier) first).getSymbol() < 0) {
ant = new StringBuilder(((ASTIdentifier) first).getName());
} else {
break;
}
} else {
pty = false;
break;
}
}
for (; v <= c; ++v) {
JexlNode child = node.jjtGetChild(v);
if (child instanceof ASTIdentifierAccess) {
ant.append('.');
ant.append(((ASTIdentifierAccess) objectNode).getName());
} else {
break;
}
}
object = context.get(ant.toString());
} else {
break;
}
}
if (object == null && !isTernaryProtected(node)) {
if (antish && ant != null) {
boolean undefined = !(context.has(ant.toString()) || isLocalVariable(node, 0));
// variable unknown in context and not a local
return unsolvableVariable(node, ant.toString(), undefined);
} else if (!pty) {
return unsolvableProperty(node, "<null>.<?>", null);
}
}
return object;
}
@Override
protected Object visit(ASTAssignment node, Object data) {
return executeAssign(node, null, data);
}
@Override
protected Object visit(ASTSetAddNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_ADD, data);
}
@Override
protected Object visit(ASTSetSubNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
}
@Override
protected Object visit(ASTSetMultNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
}
@Override
protected Object visit(ASTSetDivNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
}
@Override
protected Object visit(ASTSetModNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_MOD, data);
}
@Override
protected Object visit(ASTSetAndNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_AND, data);
}
@Override
protected Object visit(ASTSetOrNode node, Object data) {
return executeAssign(node, JexlOperator.SELF_OR, data);
}
@Override
protected Object visit(ASTSetXorNode node, 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(JexlNode node, JexlOperator assignop, Object data) { // CSOFF: MethodLength
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
// left contains the reference to assign to
final JexlNode left = node.jjtGetChild(0);
// right is the value expression to assign
Object right = node.jjtGetChild(1).jjtAccept(this, data);
Object object = null;
int symbol = -1;
boolean antish = true;
// 0: determine initial object & property:
final int last = left.jjtGetNumChildren() - 1;
if (left instanceof ASTIdentifier) {
ASTIdentifier var = (ASTIdentifier) left;
symbol = var.getSymbol();
if (symbol >= 0) {
// check we are not assigning a symbol itself
if (last < 0) {
if (assignop != null) {
Object self = frame.get(symbol);
right = operators.tryAssignOverload(node, assignop, self, right);
if (right == JexlOperator.ASSIGN) {
return self;
}
}
frame.set(symbol, right);
// make the closure accessible to itself, ie hoist the currently set variable after frame creation
if (right instanceof Closure) {
((Closure) right).setHoisted(symbol, right);
}
return right; // 1
}
object = frame.get(symbol);
// 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) {
Object self = context.get(var.getName());
right = operators.tryAssignOverload(node, assignop, self, right);
if (right == JexlOperator.ASSIGN) {
return self;
}
}
try {
context.set(var.getName(), right);
} catch (UnsupportedOperationException xsupport) {
throw new JexlException(node, "context is readonly", xsupport);
}
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
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) {
if (ant == null) {
JexlNode first = left.jjtGetChild(0);
if (first instanceof ASTIdentifier && ((ASTIdentifier) first).getSymbol() < 0) {
ant = new StringBuilder(((ASTIdentifier) first).getName());
} else {
break;
}
}
for (; v <= c; ++v) {
JexlNode child = left.jjtGetChild(v);
if (child instanceof ASTIdentifierAccess) {
ant.append('.');
ant.append(((ASTIdentifierAccess) objectNode).getName());
} else {
break;
}
}
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);
if (propertyNode instanceof ASTIdentifierAccess) {
property = ((ASTIdentifierAccess) propertyNode).getIdentifier();
// deal with antish variable
if (ant != null && object == null) {
if (last > 0) {
ant.append('.');
}
ant.append(String.valueOf(property));
if (assignop != null) {
Object self = context.get(ant.toString());
right = operators.tryAssignOverload(node, assignop, self, right);
if (right == JexlOperator.ASSIGN) {
return self;
}
}
try {
context.set(ant.toString(), right);
} catch (UnsupportedOperationException xsupport) {
throw new JexlException(node, "context is readonly", xsupport);
}
return right; // 3
}
} else if (propertyNode instanceof ASTArrayAccess) {
// can have multiple nodes - either an expression, integer literal or reference
int numChildren = propertyNode.jjtGetNumChildren() - 1;
for (int i = 0; i < numChildren; i++) {
JexlNode nindex = propertyNode.jjtGetChild(i);
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");
}
if (property == null) {
// no property, we fail
return unsolvableProperty(propertyNode, "<?>.<null>", null);
}
if (object == null) {
// no object, we fail
return unsolvableProperty(objectNode, "<null>.<?>", null);
}
// 3: one before last, assign
if (assignop != null) {
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(ASTArguments node, 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, Object data) {
// left contains the reference to the method
final JexlNode methodNode = node.jjtGetChild(0);
Object object = null;
JexlNode objectNode = null;
Object method;
// 1: determine object and method or functor
if (methodNode instanceof ASTIdentifierAccess) {
method = methodNode;
object = data;
if (object == null) {
// no object, we fail
return unsolvableMethod(objectNode, "<null>.<?>(...)");
}
} else {
method = methodNode.jjtAccept(this, null);
}
Object result = method;
for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
if (result == null) {
// no method, we fail
return unsolvableMethod(methodNode, "<?>.<null>(...)");
}
ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
result = call(node, object, result, argNode);
object = result;
}
return result;
}
@Override
protected Object visit(ASTFunctionNode node, Object data) {
int argc = node.jjtGetNumChildren();
if (argc == 2) {
ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
return call(node, context, functionNode, argNode);
} else {
// objectNode 0 is the prefix
String prefix = ((ASTIdentifier) node.jjtGetChild(0)).getName();
Object namespace = resolveNamespace(prefix, node);
// objectNode 1 is the identifier , the others are parameters.
ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
ASTArguments argNode = (ASTArguments) node.jjtGetChild(2);
return call(node, namespace, functionNode, argNode);
}
}
/**
* Concatenate arguments in call(...).
* <p>When target == context, we are dealing with a global namespace function call
* @param target the pseudo-method owner, first to-be argument
* @param narrow whether we should attempt to narrow number arguments
* @param args the other (non null) arguments
* @return the arguments array
*/
private Object[] functionArguments(Object target, boolean narrow, Object[] args) {
// when target == context, we are dealing with the null namespace
if (target == null || target == context) {
if (narrow) {
arithmetic.narrowArguments(args);
}
return args;
}
// makes target 1st args, copy others - optionally narrow numbers
Object[] nargv = new Object[args.length + 1];
if (narrow) {
nargv[0] = functionArgument(true, target);
for (int a = 1; a <= args.length; ++a) {
nargv[a] = functionArgument(true, args[a - 1]);
}
} else {
nargv[0] = target;
System.arraycopy(args, 0, nargv, 1, args.length);
}
return nargv;
}
/**
* Optionally narrows an argument for a function call.
* @param narrow whether narrowing should occur
* @param arg the argument
* @return the narrowed argument
*/
private Object functionArgument(boolean narrow, Object arg) {
return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
}
/**
* Cached function call.
*/
private static class Funcall {
/** Whether narrow should be applied to arguments. */
protected final boolean narrow;
/** The JexlMethod to delegate the call to. */
protected final JexlMethod me;
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected Funcall(JexlMethod jme, boolean flag) {
this.me = jme;
this.narrow = flag;
}
/**
* Try invocation.
* @param ii the interpreter
* @param name the method name
* @param target the method target
* @param args the method arguments
* @return the method invocation result (or JexlEngine.TRY_FAILED)
*/
protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
}
}
/**
* Cached arithmetic function call.
*/
private static class ArithmeticFuncall extends Funcall {
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
super(jme, flag);
}
@Override
protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
}
}
/**
* Cached context function call.
*/
private static class ContextFuncall extends Funcall {
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected ContextFuncall(JexlMethod jme, boolean flag) {
super(jme, flag);
}
@Override
protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
}
}
/**
* Calls a method (or function).
* <p>
* 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;
* </p>
* *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, Object target, Object functor, final ASTArguments argNode) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
// evaluate the arguments
Object[] argv = visit(argNode, null);
// get the method name if identifier
final int symbol;
final String methodName;
if (functor instanceof ASTIdentifier) {
ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
symbol = methodIdentifier.getSymbol();
methodName = methodIdentifier.getName();
functor = null;
} else if (functor instanceof ASTIdentifierAccess) {
methodName = ((ASTIdentifierAccess) functor).getName();
symbol = -1;
functor = null;
} else if (functor != null) {
symbol = -2;
methodName = null;
} else {
return unsolvableMethod(node, "?");
}
// at this point, either the functor is a non null (hopefully) 'invocable' object or we do have the methodName
JexlException xjexl;
Object caller = target;
try {
boolean cacheable = cache;
// do we have a method/function name ?
if (methodName != null) {
// is it a global or local variable ?
if (target == context) {
boolean isavar = true;
if (symbol >= 0) {
functor = frame.get(symbol);
} else if (context.has(methodName)) {
functor = context.get(methodName);
} else {
isavar = false;
}
// name is a variable, must be a functor, cant be cached
if (isavar) {
if (functor == null) {
return unsolvableMethod(node, methodName);
}
cacheable = false;
}
}
// attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
if (cacheable) {
Object cached = node.jjtGetValue();
if (cached instanceof Funcall) {
Object eval = ((Funcall) cached).tryInvoke(this, methodName, target, argv);
if (JexlEngine.TRY_FAILED != eval) {
return eval;
}
}
}
}
boolean narrow = false;
JexlMethod vm = null;
Funcall funcall = null;
// pseudo loop and a half to try acquiring methods without and with argument narrowing
while (true) {
if (functor == null) {
// try a method
vm = uberspect.getMethod(target, methodName, argv);
if (vm != null) {
if (cacheable && vm.isCacheable()) {
funcall = new Funcall(vm, narrow);
}
break;
}
// solve 'null' namespace
if (target == context) {
Object namespace = resolveNamespace(null, node);
if (namespace == context) {
// we can not solve it
break;
} else if (namespace != null) {
target = namespace;
caller = null;
continue;
}
// could not find a method, try as a property of a non-context target (performed once)
} else if (!narrow) {
// the method may be a functor stored in a property of the target
JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
if (get != null) {
functor = get.tryInvoke(target, methodName);
}
}
}
// this may happen without the above when we are chaining call like x(a)(b)
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);
}
// a generic callable
vm = uberspect.getMethod(functor, "call", argv);
if (vm != null) {
return vm.invoke(functor, argv);
}
// try JexlArithmetic or JexlContext function
} else {
// no need to narrow since this has been performed in previous loop
Object[] nargv = functionArguments(caller, narrow, argv);
vm = uberspect.getMethod(context, methodName, nargv);
if (vm != null) {
argv = nargv;
target = context;
if (cacheable && vm.isCacheable()) {
funcall = new ContextFuncall(vm, narrow);
}
break;
}
vm = uberspect.getMethod(arithmetic, methodName, nargv);
if (vm != null) {
argv = nargv;
target = arithmetic;
if (cacheable && vm.isCacheable()) {
funcall = new ArithmeticFuncall(vm, narrow);
}
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 (arithmetic.narrowArguments(argv)) {
narrow = true;
continue;
}
}
// we are done trying
break;
}
// we have either evaluated and returned or might have found a method
if (vm != null) {
// vm cannot be null if xjexl is null
Object eval = vm.invoke(target, argv);
// cache executor in volatile JexlNode.value
if (funcall != null) {
node.jjtSetValue(funcall);
}
return eval;
}
return unsolvableMethod(node, methodName);
} catch (JexlException xthru) {
throw xthru;
} catch (Exception xany) {
xjexl = exceptionOnInvocation(node, methodName, xany);
}
return invocationFailed(xjexl);
}
@Override
protected Object visit(ASTConstructorNode node, Object data) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
// first child is class or class name
Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
// get the ctor args
int argc = node.jjtGetNumChildren() - 1;
Object[] argv = new Object[argc];
for (int i = 0; i < argc; i++) {
argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
}
JexlException xjexl = null;
try {
// attempt to reuse last constructor cached in volatile JexlNode.value
if (cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlMethod) {
JexlMethod mctor = (JexlMethod) cached;
Object eval = mctor.tryInvoke(null, cobject, argv);
if (!mctor.tryFailed(eval)) {
return eval;
}
}
}
JexlMethod ctor = uberspect.getConstructor(cobject, argv);
// DG: If we can't find an exact match, narrow the parameters and try again
if (ctor == null) {
if (arithmetic.narrowArguments(argv)) {
ctor = uberspect.getConstructor(cobject, argv);
}
if (ctor == null) {
String dbgStr = cobject != null ? cobject.toString() : null;
return unsolvableMethod(node, dbgStr);
}
}
Object instance = ctor.invoke(cobject, argv);
// cache executor in volatile JexlNode.value
if (cache && ctor.isCacheable()) {
node.jjtSetValue(ctor);
}
return instance;
} catch (JexlException xthru) {
throw xthru;
} catch (Exception xany) {
String dbgStr = cobject != null ? cobject.toString() : null;
xjexl = exceptionOnInvocation(node, dbgStr, xany);
}
return invocationFailed(xjexl);
}
/**
* Gets an attribute of an object.
*
* @param object to retrieve value from
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @return the attribute value
*/
public Object getAttribute(Object object, Object attribute) {
return getAttribute(object, attribute, null);
}
/**
* Gets an attribute of an object.
*
* @param object to retrieve value from
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @param node the node that evaluated as the object
* @return the attribute value
*/
protected Object getAttribute(Object object, Object attribute, JexlNode node) {
if (object == null) {
throw new JexlException(node, "object is null");
}
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
Object result = operators.tryOverload(node, operator, object, attribute);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertyGet) {
JexlPropertyGet vg = (JexlPropertyGet) cached;
Object value = vg.tryInvoke(object, attribute);
if (!vg.tryFailed(value)) {
return value;
}
}
}
// resolve that property
Exception xcause = null;
List<PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
if (vg != null) {
try {
Object value = vg.invoke(object);
// cache executor in volatile JexlNode.value
if (node != null && cache && vg.isCacheable()) {
node.jjtSetValue(vg);
}
return value;
} catch (Exception xany) {
xcause = xany;
}
}
// lets fail
if (node != null) {
String attrStr = attribute != null ? attribute.toString() : null;
return unsolvableProperty(node, attrStr, xcause);
} else {
// direct call
String error = "unable to get object property"
+ ", class: " + object.getClass().getName()
+ ", property: " + attribute;
throw new UnsupportedOperationException(error, xcause);
}
}
/**
* Sets an attribute of an object.
*
* @param object to set the value to
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @param value the value to assign to the object's attribute
*/
public void setAttribute(Object object, Object attribute, Object value) {
setAttribute(object, attribute, value, null);
}
/**
* Sets an attribute of an object.
*
* @param object to set the value to
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @param value the value to assign to the object's attribute
* @param node the node that evaluated as the object
*/
protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
Object result = operators.tryOverload(node, operator, object, attribute, value);
if (result != JexlEngine.TRY_FAILED) {
return;
}
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertySet) {
JexlPropertySet setter = (JexlPropertySet) cached;
Object eval = setter.tryInvoke(object, attribute, value);
if (!setter.tryFailed(eval)) {
return;
}
}
}
Exception xcause = null;
List<PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
// if we can't find an exact match, narrow the value argument and try again
if (vs == null) {
// replace all numbers with the smallest type that will fit
Object[] narrow = {value};
if (arithmetic.narrowArguments(narrow)) {
vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
}
}
if (vs != null) {
try {
// cache executor in volatile JexlNode.value
vs.invoke(object, value);
if (node != null && cache && vs.isCacheable()) {
node.jjtSetValue(vs);
}
return;
} catch (Exception xany) {
xcause = xany;
}
}
// lets fail
if (node != null) {
String attrStr = attribute != null ? attribute.toString() : null;
unsolvableProperty(node, attrStr, xcause);
} else {
// direct call
String error = "unable to set object property"
+ ", class: " + object.getClass().getName()
+ ", property: " + attribute
+ ", argument: " + value.getClass().getSimpleName();
throw new UnsupportedOperationException(error, xcause);
}
}
@Override
protected Object visit(ASTJxltLiteral node, Object data) {
TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue();
if (tp == null) {
TemplateEngine jxlt = jexl.jxlt();
tp = jxlt.parseExpression(node.jexlInfo(), node.getLiteral(), frame != null ? frame.getScope() : null);
node.jjtSetValue(tp);
}
if (tp != null) {
return tp.evaluate(frame, context);
}
return null;
}
@Override
protected Object visit(ASTAnnotation node, Object data) {
throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported.");
}
@Override
protected Object visit(ASTAnnotatedStatement node, Object data) {
return processAnnotation(node, 0, data);
}
/**
* 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) {
JexlNode block = stmt.jjtGetChild(last);
return block.jjtAccept(Interpreter.this, data);
}
// tracking whether we processed the annotation
final boolean[] processed = new boolean[]{false};
final Callable<Object> jstmt = new Callable<Object>() {
@Override
public Object call() throws Exception {
processed[0] = true;
return processAnnotation(stmt, index + 1, data);
}
};
// the annotation node and name
final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index);
final String aname = anode.getName();
// evaluate the arguments
Object[] argv = anode.jjtGetNumChildren() > 0
? visit((ASTArguments) anode.jjtGetChild(0), null) : null;
// wrap the future, will recurse through annotation processor
try {
Object result = processAnnotation(aname, argv, jstmt);
// not processing an annotation is an error
if (!processed[0]) {
return annotationError(anode, aname, null);
} else {
return result;
}
} catch(JexlException xjexl) {
throw xjexl;
} catch(Exception xany) {
return annotationError(anode, aname, xany);
}
}
/**
* 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(String annotation, Object[] args, Callable<Object> stmt) throws Exception {
return context instanceof JexlContext.AnnotationProcessor
? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
: stmt.call();
}
}