| /* |
| * 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; |
| |
| import java.util.Iterator; |
| /** |
| * 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) { |
| interpreter.operatorError(node, operator, xany); |
| } |
| } |
| return JexlEngine.TRY_FAILED; |
| } |
| |
| /** |
| * Attempts to call an overloaded forEach operator. |
| * <p> |
| * This takes care of finding and caching the operator method when appropriate |
| * @param node the syntactic node |
| * @param arg the argument |
| * @return the result of the operator evaluation or TRY_FAILED |
| */ |
| protected Object tryForeachOverload(JexlNode node, Object arg) { |
| if (operators != null && operators.overloads(JexlOperator.FOR_EACH)) { |
| 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(JexlOperator.FOR_EACH.getMethodName(), arithmetic, arg); |
| if (!me.tryFailed(eval)) { |
| return eval; |
| } |
| } |
| } |
| try { |
| JexlMethod vm = operators.getOperator(JexlOperator.FOR_EACH, arg); |
| if (vm != null && Iterator.class.isAssignableFrom(vm.getReturnType())) { |
| Object result = vm.invoke(arithmetic, arg); |
| if (cache) { |
| node.jjtSetValue(vm); |
| } |
| return result; |
| } |
| } catch (Exception xany) { |
| interpreter.operatorError(node, JexlOperator.FOR_EACH, 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 <=> 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; |
| } |
| } |