blob: d82f2bd901f1123f307926d927b598d10c647110 [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.jexl2;
import java.lang.reflect.Constructor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.jexl2.parser.SimpleNode;
import org.apache.commons.logging.Log;
import org.apache.commons.jexl2.parser.JexlNode;
import org.apache.commons.jexl2.parser.ASTAdditiveNode;
import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
import org.apache.commons.jexl2.parser.ASTAndNode;
import org.apache.commons.jexl2.parser.ASTAmbiguous;
import org.apache.commons.jexl2.parser.ASTArrayAccess;
import org.apache.commons.jexl2.parser.ASTArrayLiteral;
import org.apache.commons.jexl2.parser.ASTAssignment;
import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
import org.apache.commons.jexl2.parser.ASTBlock;
import org.apache.commons.jexl2.parser.ASTConstructorNode;
import org.apache.commons.jexl2.parser.ASTDivNode;
import org.apache.commons.jexl2.parser.ASTEQNode;
import org.apache.commons.jexl2.parser.ASTERNode;
import org.apache.commons.jexl2.parser.ASTEmptyFunction;
import org.apache.commons.jexl2.parser.ASTFalseNode;
import org.apache.commons.jexl2.parser.ASTFunctionNode;
import org.apache.commons.jexl2.parser.ASTFloatLiteral;
import org.apache.commons.jexl2.parser.ASTForeachStatement;
import org.apache.commons.jexl2.parser.ASTGENode;
import org.apache.commons.jexl2.parser.ASTGTNode;
import org.apache.commons.jexl2.parser.ASTIdentifier;
import org.apache.commons.jexl2.parser.ASTIfStatement;
import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
import org.apache.commons.jexl2.parser.ASTJexlScript;
import org.apache.commons.jexl2.parser.ASTLENode;
import org.apache.commons.jexl2.parser.ASTLTNode;
import org.apache.commons.jexl2.parser.ASTMapEntry;
import org.apache.commons.jexl2.parser.ASTMapLiteral;
import org.apache.commons.jexl2.parser.ASTMethodNode;
import org.apache.commons.jexl2.parser.ASTModNode;
import org.apache.commons.jexl2.parser.ASTMulNode;
import org.apache.commons.jexl2.parser.ASTNENode;
import org.apache.commons.jexl2.parser.ASTNRNode;
import org.apache.commons.jexl2.parser.ASTNotNode;
import org.apache.commons.jexl2.parser.ASTNullLiteral;
import org.apache.commons.jexl2.parser.ASTOrNode;
import org.apache.commons.jexl2.parser.ASTReference;
import org.apache.commons.jexl2.parser.ASTSizeFunction;
import org.apache.commons.jexl2.parser.ASTSizeMethod;
import org.apache.commons.jexl2.parser.ASTStringLiteral;
import org.apache.commons.jexl2.parser.ASTTernaryNode;
import org.apache.commons.jexl2.parser.ASTTrueNode;
import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
import org.apache.commons.jexl2.parser.ASTWhileStatement;
import org.apache.commons.jexl2.parser.Node;
import org.apache.commons.jexl2.parser.ParserVisitor;
import org.apache.commons.jexl2.introspection.Uberspect;
import org.apache.commons.jexl2.introspection.JexlMethod;
import org.apache.commons.jexl2.introspection.JexlPropertyGet;
import org.apache.commons.jexl2.introspection.JexlPropertySet;
/**
* An interpreter of JEXL syntax.
*
* @since 2.0
*/
public class Interpreter implements ParserVisitor {
/** The logger. */
protected final Log logger;
/** The uberspect. */
protected final Uberspect uberspect;
/** The arithmetic handler. */
protected final JexlArithmetic arithmetic;
/** The map of registered functions. */
protected final Map<String, Object> functions;
/** The map of registered functions. */
protected Map<String, Object> functors;
/** The context to store/retrieve variables. */
protected final JexlContext context;
/** Strict interpreter flag. */
protected final boolean strict;
/** Silent intepreter flag. */
protected boolean silent;
/** Cache executors. */
protected final boolean cache;
/** Registers made of 2 pairs of {register-name, value}. */
protected Object[] registers = null;
/** Empty parameters for method matching. */
protected static final Object[] EMPTY_PARAMS = new Object[0];
/**
* Creates an interpreter.
* @param jexl the engine creating this interpreter
* @param aContext the context to evaluate expression
*/
public Interpreter(JexlEngine jexl, JexlContext aContext) {
this.logger = jexl.logger;
this.uberspect = jexl.uberspect;
this.arithmetic = jexl.arithmetic;
this.functions = jexl.functions;
this.strict = !this.arithmetic.isLenient();
this.silent = jexl.silent;
this.cache = jexl.cache != null;
this.context = aContext;
this.functors = null;
}
/**
* Sets whether this interpreter throws JexlException during evaluation.
* @param flag true means no JexlException will be thrown but will be logged
* as info through the Jexl engine logger, false allows them to be thrown.
*/
public void setSilent(boolean flag) {
this.silent = flag;
}
/**
* Checks whether this interpreter throws JexlException during evaluation.
* @return true if silent, false otherwise
*/
public boolean isSilent() {
return this.silent;
}
/**
* Interpret the given script/expression.
* <p>
* If the underlying JEXL engine is silent, errors will be logged through its logger as info.
* </p>
* @param node the script or expression to interpret.
* @return the result of the interpretation.
* @throws JexlException if any error occurs during interpretation.
*/
public Object interpret(JexlNode node) {
try {
return node.jjtAccept(this, null);
} catch (JexlException xjexl) {
if (silent) {
logger.warn(xjexl.getMessage(), xjexl.getCause());
return null;
}
throw xjexl;
}
}
/**
* Gets the uberspect.
* @return an {@link Uberspect}
*/
protected Uberspect getUberspect() {
return uberspect;
}
/**
* Sets this interpreter registers for bean access/assign expressions.
* @param theRegisters the array of registers
*/
protected void setRegisters(Object[] theRegisters) {
this.registers = theRegisters;
}
/**
* Finds the node causing a NPE for diadic operators.
* @param xrt the RuntimeException
* @param node the parent node
* @param left the left argument
* @param right the right argument
* @return the left, right or parent node
*/
protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
if (xrt instanceof NullPointerException
&& JexlException.NULL_OPERAND == xrt.getMessage()) {
if (left == null) {
return node.jjtGetChild(0);
}
if (right == null) {
return node.jjtGetChild(1);
}
}
return node;
}
/**
* Triggered when variable can not be resolved.
* @param xjexl the JexlException ("undefined variable " + variable)
* @return throws JexlException if strict, null otherwise
*/
protected Object unknownVariable(JexlException xjexl) {
if (strict) {
throw xjexl;
}
if (!silent) {
logger.warn(xjexl.getMessage());
}
return null;
}
/**
* Triggered when method, function or constructor invocation fails.
* @param xjexl the JexlException wrapping the original error
* @return throws JexlException if strict, null otherwise
*/
protected Object invocationFailed(JexlException xjexl) {
if (strict) {
throw xjexl;
}
if (!silent) {
logger.warn(xjexl.getMessage(), xjexl.getCause());
}
return null;
}
/**
* Resolves a namespace, eventually allocating an instance using context as constructor argument.
* The lifetime of such instances span the current expression or script evaluation.
*
* @param prefix the prefix name (may be null for global namespace)
* @param node the AST node
* @return the namespace instance
*/
protected Object resolveNamespace(String prefix, JexlNode node) {
Object namespace;
// check whether this namespace is a functor
if (functors != null) {
namespace = functors.get(prefix);
if (namespace != null) {
return namespace;
}
}
namespace = functions.get(prefix);
if (namespace == null) {
throw new JexlException(node, "no such function namespace " + prefix);
}
// allow namespace to be instantiated as functor with context
if (namespace instanceof Class<?>) {
Object[] args = new Object[]{context};
Constructor<?> ctor = uberspect.getConstructor(namespace,args, node);
if (ctor != null) {
try {
namespace = ctor.newInstance(args);
if (functors == null) {
functors = new HashMap<String, Object>();
}
functors.put(prefix, namespace);
} catch (Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
}
return namespace;
}
/** {@inheritDoc} */
public Object visit(ASTAdditiveNode node, Object data) {
/**
* The pattern for exception mgmt is to let the child*.jjtAccept
* out of the try/catch loop so that if one fails, the ex will
* traverse up to the interpreter.
* In cases where this is not convenient/possible, JexlException must
* be caught explicitly and rethrown.
*/
Object left = node.jjtGetChild(0).jjtAccept(this, data);
for(int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
Object right = node.jjtGetChild(c).jjtAccept(this, data);
try {
JexlNode op = node.jjtGetChild(c - 1);
if (op instanceof ASTAdditiveOperator) {
String which = ((ASTAdditiveOperator) op).image;
if ("+".equals(which)) {
left = arithmetic.add(left, right);
continue;
}
if ("-".equals(which)) {
left = arithmetic.subtract(left, right);
continue;
}
throw new UnsupportedOperationException("unknown operator " + which);
}
throw new IllegalArgumentException("unknown operator " + op);
} catch (RuntimeException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "+/- error", xrt);
}
}
return left;
}
/** {@inheritDoc} */
public Object visit(ASTAdditiveOperator node, Object data) {
throw new UnsupportedOperationException("Shoud not be called.");
}
/** {@inheritDoc} */
public Object visit(ASTAndNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
try {
boolean leftValue = arithmetic.toBoolean(left);
if (!leftValue) {
return Boolean.FALSE;
}
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
}
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
boolean rightValue = arithmetic.toBoolean(right);
if (!rightValue) {
return Boolean.FALSE;
}
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
}
return Boolean.TRUE;
}
/** {@inheritDoc} */
public Object visit(ASTArrayAccess node, Object data) {
// first objectNode is the identifier
Object object = node.jjtGetChild(0).jjtAccept(this, data);
// can have multiple nodes - either an expression, integer literal or
// reference
int numChildren = node.jjtGetNumChildren();
for (int i = 1; i < numChildren; i++) {
JexlNode nindex = node.jjtGetChild(i);
Object index = nindex.jjtAccept(this, null);
object = getAttribute(object, index, nindex);
}
return object;
}
/** {@inheritDoc} */
public Object visit(ASTArrayLiteral node, Object data) {
int childCount = node.jjtGetNumChildren();
Object[] array = new Object[childCount];
for (int i = 0; i < childCount; i++) {
Object entry = node.jjtGetChild(i).jjtAccept(this, data);
array[i] = entry;
}
return arithmetic.narrowArrayType(array);
}
/** {@inheritDoc} */
public Object visit(ASTAssignment node, Object data) {
// left contains the reference to assign to
JexlNode left = node.jjtGetChild(0);
if (!(left instanceof ASTReference)) {
throw new JexlException(left, "illegal assignment form");
}
// right is the value expression to assign
Object right = node.jjtGetChild(1).jjtAccept(this, data);
// determine initial object & property:
JexlNode objectNode = null;
Object object = null;
JexlNode propertyNode = null;
Object property = null;
boolean isVariable = true;
int v = 0;
StringBuilder variableName = null;
// 1: follow children till penultimate
int last = left.jjtGetNumChildren() - 1;
for (int c = 0; c < last; ++c) {
objectNode = left.jjtGetChild(c);
// evaluate the property within the object
object = objectNode.jjtAccept(this, object);
if (object != null) {
continue;
}
isVariable &= objectNode instanceof ASTIdentifier;
// if we get null back as a result, check for an ant variable
if (isVariable) {
if (v == 0) {
variableName = new StringBuilder(left.jjtGetChild(0).image);
v = 1;
}
for(; v <= c; ++v) {
variableName.append('.');
variableName.append(left.jjtGetChild(v).image);
}
object = context.get(variableName.toString());
// disallow mixing ant & bean with same root; avoid ambiguity
if (object != null) {
isVariable = false;
}
} else {
throw new JexlException(objectNode, "illegal assignment form");
}
}
// 2: last objectNode will perform assignement in all cases
propertyNode = left.jjtGetChild(last);
if (propertyNode instanceof ASTIdentifier) {
property = ((ASTIdentifier) propertyNode).image;
// deal with ant variable
if (isVariable && object == null) {
if (variableName != null) {
if (last > 0) {
variableName.append('.');
}
variableName.append(property);
property = variableName.toString();
}
context.set(String.valueOf(property), right);
return right;
}
} else if (propertyNode instanceof ASTIntegerLiteral) {
property = visit((ASTIntegerLiteral) propertyNode, null);
// deal with ant variable
if (isVariable && object == null) {
if (variableName != null) {
if (last > 0) {
variableName.append('.');
}
variableName.append(property);
property = variableName.toString();
}
context.set(String.valueOf(property), right);
return right;
}
} else if (propertyNode instanceof ASTArrayAccess) {
// first objectNode is the identifier
objectNode = propertyNode;
ASTArrayAccess narray = (ASTArrayAccess) objectNode;
Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
if (nobject == null) {
throw new JexlException(objectNode, "array element is null");
} else {
object = nobject;
}
// can have multiple nodes - either an expression, integer literal or
// reference
last = narray.jjtGetNumChildren() - 1;
for (int i = 1; i < last; i++) {
objectNode = narray.jjtGetChild(i);
Object index = objectNode.jjtAccept(this, null);
object = getAttribute(object, index, objectNode);
}
property = narray.jjtGetChild(last).jjtAccept(this, null);
} else {
throw new JexlException(objectNode, "illegal assignment form");
}
if (property == null) {
// no property, we fail
throw new JexlException(propertyNode, "property is null");
}
if (object == null) {
// no object, we fail
throw new JexlException(objectNode, "bean is null");
}
// one before last, assign
setAttribute(object, property, right, propertyNode);
return right;
}
/** {@inheritDoc} */
public Object visit(ASTBitwiseAndNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
int n = 0;
// coerce these two values longs and 'and'.
try {
long l = arithmetic.toLong(left);
n = 1;
long r = arithmetic.toLong(right);
return Long.valueOf(l & r);
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTBitwiseComplNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
try {
long l = arithmetic.toLong(left);
return Long.valueOf(~l);
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTBitwiseOrNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
int n = 0;
// coerce these two values longs and 'or'.
try {
long l = arithmetic.toLong(left);
n = 1;
long r = arithmetic.toLong(right);
return Long.valueOf(l | r);
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTBitwiseXorNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
int n = 0;
// coerce these two values longs and 'xor'.
try {
long l = arithmetic.toLong(left);
n = 1;
long r = arithmetic.toLong(right);
return Long.valueOf(l ^ r);
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTBlock node, Object data) {
int numChildren = node.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
result = node.jjtGetChild(i).jjtAccept(this, data);
}
return result;
}
/** {@inheritDoc} */
public Object visit(ASTDivNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.divide(left, right);
} catch (RuntimeException xrt) {
if (!strict && xrt instanceof ArithmeticException) {
return new Double(0.0);
}
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "divide error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTEmptyFunction node, Object data) {
Object o = node.jjtGetChild(0).jjtAccept(this, data);
if (o == null) {
return Boolean.TRUE;
}
if (o instanceof String && "".equals(o)) {
return Boolean.TRUE;
}
if (o.getClass().isArray() && ((Object[]) o).length == 0) {
return Boolean.TRUE;
}
if (o instanceof Collection<?> && ((Collection<?>) o).isEmpty()) {
return Boolean.TRUE;
}
// Map isn't a collection
if (o instanceof Map<?, ?> && ((Map<?, ?>) o).isEmpty()) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
/** {@inheritDoc} */
public Object visit(ASTEQNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "== error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTFalseNode node, Object data) {
return Boolean.FALSE;
}
/** {@inheritDoc} */
public Object visit(ASTFloatLiteral node, Object data) {
Float value = (Float) node.jjtGetValue();
if (value == null) {
value = Float.valueOf(node.image);
node.jjtSetValue(value);
}
return value;
}
/** {@inheritDoc} */
public 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);
/* second objectNode is the variable to iterate */
Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
// make sure there is a value to iterate on and a statement to execute
if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
/* third objectNode is the statement to execute */
JexlNode statement = node.jjtGetChild(2);
// get an iterator for the collection/array etc via the
// introspector.
Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node);
if (itemsIterator != null) {
while (itemsIterator.hasNext()) {
// set loopVariable to value of iterator
Object value = itemsIterator.next();
context.set(loopVariable.image, value);
// execute statement
result = statement.jjtAccept(this, data);
}
}
}
return result;
}
/** {@inheritDoc} */
public Object visit(ASTGENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, ">= error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTGTNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "> error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTERNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "=~ error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTIdentifier node, Object data) {
String name = node.image;
if (data == null) {
if (registers != null) {
if (registers[0].equals(name)) {
return registers[1];
}
if (registers[2].equals(name)) {
return registers[3];
}
}
Object value = context.get(name);
if (value == null
&& !(node.jjtGetParent() instanceof ASTReference)
&& !context.has(name)) {
JexlException xjexl = new JexlException(node, "undefined variable " + name);
return unknownVariable(xjexl);
}
return value;
} else {
return getAttribute(data, name, node);
}
}
/** {@inheritDoc} */
public Object visit(ASTIfStatement node, Object data) {
int n = 0;
try {
Object result = null;
/* first objectNode is the expression */
Object expression = node.jjtGetChild(0).jjtAccept(this, data);
if (arithmetic.toBoolean(expression)) {
// first objectNode is true statement
n = 1;
result = node.jjtGetChild(1).jjtAccept(this, data);
} else {
// if there is a false, execute it. false statement is the second
// objectNode
if (node.jjtGetNumChildren() == 3) {
n = 2;
result = node.jjtGetChild(2).jjtAccept(this, data);
}
}
return result;
} catch (JexlException error) {
throw error;
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(n), "if error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTIntegerLiteral node, Object data) {
if (data != null) {
Integer value = Integer.valueOf(node.image);
return getAttribute(data, value, node);
}
Integer value = (Integer) node.jjtGetValue();
if (value == null) {
value = Integer.valueOf(node.image);
node.jjtSetValue(value);
}
return value;
}
/** {@inheritDoc} */
public Object visit(ASTJexlScript node, Object data) {
int numChildren = node.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
JexlNode child = node.jjtGetChild(i);
result = child.jjtAccept(this, data);
}
return result;
}
/** {@inheritDoc} */
public Object visit(ASTLENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "<= error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTLTNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "< error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTMapEntry node, Object data) {
Object key = node.jjtGetChild(0).jjtAccept(this, data);
Object value = node.jjtGetChild(1).jjtAccept(this, data);
return new Object[]{key, value};
}
/** {@inheritDoc} */
public Object visit(ASTMapLiteral node, Object data) {
int childCount = node.jjtGetNumChildren();
Map<Object, Object> map = new HashMap<Object, Object>();
for (int i = 0; i < childCount; i++) {
Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
map.put(entry[0], entry[1]);
}
return map;
}
/** {@inheritDoc} */
public Object visit(ASTMethodNode node, Object data) {
// the object to invoke the method on should be in the data argument
if (data == null) {
// if the first child of the (ASTReference) parent,
// it is considered as calling a 'top level' function
if (node.jjtGetParent().jjtGetChild(0) == node) {
data = resolveNamespace(null, node);
if (data == null) {
throw new JexlException(node, "no default function namespace");
}
} else {
throw new JexlException(node, "attempting to call method on null");
}
}
// objectNode 0 is the identifier (method name), the others are parameters.
String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
// get our arguments
int argc = node.jjtGetNumChildren() - 1;
Object[] argv = new Object[argc];
for (int i = 0; i < argc; i++) {
argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
}
JexlException xjexl = null;
try {
// attempt to reuse last executor cached in volatile JexlNode.value
if (cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlMethod) {
JexlMethod me = (JexlMethod) cached;
Object eval = me.tryInvoke(methodName, data, argv);
if (!me.tryFailed(eval)) {
return eval;
}
}
}
JexlMethod vm = uberspect.getMethod(data, methodName, argv, node);
// DG: If we can't find an exact match, narrow the parameters and try again!
if (vm == null) {
if (arithmetic.narrowArguments(argv)) {
vm = uberspect.getMethod(data, methodName, argv, node);
}
if (vm == null) {
xjexl = new JexlException(node, "unknown or ambiguous method", null);
}
}
if (xjexl == null) {
Object eval = vm.invoke(data, argv); // vm cannot be null if xjexl is null
// cache executor in volatile JexlNode.value
if (cache && vm.isCacheable()) {
node.jjtSetValue(vm);
}
return eval;
}
} catch (InvocationTargetException e) {
xjexl = new JexlException(node, "method invocation error", e.getCause());
} catch (Exception e) {
xjexl = new JexlException(node, "method error", e);
}
return invocationFailed(xjexl);
}
/** {@inheritDoc} */
public Object visit(ASTConstructorNode node, Object data) {
// first child is class or class name
Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
// get the ctor args
int argc = node.jjtGetNumChildren() - 1;
Object[] argv = new Object[argc];
for (int i = 0; i < argc; i++) {
argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
}
JexlException xjexl = null;
try {
Constructor<?> ctor = uberspect.getConstructor(cobject, argv, node);
// DG: If we can't find an exact match, narrow the parameters and
// try again!
if (ctor == null) {
if (arithmetic.narrowArguments(argv)) {
ctor = uberspect.getConstructor(cobject, argv, node);
}
if (ctor == null) {
xjexl = new JexlException(node, "unknown constructor", null);
}
}
if (xjexl == null) {
return ctor.newInstance(argv);
}
} catch (InvocationTargetException e) {
xjexl = new JexlException(node, "constructor invocation error", e.getCause());
} catch (Exception e) {
xjexl = new JexlException(node, "constructor error", e);
}
return invocationFailed(xjexl);
}
/** {@inheritDoc} */
public Object visit(ASTFunctionNode node, Object data) {
// objectNode 0 is the prefix
String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image;
Object namespace = resolveNamespace(prefix, node);
// objectNode 1 is the identifier , the others are parameters.
String function = ((ASTIdentifier) node.jjtGetChild(1)).image;
// get our args
int argc = node.jjtGetNumChildren() - 2;
Object[] argv = new Object[argc];
for (int i = 0; i < argc; i++) {
argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
}
JexlException xjexl = null;
try {
// attempt to reuse last executor cached in volatile JexlNode.value
if (cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlMethod) {
JexlMethod me = (JexlMethod) cached;
Object eval = me.tryInvoke(function, namespace, argv);
if (!me.tryFailed(eval)) {
return eval;
}
}
}
JexlMethod vm = uberspect.getMethod(namespace, function, argv, node);
// DG: If we can't find an exact match, narrow the parameters and
// try again!
if (vm == null) {
// replace all numbers with the smallest type that will fit
if (arithmetic.narrowArguments(argv)) {
vm = uberspect.getMethod(namespace, function, argv, node);
}
if (vm == null) {
xjexl = new JexlException(node, "unknown function", null);
}
}
if (xjexl == null) {
Object eval = vm.invoke(namespace, argv); // vm cannot be null if xjexl is null
// cache executor in volatile JexlNode.value
if (cache && vm.isCacheable()) {
node.jjtSetValue(vm);
}
return eval;
}
} catch (InvocationTargetException e) {
xjexl = new JexlException(node, "function invocation error", e.getCause());
} catch (Exception e) {
xjexl = new JexlException(node, "function error", e);
}
return invocationFailed(xjexl);
}
/** {@inheritDoc} */
public Object visit(ASTModNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.mod(left, right);
} catch (RuntimeException xrt) {
if (!strict && xrt instanceof ArithmeticException) {
return new Double(0.0);
}
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "% error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTMulNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.multiply(left, right);
} catch (RuntimeException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "* error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTNENode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
} catch (RuntimeException xrt) {
JexlNode xnode = findNullOperand(xrt, node, left, right);
throw new JexlException(xnode, "!= error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTNRNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
} catch (RuntimeException xrt) {
throw new JexlException(node, "!~ error", xrt);
}
}
/** {@inheritDoc} */
public Object visit(ASTNotNode node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
}
/** {@inheritDoc} */
public Object visit(ASTNullLiteral node, Object data) {
return null;
}
/** {@inheritDoc} */
public Object visit(ASTOrNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
try {
boolean leftValue = arithmetic.toBoolean(left);
if (leftValue) {
return Boolean.TRUE;
}
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
}
Object right = node.jjtGetChild(1).jjtAccept(this, data);
try {
boolean rightValue = arithmetic.toBoolean(right);
if (rightValue) {
return Boolean.TRUE;
}
} catch (RuntimeException xrt) {
throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
}
return Boolean.FALSE;
}
/** {@inheritDoc} */
public Object visit(ASTReference node, Object data) {
// could be array access, identifier or map literal
// followed by zero or more ("." and array access, method, size,
// identifier or integer literal)
int numChildren = node.jjtGetNumChildren();
// pass first piece of data in and loop through children
Object result = null;
StringBuilder variableName = null;
boolean isVariable = true;
int v = 0;
for (int c = 0; c < numChildren; c++) {
JexlNode theNode = node.jjtGetChild(c);
isVariable &= (theNode instanceof ASTIdentifier);
result = theNode.jjtAccept(this, result);
// if we get null back a result, check for an ant variable
if (result == null && isVariable) {
if (v == 0) {
variableName = new StringBuilder(node.jjtGetChild(0).image);
v = 1;
}
for(; v <= c; ++v) {
variableName.append('.');
variableName.append(node.jjtGetChild(v).image);
}
result = context.get(variableName.toString());
}
}
if (result == null) {
if (isVariable
&& !(node.jjtGetParent() instanceof ASTTernaryNode)
&& !context.has(variableName.toString())) {
JexlException xjexl = new JexlException(node, "undefined variable " + variableName.toString());
return unknownVariable(xjexl);
}
}
return result;
}
/** {@inheritDoc} */
public Object visit(ASTSizeFunction node, Object data) {
Object val = node.jjtGetChild(0).jjtAccept(this, data);
if (val == null) {
throw new JexlException(node, "size() : argument is null", null);
}
return Integer.valueOf(sizeOf(node, val));
}
/** {@inheritDoc} */
public Object visit(ASTSizeMethod node, Object data) {
return Integer.valueOf(sizeOf(node, data));
}
/** {@inheritDoc} */
public Object visit(ASTStringLiteral node, Object data) {
return node.image;
}
/** {@inheritDoc} */
public Object visit(ASTTernaryNode node, Object data) {
Object condition = node.jjtGetChild(0).jjtAccept(this, data);
if (node.jjtGetNumChildren() == 3) {
if (condition != null && arithmetic.toBoolean(condition)) {
return node.jjtGetChild(1).jjtAccept(this, data);
} else {
return node.jjtGetChild(2).jjtAccept(this, data);
}
}
if (condition != null && !Boolean.FALSE.equals(condition)) {
return condition;
} else {
return node.jjtGetChild(1).jjtAccept(this, data);
}
}
/** {@inheritDoc} */
public Object visit(ASTTrueNode node, Object data) {
return Boolean.TRUE;
}
/** {@inheritDoc} */
public Object visit(ASTUnaryMinusNode node, Object data) {
JexlNode valNode = node.jjtGetChild(0);
Object val = valNode.jjtAccept(this, data);
if (val instanceof Byte) {
byte valueAsByte = ((Byte) val).byteValue();
return Byte.valueOf((byte) -valueAsByte);
} else if (val instanceof Short) {
short valueAsShort = ((Short) val).shortValue();
return Short.valueOf((short) -valueAsShort);
} else if (val instanceof Integer) {
int valueAsInt = ((Integer) val).intValue();
return Integer.valueOf(-valueAsInt);
} else if (val instanceof Long) {
long valueAsLong = ((Long) val).longValue();
return Long.valueOf(-valueAsLong);
} else if (val instanceof Float) {
float valueAsFloat = ((Float) val).floatValue();
return new Float(-valueAsFloat);
} else if (val instanceof Double) {
double valueAsDouble = ((Double) val).doubleValue();
return new Double(-valueAsDouble);
} else if (val instanceof BigDecimal) {
BigDecimal valueAsBigD = (BigDecimal) val;
return valueAsBigD.negate();
} else if (val instanceof BigInteger) {
BigInteger valueAsBigI = (BigInteger) val;
return valueAsBigI.negate();
}
throw new JexlException(valNode, "not a number");
}
/** {@inheritDoc} */
public Object visit(ASTWhileStatement node, Object data) {
Object result = null;
/* first objectNode is the expression */
Node expressionNode = node.jjtGetChild(0);
while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
// execute statement
result = node.jjtGetChild(1).jjtAccept(this, data);
}
return result;
}
/**
* Calculate the <code>size</code> of various types: Collection, Array,
* Map, String, and anything that has a int size() method.
* @param node the node that gave the value to size
* @param val the object to get the size of.
* @return the size of val
*/
private int sizeOf(JexlNode node, Object val) {
if (val instanceof Collection<?>) {
return ((Collection<?>) val).size();
} else if (val.getClass().isArray()) {
return Array.getLength(val);
} else if (val instanceof Map<?, ?>) {
return ((Map<?, ?>) val).size();
} else if (val instanceof String) {
return ((String) val).length();
} else {
// check if there is a size method on the object that returns an
// integer and if so, just use it
Object[] params = new Object[0];
JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
if (vm != null && vm.getReturnType() == Integer.TYPE) {
Integer result;
try {
result = (Integer) vm.invoke(val, params);
} catch (Exception e) {
throw new JexlException(node, "size() : error executing", e);
}
return result.intValue();
}
throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
}
}
/**
* Gets an attribute of an object.
*
* @param object to retrieve value from
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
* key for a map
* @return the attribute value
*/
public Object getAttribute(Object object, Object attribute) {
return getAttribute(object, attribute, null);
}
/**
* 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");
}
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertyGet) {
JexlPropertyGet vg = (JexlPropertyGet) cached;
Object value = vg.tryInvoke(object, attribute);
if (!vg.tryFailed(value)) {
return value;
}
}
}
JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
if (vg != null) {
try {
Object value = vg.invoke(object);
// cache executor in volatile JexlNode.value
if (node != null && cache && vg.isCacheable()) {
node.jjtSetValue(vg);
}
return value;
} catch (Exception xany) {
if (node == null) {
throw new RuntimeException(xany);
} else {
JexlException xjexl = new JexlException(node, "get object property error", xany);
if (strict) {
throw xjexl;
}
if (!silent) {
logger.warn(xjexl.getMessage());
}
}
}
}
return null;
}
/**
* Sets an attribute of an object.
*
* @param object to set the value to
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
* key for a map
* @param value the value to assign to the object's attribute
*/
public void setAttribute(Object object, Object attribute, Object value) {
setAttribute(object, attribute, value, null);
}
/**
* Sets an attribute of an object.
*
* @param object to set the value to
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
* key for a map
* @param value the value to assign to the object's attribute
* @param node the node that evaluated as the object
*/
protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertySet) {
JexlPropertySet setter = (JexlPropertySet) cached;
Object eval = setter.tryInvoke(object, attribute, value);
if (!setter.tryFailed(eval)) {
return;
}
}
}
JexlException xjexl = null;
JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
if (vs != null) {
try {
// cache executor in volatile JexlNode.value
vs.invoke(object, value);
if (node != null && cache && vs.isCacheable()) {
node.jjtSetValue(vs);
}
return;
} catch (RuntimeException xrt) {
if (node == null) {
throw xrt;
}
xjexl = new JexlException(node, "set object property error", xrt);
} catch (Exception xany) {
if (node == null) {
throw new RuntimeException(xany);
}
xjexl = new JexlException(node, "set object property error", xany);
}
}
if (xjexl == null) {
String error = "unable to set object property"
+ ", class: " + object.getClass().getName()
+ ", property: " + attribute;
if (node == null) {
throw new UnsupportedOperationException(error);
}
xjexl = new JexlException(node, error, null);
}
if (strict) {
throw xjexl;
}
if (!silent) {
logger.warn(xjexl.getMessage());
}
}
/**
* Unused, satisfy ParserVisitor interface.
* @param node a node
* @param data the data
* @return does not return
*/
public Object visit(SimpleNode node, Object data) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Unused, should throw in Parser.
* @param node a node
* @param data the data
* @return does not return
*/
public Object visit(ASTAmbiguous node, Object data) {
throw new UnsupportedOperationException("unexpected type of node");
}
}