View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import org.apache.commons.jexl3.JexlArithmetic;
20  import org.apache.commons.jexl3.JexlEngine;
21  import org.apache.commons.jexl3.JexlException;
22  import org.apache.commons.jexl3.JexlOperator;
23  import org.apache.commons.jexl3.internal.introspection.MethodExecutor;
24  import org.apache.commons.jexl3.introspection.JexlMethod;
25  import org.apache.commons.jexl3.introspection.JexlUberspect;
26  import org.apache.commons.jexl3.parser.JexlNode;
27  
28  import java.lang.reflect.Method;
29  import java.util.function.Consumer;
30  
31  /**
32   * Helper class to deal with operator overloading and specifics.
33   * @since 3.0
34   */
35  public class Operators {
36      /** The owner. */
37      protected final InterpreterBase interpreter;
38      /** The overloaded arithmetic operators. */
39      protected final JexlArithmetic.Uberspect operators;
40  
41      /**
42       * Constructor.
43       * @param owner the owning interpreter
44       */
45      protected Operators(final InterpreterBase owner) {
46          final JexlArithmetic arithmetic = owner.arithmetic;
47          final JexlUberspect uberspect = owner.uberspect;
48          this.interpreter = owner;
49          this.operators = uberspect.getArithmetic(arithmetic);
50      }
51  
52      /**
53       * Checks whether a method returns a boolean or a Boolean.
54       * @param vm the JexlMethod (may be null)
55       * @return true of false
56       */
57      private boolean returnsBoolean(final JexlMethod vm) {
58          if (vm !=null) {
59              final Class<?> rc = vm.getReturnType();
60              return Boolean.TYPE.equals(rc) || Boolean.class.equals(rc);
61          }
62          return false;
63      }
64  
65      /**
66       * Checks whether a method returns an int or an Integer.
67       * @param vm the JexlMethod (may be null)
68       * @return true of false
69       */
70      private boolean returnsInteger(final JexlMethod vm) {
71          if (vm !=null) {
72              final Class<?> rc = vm.getReturnType();
73              return Integer.TYPE.equals(rc) || Integer.class.equals(rc);
74          }
75          return false;
76      }
77  
78      /**
79       * Checks whether a method is a JexlArithmetic method.
80       * @param vm the JexlMethod (may be null)
81       * @return true of false
82       */
83      private boolean isArithmetic(final JexlMethod vm) {
84          if (vm instanceof MethodExecutor) {
85              final Method method = ((MethodExecutor) vm).getMethod();
86              return JexlArithmetic.class.equals(method.getDeclaringClass());
87          }
88          return false;
89      }
90  
91      /**
92       * Throw a NPE if operator is strict and one of the arguments is null.
93       * @param arithmetic the JEXL arithmetic instance
94       * @param operator the operator to check
95       * @param args the operands
96       * @throws JexlArithmetic.NullOperand if operator is strict and an operand is null
97       */
98      protected void controlNullOperands(final JexlArithmetic arithmetic, final JexlOperator operator, final Object...args) {
99          for (final Object arg : args) {
100             // only check operator if necessary
101             if (arg == null) {
102                 // check operator only once if it is not strict
103                 if (arithmetic.isStrict(operator)) {
104                     throw new JexlArithmetic.NullOperand();
105                 }
106                 break;
107             }
108         }
109     }
110 
111     /**
112      * Attempts to call an operator.
113      * <p>
114      *     This performs the null argument control against the strictness of the operator.
115      * </p>
116      * <p>
117      * This takes care of finding and caching the operator method when appropriate.
118      * </p>
119      * @param node     the syntactic node
120      * @param operator the operator
121      * @param args     the arguments
122      * @return the result of the operator evaluation or TRY_FAILED
123      */
124     protected Object tryOverload(final JexlNode node, final JexlOperator operator, final Object... args) {
125         final JexlArithmetic arithmetic = interpreter.arithmetic;
126         controlNullOperands(arithmetic, operator, args);
127         if (operators != null && operators.overloads(operator)) {
128             final boolean cache = interpreter.cache;
129             try {
130                 if (cache) {
131                     final Object cached = node.jjtGetValue();
132                     if (cached instanceof JexlMethod) {
133                         final JexlMethod me = (JexlMethod) cached;
134                         final Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
135                         if (!me.tryFailed(eval)) {
136                             return eval;
137                         }
138                     }
139                 }
140                 final JexlMethod vm = operators.getOperator(operator, args);
141                 if (vm != null && !isArithmetic(vm)) {
142                     final Object result = vm.invoke(arithmetic, args);
143                     if (cache && !vm.tryFailed(result)) {
144                         node.jjtSetValue(vm);
145                     }
146                     return result;
147                 }
148             } catch (final Exception xany) {
149                 // ignore return if lenient, will return try_failed
150                 interpreter.operatorError(node, operator, xany);
151             }
152         }
153         return JexlEngine.TRY_FAILED;
154     }
155 
156     /**
157      * Helper for postfix assignment operators.
158      * @param operator the operator
159      * @return true if operator is a postfix operator (x++, y--)
160      */
161     private static boolean isPostfix(final JexlOperator operator) {
162         return operator == JexlOperator.GET_AND_INCREMENT || operator == JexlOperator.GET_AND_DECREMENT;
163     }
164 
165     /**
166      * Tidy arguments based on operator arity.
167      * <p>The interpreter may add a null to the arguments of operator expecting only one parameter.</p>
168      * @param operator the operator
169      * @param args the arguements (as seen by the interpreter)
170      * @return the tidied arguments
171      */
172     private Object[] arguments(final JexlOperator operator, final Object...args) {
173         return operator.getArity() == 1 && args.length > 1 ? new Object[]{args[0]} : args;
174     }
175 
176     /**
177      * Evaluates an assign operator.
178      * <p>
179      * This takes care of finding and caching the operator method when appropriate.
180      * If an overloads returns Operator.ASSIGN, it means the side-effect is complete.
181      * Otherwise, a += b &lt;=&gt; a = a + b
182      * </p>
183      * @param node     the syntactic node
184      * @param operator the operator
185      * @param args     the arguments, the first one being the target of assignment
186      * @return JexlOperator.ASSIGN if operation assignment has been performed,
187      *         JexlEngine.TRY_FAILED if no operation was performed,
188      *         the value to use as the side effect argument otherwise
189      */
190     protected Object tryAssignOverload(final JexlNode node,
191                                        final JexlOperator operator,
192                                        final Consumer<Object> assignFun,
193                                        final Object...args) {
194         final JexlArithmetic arithmetic = interpreter.arithmetic;
195         if (args.length < operator.getArity()) {
196             return JexlEngine.TRY_FAILED;
197         }
198         Object result;
199         try {
200         // if some overloads exist...
201         if (operators != null) {
202             // try to call overload with side effect; the object is modified
203             result = tryOverload(node, operator, arguments(operator, args));
204             if (result != JexlEngine.TRY_FAILED) {
205                 return result; // 1
206             }
207             // try to call base overload (ie + for +=)
208             final JexlOperator base = operator.getBaseOperator();
209             if (base != null && operators.overloads(base)) {
210                 result = tryOverload(node, base, arguments(base, args));
211                 if (result != JexlEngine.TRY_FAILED) {
212                     assignFun.accept(result);
213                     return isPostfix(operator) ? args[0] : result; // 2
214                 }
215             }
216         }
217         // base eval
218         switch (operator) {
219             case SELF_ADD:
220                 result = arithmetic.add(args[0], args[1]);
221                 break;
222             case SELF_SUBTRACT:
223                 result = arithmetic.subtract(args[0], args[1]);
224                 break;
225             case SELF_MULTIPLY:
226                 result = arithmetic.multiply(args[0], args[1]);
227                 break;
228             case SELF_DIVIDE:
229                 result = arithmetic.divide(args[0], args[1]);
230                 break;
231             case SELF_MOD:
232                 result = arithmetic.mod(args[0], args[1]);
233                 break;
234             case SELF_AND:
235                 result = arithmetic.and(args[0], args[1]);
236                 break;
237             case SELF_OR:
238                 result = arithmetic.or(args[0], args[1]);
239                 break;
240             case SELF_XOR:
241                 result = arithmetic.xor(args[0], args[1]);
242                 break;
243             case SELF_SHIFTLEFT:
244                 result = arithmetic.shiftLeft(args[0], args[1]);
245                 break;
246             case SELF_SHIFTRIGHT:
247                 result = arithmetic.shiftRight(args[0], args[1]);
248                 break;
249             case SELF_SHIFTRIGHTU:
250                 result = arithmetic.shiftRightUnsigned(args[0], args[1]);
251                 break;
252             case INCREMENT_AND_GET:
253                 result = arithmetic.increment(args[0]);
254                 break;
255             case DECREMENT_AND_GET:
256                 result = arithmetic.decrement(args[0]);
257                 break;
258             case GET_AND_INCREMENT:
259                 result = args[0];
260                 assignFun.accept(arithmetic.increment(result));
261                 return result; // 3
262             case GET_AND_DECREMENT: {
263                 result = args[0];
264                 assignFun.accept(arithmetic.decrement(result));
265                 return result; // 4
266             }
267             default:
268                 // unexpected, new operator added?
269                 throw new UnsupportedOperationException(operator.getOperatorSymbol());
270             }
271             assignFun.accept(result);
272             return result; // 5
273         } catch (final Exception xany) {
274             interpreter.operatorError(node, operator, xany);
275         }
276         return JexlEngine.TRY_FAILED;
277     }
278 
279     /**
280      * The 'startsWith' operator implementation.
281      * @param node     the node
282      * @param operator the calling operator, $= or $!
283      * @param left     the left operand
284      * @param right    the right operand
285      * @return true if left starts with right, false otherwise
286      */
287     protected boolean startsWith(final JexlNode node, final String operator, final Object left, final Object right) {
288         final JexlArithmetic arithmetic = interpreter.arithmetic;
289         final JexlUberspect uberspect = interpreter.uberspect;
290         try {
291             // try operator overload
292             final Object result = tryOverload(node, JexlOperator.STARTSWITH, left, right);
293             if (result instanceof Boolean) {
294                 return (Boolean) result;
295             }
296             // use arithmetic / pattern matching ?
297             final Boolean matched = arithmetic.startsWith(left, right);
298             if (matched != null) {
299                 return matched;
300             }
301             // try a startsWith method (duck type)
302             try {
303                 final Object[] argv = {right};
304                 JexlMethod vm = uberspect.getMethod(left, "startsWith", argv);
305                 if (returnsBoolean(vm)) {
306                     return (Boolean) vm.invoke(left, argv);
307                 }
308                 if (arithmetic.narrowArguments(argv)) {
309                     vm = uberspect.getMethod(left, "startsWith", argv);
310                     if (returnsBoolean(vm)) {
311                         return (Boolean) vm.invoke(left, argv);
312                     }
313                 }
314             } catch (final Exception e) {
315                 throw new JexlException(node, operator + " error", e);
316             }
317             // defaults to equal
318             return arithmetic.equals(left, right);
319         } catch (final ArithmeticException xrt) {
320             throw new JexlException(node, operator + " error", xrt);
321         }
322     }
323 
324     /**
325      * The 'endsWith' operator implementation.
326      * @param node     the node
327      * @param operator the calling operator, ^= or ^!
328      * @param left     the left operand
329      * @param right    the right operand
330      * @return true if left ends with right, false otherwise
331      */
332     protected boolean endsWith(final JexlNode node, final String operator, final Object left, final Object right) {
333         final JexlArithmetic arithmetic = interpreter.arithmetic;
334         final JexlUberspect uberspect = interpreter.uberspect;
335         try {
336             // try operator overload
337             final Object result = tryOverload(node, JexlOperator.ENDSWITH, left, right);
338             if (result instanceof Boolean) {
339                 return (Boolean) result;
340             }
341             // use arithmetic / pattern matching ?
342             final Boolean matched = arithmetic.endsWith(left, right);
343             if (matched != null) {
344                 return matched;
345             }
346             // try a endsWith method (duck type)
347             try {
348                 final Object[] argv = {right};
349                 JexlMethod vm = uberspect.getMethod(left, "endsWith", argv);
350                 if (returnsBoolean(vm)) {
351                     return (Boolean) vm.invoke(left, argv);
352                 }
353                 if (arithmetic.narrowArguments(argv)) {
354                     vm = uberspect.getMethod(left, "endsWith", argv);
355                     if (returnsBoolean(vm)) {
356                         return (Boolean) vm.invoke(left, argv);
357                     }
358                 }
359             } catch (final Exception e) {
360                 throw new JexlException(node, operator + " error", e);
361             }
362             // defaults to equal
363             return arithmetic.equals(left, right);
364         } catch (final ArithmeticException xrt) {
365             throw new JexlException(node, operator + " error", xrt);
366         }
367     }
368 
369     /**
370      * The 'match'/'in' operator implementation.
371      * <p>
372      * Note that 'x in y' or 'x matches y' means 'y contains x' ;
373      * the JEXL operator arguments order syntax is the reverse of this method call.
374      * </p>
375      * @param node  the node
376      * @param op    the calling operator, =~ or !~
377      * @param right the left operand
378      * @param left  the right operand
379      * @return true if left matches right, false otherwise
380      */
381     protected boolean contains(final JexlNode node, final String op, final Object left, final Object right) {
382         final JexlArithmetic arithmetic = interpreter.arithmetic;
383         final JexlUberspect uberspect = interpreter.uberspect;
384         try {
385             // try operator overload
386             final Object result = tryOverload(node, JexlOperator.CONTAINS, left, right);
387             if (result instanceof Boolean) {
388                 return (Boolean) result;
389             }
390             // use arithmetic / pattern matching ?
391             final Boolean matched = arithmetic.contains(left, right);
392             if (matched != null) {
393                 return matched;
394             }
395             // try a contains method (duck type set)
396             try {
397                 final Object[] argv = {right};
398                 JexlMethod vm = uberspect.getMethod(left, "contains", argv);
399                 if (returnsBoolean(vm)) {
400                     return (Boolean) vm.invoke(left, argv);
401                 }
402                 if (arithmetic.narrowArguments(argv)) {
403                     vm = uberspect.getMethod(left, "contains", argv);
404                     if (returnsBoolean(vm)) {
405                         return (Boolean) vm.invoke(left, argv);
406                     }
407                 }
408             } catch (final Exception e) {
409                 throw new JexlException(node, op + " error", e);
410             }
411             // defaults to equal
412             return arithmetic.equals(left, right);
413         } catch (final ArithmeticException xrt) {
414             throw new JexlException(node, op + " error", xrt);
415         }
416     }
417 
418     /**
419      * Check for emptyness of various types: Collection, Array, Map, String, and anything that has a boolean isEmpty()
420      * method.
421      * <p>Note that the result may not be a boolean.
422      *
423      * @param node   the node holding the object
424      * @param object the object to check the emptyness of
425      * @return the evaluation result
426      */
427     protected Object empty(final JexlNode node, final Object object) {
428         if (object == null) {
429             return true;
430         }
431         Object result = tryOverload(node, JexlOperator.EMPTY, object);
432         if (result != JexlEngine.TRY_FAILED) {
433             return result;
434         }
435         final JexlArithmetic arithmetic = interpreter.arithmetic;
436         result = arithmetic.isEmpty(object, null);
437         if (result == null) {
438             final JexlUberspect uberspect = interpreter.uberspect;
439             result = false;
440             // check if there is an isEmpty method on the object that returns a
441             // boolean and if so, just use it
442             final JexlMethod vm = uberspect.getMethod(object, "isEmpty", InterpreterBase.EMPTY_PARAMS);
443             if (returnsBoolean(vm)) {
444                 try {
445                     result = vm.invoke(object, InterpreterBase.EMPTY_PARAMS);
446                 } catch (final Exception xany) {
447                     interpreter.operatorError(node, JexlOperator.EMPTY, xany);
448                 }
449             }
450         }
451         return !(result instanceof Boolean) || (Boolean) result;
452     }
453 
454     /**
455      * Calculate the <code>size</code> of various types:
456      * Collection, Array, Map, String, and anything that has a int size() method.
457      * <p>Note that the result may not be an integer.
458      *
459      * @param node   the node that gave the value to size
460      * @param object the object to get the size of
461      * @return the evaluation result
462      */
463     protected Object size(final JexlNode node, final Object object) {
464         if (object == null) {
465             return 0;
466         }
467         Object result = tryOverload(node, JexlOperator.SIZE, object);
468         if (result != JexlEngine.TRY_FAILED) {
469             return result;
470         }
471         final JexlArithmetic arithmetic = interpreter.arithmetic;
472         result = arithmetic.size(object, null);
473         if (result == null) {
474             final JexlUberspect uberspect = interpreter.uberspect;
475             // check if there is a size method on the object that returns an
476             // integer and if so, just use it
477             final JexlMethod vm = uberspect.getMethod(object, "size", InterpreterBase.EMPTY_PARAMS);
478             if (returnsInteger(vm)) {
479                 try {
480                     result = vm.invoke(object, InterpreterBase.EMPTY_PARAMS);
481                 } catch (final Exception xany) {
482                     interpreter.operatorError(node, JexlOperator.SIZE, xany);
483                 }
484             }
485         }
486         return result instanceof Number ? ((Number) result).intValue() : 0;
487     }
488 }