blob: ed5fd38d2d8bec5904c50d5a6119e4a13da500d4 [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.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.JexlNode;
/**
* Helper class to deal with operator overloading and specifics.
* @since 3.0
*/
public class Operators {
/** The owner. */
protected final Interpreter interpreter;
/** The overloaded arithmetic operators. */
protected final JexlArithmetic.Uberspect operators;
/**
* Constructor.
* @param owner the owning interpreter
*/
protected Operators(Interpreter owner) {
final JexlArithmetic arithmetic = owner.arithmetic;
final JexlUberspect uberspect = owner.uberspect;
this.interpreter = owner;
this.operators = uberspect.getArithmetic(arithmetic);
}
/**
* Checks whether a method returns a boolean or a Boolean.
* @param vm the JexlMethod (may be null)
* @return true of false
*/
private boolean returnsBoolean(JexlMethod vm) {
if (vm !=null) {
Class<?> rc = vm.getReturnType();
return Boolean.TYPE.equals(rc) || Boolean.class.equals(rc);
}
return false;
}
/**
* Attempts to call an operator.
* <p>
* This takes care of finding and caching the operator method when appropriate
* @param node the syntactic node
* @param operator the operator
* @param args the arguments
* @return the result of the operator evaluation or TRY_FAILED
*/
protected Object tryOverload(JexlNode node, JexlOperator operator, Object... args) {
if (operators != null && operators.overloads(operator)) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
final boolean cache = interpreter.cache;
if (cache) {
Object cached = node.jjtGetValue();
if (cached instanceof JexlMethod) {
JexlMethod me = (JexlMethod) cached;
Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
if (!me.tryFailed(eval)) {
return eval;
}
}
}
try {
JexlMethod vm = operators.getOperator(operator, args);
if (vm != null) {
Object result = vm.invoke(arithmetic, args);
if (cache) {
node.jjtSetValue(vm);
}
return result;
}
} catch (Exception xany) {
return interpreter.operatorError(node, operator, xany);
}
}
return JexlEngine.TRY_FAILED;
}
/**
* Evaluates an assign operator.
* <p>
* This takes care of finding and caching the operator method when appropriate.
* If an overloads returns Operator.ASSIGN, it means the side-effect is complete.
* Otherwise, a += b &lt;=&gt; a = a + b
* </p>
* @param node the syntactic node
* @param operator the operator
* @param args the arguments, the first one being the target of assignment
* @return the result of the operator evaluation
*/
protected Object tryAssignOverload(JexlNode node, JexlOperator operator, Object...args) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
if (args.length != operator.getArity()) {
return JexlEngine.TRY_FAILED;
}
// try to call overload on side effect
Object result = tryOverload(node, operator, args);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
// call base operator
JexlOperator base = operator.getBaseOperator();
if (base == null) {
throw new IllegalArgumentException("must be called with a side-effect operator");
}
if (operators != null && operators.overloads(base)) {
// in case there is an overload
try {
JexlMethod vm = operators.getOperator(base, args);
if (vm != null) {
result = vm.invoke(arithmetic, args);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
}
} catch (Exception xany) {
interpreter.operatorError(node, base, xany);
}
}
// base eval
switch (operator) {
case SELF_ADD:
return arithmetic.add(args[0], args[1]);
case SELF_SUBTRACT:
return arithmetic.subtract(args[0], args[1]);
case SELF_MULTIPLY:
return arithmetic.multiply(args[0], args[1]);
case SELF_DIVIDE:
return arithmetic.divide(args[0], args[1]);
case SELF_MOD:
return arithmetic.mod(args[0], args[1]);
case SELF_AND:
return arithmetic.and(args[0], args[1]);
case SELF_OR:
return arithmetic.or(args[0], args[1]);
case SELF_XOR:
return arithmetic.xor(args[0], args[1]);
default:
throw new JexlException.Operator(node, operator.getOperatorSymbol(), null);
}
}
/**
* The 'startsWith' operator implementation.
* @param node the node
* @param operator the calling operator, $= or $!
* @param left the left operand
* @param right the right operand
* @return true if left starts with right, false otherwise
*/
protected boolean startsWith(JexlNode node, String operator, Object left, Object right) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
final JexlUberspect uberspect = interpreter.uberspect;
try {
// try operator overload
Object result = tryOverload(node, JexlOperator.STARTSWITH, left, right);
if (result instanceof Boolean) {
return (Boolean) result;
}
// use arithmetic / pattern matching ?
Boolean matched = arithmetic.startsWith(left, right);
if (matched != null) {
return matched;
}
// try a startsWith method (duck type)
try {
Object[] argv = {right};
JexlMethod vm = uberspect.getMethod(left, "startsWith", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
} else if (arithmetic.narrowArguments(argv)) {
vm = uberspect.getMethod(left, "startsWith", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
}
}
} catch (Exception e) {
throw new JexlException(node, operator + " error", e);
}
// defaults to equal
return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, operator + " error", xrt);
}
}
/**
* The 'endsWith' operator implementation.
* @param node the node
* @param operator the calling operator, ^= or ^!
* @param left the left operand
* @param right the right operand
* @return true if left ends with right, false otherwise
*/
protected boolean endsWith(JexlNode node, String operator, Object left, Object right) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
final JexlUberspect uberspect = interpreter.uberspect;
try {
// try operator overload
Object result = tryOverload(node, JexlOperator.ENDSWITH, left, right);
if (result instanceof Boolean) {
return (Boolean) result;
}
// use arithmetic / pattern matching ?
Boolean matched = arithmetic.endsWith(left, right);
if (matched != null) {
return matched;
}
// try a endsWith method (duck type)
try {
Object[] argv = {right};
JexlMethod vm = uberspect.getMethod(left, "endsWith", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
} else if (arithmetic.narrowArguments(argv)) {
vm = uberspect.getMethod(left, "endsWith", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
}
}
} catch (Exception e) {
throw new JexlException(node, operator + " error", e);
}
// defaults to equal
return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
} catch (ArithmeticException xrt) {
throw new JexlException(node, operator + " error", xrt);
}
}
/**
* The 'match'/'in' operator implementation.
* <p>
* Note that 'x in y' or 'x matches y' means 'y contains x' ;
* the JEXL operator arguments order syntax is the reverse of this method call.
* </p>
* @param node the node
* @param op the calling operator, =~ or !~
* @param right the left operand
* @param left the right operand
* @return true if left matches right, false otherwise
*/
protected boolean contains(JexlNode node, String op, Object left, Object right) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
final JexlUberspect uberspect = interpreter.uberspect;
try {
// try operator overload
Object result = tryOverload(node, JexlOperator.CONTAINS, left, right);
if (result instanceof Boolean) {
return (Boolean) result;
}
// use arithmetic / pattern matching ?
Boolean matched = arithmetic.contains(left, right);
if (matched != null) {
return matched;
}
// try a contains method (duck type set)
try {
Object[] argv = {right};
JexlMethod vm = uberspect.getMethod(left, "contains", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
} else if (arithmetic.narrowArguments(argv)) {
vm = uberspect.getMethod(left, "contains", argv);
if (returnsBoolean(vm)) {
return (Boolean) vm.invoke(left, argv);
}
}
} catch (Exception e) {
throw new JexlException(node, op + " error", e);
}
// defaults to equal
return arithmetic.equals(left, right);
} catch (ArithmeticException xrt) {
throw new JexlException(node, op + " error", xrt);
}
}
/**
* Check for emptyness of various types: Collection, Array, Map, String, and anything that has a boolean isEmpty()
* method.
* <p>Note that the result may not be a boolean.
*
* @param node the node holding the object
* @param object the object to check the emptyness of
* @return the evaluation result
*/
protected Object empty(JexlNode node, Object object) {
if (object == null) {
return Boolean.TRUE;
}
final JexlArithmetic arithmetic = interpreter.arithmetic;
final JexlUberspect uberspect = interpreter.uberspect;
Object result = Operators.this.tryOverload(node, JexlOperator.EMPTY, object);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
result = arithmetic.isEmpty(object);
if (result == null) {
result = false;
// check if there is an isEmpty method on the object that returns a
// boolean and if so, just use it
JexlMethod vm = uberspect.getMethod(object, "isEmpty", Interpreter.EMPTY_PARAMS);
if (returnsBoolean(vm)) {
try {
result = (Boolean) vm.invoke(object, Interpreter.EMPTY_PARAMS);
} catch (Exception xany) {
interpreter.operatorError(node, JexlOperator.EMPTY, xany);
}
}
}
return result;
}
/**
* Calculate the <code>size</code> of various types:
* Collection, Array, Map, String, and anything that has a int size() method.
* <p>Note that the result may not be an integer.
*
* @param node the node that gave the value to size
* @param object the object to get the size of.
* @return the evaluation result
*/
protected Object size(JexlNode node, Object object) {
if (object == null) {
return 0;
}
final JexlArithmetic arithmetic = interpreter.arithmetic;
final JexlUberspect uberspect = interpreter.uberspect;
Object result = Operators.this.tryOverload(node, JexlOperator.SIZE, object);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
result = arithmetic.size(object);
if (result == null) {
// check if there is a size method on the object that returns an
// integer and if so, just use it
JexlMethod vm = uberspect.getMethod(object, "size", Interpreter.EMPTY_PARAMS);
if (vm != null && (Integer.TYPE.equals(vm.getReturnType()) || Integer.class.equals(vm.getReturnType()))) {
try {
result = (Integer) vm.invoke(object, Interpreter.EMPTY_PARAMS);
} catch (Exception xany) {
interpreter.operatorError(node, JexlOperator.SIZE, xany);
}
}
}
return result;
}
}