This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 3802c3b8 JEXL-428 : Added JexlOperator.Uberspect interface; - 
Operator(s).java minor refactor & 3.4 compatibility improvements;
3802c3b8 is described below

commit 3802c3b819b88d5990da2eacb7d73c26c62ea644
Author: Henrib <hbies...@gmail.com>
AuthorDate: Mon Oct 14 10:53:59 2024 +0200

    JEXL-428 : Added JexlOperator.Uberspect interface;
    - Operator(s).java minor refactor & 3.4 compatibility improvements;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  39 +---
 .../org/apache/commons/jexl3/JexlOperator.java     | 116 +++++++++-
 .../commons/jexl3/internal/InterpreterBase.java    |  61 ++---
 .../internal/{Operators.java => Operator.java}     | 245 ++++++++++-----------
 .../jexl3/internal/introspection/Uberspect.java    |  85 ++++---
 .../commons/jexl3/introspection/JexlUberspect.java |  12 +
 .../commons/jexl3/ArithmeticOperatorTest.java      |  70 +++---
 .../commons/jexl3/jexl342/ReferenceUberspect.java  |  11 +-
 8 files changed, 381 insertions(+), 258 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 8c8e894c..a81a8366 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -157,23 +157,11 @@ public class JexlArithmetic {
          * Gets the most specific method for an operator.
          *
          * @param operator the operator
-         * @param args      the arguments
+         * @param args     the arguments
          * @return the most specific method or null if no specific override 
could be found
          */
         JexlMethod getOperator(JexlOperator operator, Object... args);
 
-        /**
-         * Try to find the most specific method and evaluate an operator.
-         * <p>This method does not call {@link #overloads(JexlOperator)} and 
shall not be called with an
-         * assignment operator.</p>
-         *
-         * @param reference an optional cache reference storing actual method 
or failing signature
-         * @param operator the operator
-         * @param args      the arguments
-         * @return TRY_FAILED if no specific method could be found, the 
evaluation result otherwise
-         */
-        Object tryEval(JexlCache.Reference reference, JexlOperator operator, 
Object...args);
-
         /**
          * Checks whether this uberspect has overloads for a given operator.
          *
@@ -413,8 +401,7 @@ public class JexlArithmetic {
             return (Number) value;
         }
         if (value instanceof Boolean) {
-            final Boolean b = (Boolean) value;
-            return b ? 1L : 0L;
+            return (boolean) value ? 1L : 0L;
         }
         if (value instanceof AtomicBoolean) {
             final AtomicBoolean b = (AtomicBoolean) value;
@@ -1043,7 +1030,7 @@ public class JexlArithmetic {
         }
         if (val instanceof CharSequence) {
             final Matcher m = FLOAT_PATTERN.matcher((CharSequence) val);
-            // first group is decimal, second is exponent;
+            // first matcher group is decimal, second is exponent
             // one of them must exist hence start({1,2}) >= 0
             return m.matches() && (m.start(1) >= 0 || m.start(2) >= 0);
         }
@@ -1187,6 +1174,7 @@ public class JexlArithmetic {
      * Creates a map-builder.
      * @param size the number of elements in the map
      * @return a map-builder instance
+     * @deprecated 3.3
      */
     @Deprecated
     public MapBuilder mapBuilder(final int size) {
@@ -1591,10 +1579,7 @@ public class JexlArithmetic {
     @Deprecated
     public JexlArithmetic options(final JexlEngine.Options options) {
         if (options != null) {
-            Boolean ostrict = options.isStrictArithmetic();
-            if (ostrict == null) {
-                ostrict = isStrict();
-            }
+            boolean isstrict = Boolean.TRUE == options.isStrictArithmetic() || 
isStrict();
             MathContext bigdContext = options.getArithmeticMathContext();
             if (bigdContext == null) {
                 bigdContext = getMathContext();
@@ -1603,10 +1588,10 @@ public class JexlArithmetic {
             if (bigdScale == Integer.MIN_VALUE) {
                 bigdScale = getMathScale();
             }
-            if (ostrict != isStrict()
+            if (isstrict != isStrict()
                 || bigdScale != getMathScale()
                 || bigdContext != getMathContext()) {
-                return createWithOptions(ostrict, bigdContext, bigdScale);
+                return createWithOptions(isstrict, bigdContext, bigdScale);
             }
         }
         return this;
@@ -2046,7 +2031,7 @@ public class JexlArithmetic {
             return roundBigDecimal(parseBigDecimal(val.toString()));
         }
         if (val instanceof Boolean) {
-            return BigDecimal.valueOf((Boolean) val ? 1. : 0.);
+            return BigDecimal.valueOf((boolean) val ? 1. : 0.);
         }
         if (val instanceof AtomicBoolean) {
             return BigDecimal.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
@@ -2106,7 +2091,7 @@ public class JexlArithmetic {
             return BigInteger.valueOf(((Number) val).longValue());
         }
         if (val instanceof Boolean) {
-            return BigInteger.valueOf((Boolean) val ? 1L : 0L);
+            return BigInteger.valueOf((boolean) val ? 1L : 0L);
         }
         if (val instanceof AtomicBoolean) {
             return BigInteger.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
@@ -2198,7 +2183,7 @@ public class JexlArithmetic {
             return ((Number) val).doubleValue();
         }
         if (val instanceof Boolean) {
-            return (Boolean) val ? 1. : 0.;
+            return (boolean) val ? 1. : 0.;
         }
         if (val instanceof AtomicBoolean) {
             return ((AtomicBoolean) val).get() ? 1. : 0.;
@@ -2252,7 +2237,7 @@ public class JexlArithmetic {
             return parseInteger((String) val);
         }
         if (val instanceof Boolean) {
-            return (Boolean) val ? 1 : 0;
+            return (boolean) val ? 1 : 0;
         }
         if (val instanceof AtomicBoolean) {
             return ((AtomicBoolean) val).get() ? 1 : 0;
@@ -2303,7 +2288,7 @@ public class JexlArithmetic {
             return parseLong((String) val);
         }
         if (val instanceof Boolean) {
-            return (Boolean) val ? 1L : 0L;
+            return (boolean) val ? 1L : 0L;
         }
         if (val instanceof AtomicBoolean) {
             return ((AtomicBoolean) val).get() ? 1L : 0L;
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java 
b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index ca3eec09..9d55c802 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -17,6 +17,8 @@
 
 package org.apache.commons.jexl3;
 
+import java.util.function.Consumer;
+
 /**
  * The JEXL operators.
  *
@@ -36,7 +38,7 @@ package org.apache.commons.jexl3;
  * <li>Operators may return JexlEngine.TRY_AGAIN to fallback on default JEXL 
implementation</li>
  * </ul>
  *
- * For side effect operators, operators that modify the left-hand size value 
(+=, -=, etc), the user implemented
+ * For side effect operators, operators that modify the left-hand size value 
(+=, -=, etc.), the user implemented
  * overload methods may return:
  * <ul>
  *     <li>JexlEngine.TRY_FAIL to let the default fallback behavior be 
executed.</li>
@@ -544,4 +546,116 @@ public enum JexlOperator {
         return operator;
     }
 
+    /**
+     * Uberspect that solves and evaluates JexlOperator overloads.
+     * <p>This is used by the interpreter to find and execute operator 
overloads implemented in a derived
+     * JexlArithmetic - or in some cases, as methods of the left argument type 
(contains, size, ...).</p>
+     * <p>This also allows reusing the core logic when extending the 
applicative type-system; for
+     * instance, implementing a Comparator class that calls compare
+     * (<code>operator.tryOverload(this, JexlOperator.COMPARE, left, 
right)</code>, etc.</p>
+     * @since 3.4.1
+     */
+    public interface Uberspect extends JexlArithmetic.Uberspect {
+        /**
+         * Try to find the most specific method and evaluate an operator.
+         * <p>This method does not call {@link #overloads(JexlOperator)} and 
shall not be called with an
+         * assignment operator; use {@link 
#tryAssignOverload(JexlCache.Reference, JexlOperator, Consumer, Object...)}
+         * in that case.</p>
+         *
+         * @param reference an optional reference caching resolved method or 
failing signature
+         * @param operator the operator
+         * @param args      the arguments
+         * @return TRY_FAILED if no specific method could be found, the 
evaluation result otherwise
+         */
+        Object tryOverload(JexlCache.Reference reference, JexlOperator 
operator, Object...args);
+
+        /**
+         * Evaluates an assign operator.
+         * <p>
+         * This takes care of finding and caching the operator method when 
appropriate.
+         * If an overloads returns a value not-equal to TRY_FAILED, it means 
the side-effect is complete.
+         * Otherwise, {@code a += b <=> a = a + b}
+         * </p>
+         * @param node     an optional reference caching resolved method or 
failing signature
+         * @param operator the operator
+         * @param assign   the actual function that performs the side effect
+         * @param args     the arguments, the first one being the target of 
assignment
+         * @return JexlEngine.TRY_FAILED if no operation was performed,
+         *         the value to use as the side effect argument otherwise
+         */
+        Object tryAssignOverload(final JexlCache.Reference node,
+                                 final JexlOperator operator,
+                                 final Consumer<Object> assign,
+                                 final Object... args);
+
+        /**
+         * Calculate the {@code size} of various types:
+         * Collection, Array, Map, String, and anything that has an int size() 
method.
+         * <p>Seeks an overload or use the default arithmetic 
implementation.</p>
+         * <p>Note that the result may not be an integer.
+         *
+         * @param node   an optional reference caching resolved method or 
failing signature
+         * @param object the object to get the size of
+         * @return the evaluation result
+         */
+        Object size(final JexlCache.Reference node, final Object object);
+
+        /**
+         * Check for emptiness of various types: Collection, Array, Map, 
String, and anything that has a boolean isEmpty()
+         * method.
+         * <p>Seeks an overload or use the default arithmetic 
implementation.</p>
+         * <p>Note that the result may not be a boolean.
+         *
+         * @param node   the node holding the object
+         * @param object the object to check the emptiness of
+         * @return the evaluation result
+         */
+        Object empty(final JexlCache.Reference node, final Object object);
+
+        /**
+         * The 'match'/'in' operator implementation.
+         * <p>Seeks an overload or use the default arithmetic 
implementation.</p>
+         * <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  an optional reference caching resolved method or 
failing signature
+         * @param operator    the calling operator, =~ or !~
+         * @param right the left operand
+         * @param left  the right operand
+         * @return true if left matches right, false otherwise
+         */
+        boolean contains(final JexlCache.Reference node,
+                         final JexlOperator operator,
+                         final Object left,
+                         final Object right);
+
+        /**
+         * The 'startsWith' operator implementation.
+         * <p>Seeks an overload or use the default arithmetic 
implementation.</p>
+         * @param node     an optional reference caching resolved method or 
failing signature
+         * @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
+         */
+        boolean startsWith(final JexlCache.Reference node,
+                           final JexlOperator operator,
+                           final Object left,
+                           final Object right);
+
+        /**
+         * The 'endsWith' operator implementation.
+         * <p>Seeks an overload or use the default arithmetic 
implementation.</p>
+         * @param node     an optional reference caching resolved method or 
failing signature
+         * @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
+         */
+        boolean endsWith(final JexlCache.Reference node,
+                         final JexlOperator operator,
+                         final Object left,
+                         final Object right);
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java 
b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index bd40e865..ec7d85de 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -311,7 +311,7 @@ public abstract class InterpreterBase extends ParserVisitor 
{
     /** The class name resolver. */
     protected final JexlContext.ClassNameResolver fqcnSolver;
     /** The operators evaluation delegate. */
-    protected final Operators operators;
+    protected final JexlOperator.Uberspect operators;
     /** The map of 'prefix:function' to object resolving as namespaces. */
     protected final Map<String, Object> functions;
     /** The map of dynamically created namespaces, NamespaceFunctor or 
duck-types of those. */
@@ -349,7 +349,11 @@ public abstract class InterpreterBase extends 
ParserVisitor {
         this.cancelled = acancel != null ? acancel : new AtomicBoolean();
         this.functions = options.getNamespaces();
         this.functors = null;
-        this.operators = (Operators) uberspect.getArithmetic(arithmetic);
+        JexlOperator.Uberspect ops = uberspect.getOperator(arithmetic);
+        if (ops == null) {
+            ops = new Operator(uberspect, arithmetic);
+        }
+        this.operators = ops;
         // the import package facility
         final Collection<String> imports = options.getImports();
         this.fqcnSolver = imports.isEmpty()
@@ -467,28 +471,28 @@ public abstract class InterpreterBase extends 
ParserVisitor {
     /**
      * Triggered when a captured variable is const and assignment is attempted.
      * @param node  the node where the error originated from
-     * @param var   the variable name
+     * @param variable   the variable name
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    protected Object constVariable(final JexlNode node, final String var) {
-        return variableError(node, var, VariableIssue.CONST);
+    protected Object constVariable(final JexlNode node, final String variable) 
{
+        return variableError(node, variable, VariableIssue.CONST);
     }
 
     /**
      * Defines a variable.
-     * @param var the variable to define
+     * @param variable the variable to define
      * @param frame the frame in which it will be defined
      * @return true if definition succeeded, false otherwise
      */
-    protected boolean defineVariable(final ASTVar var, final LexicalFrame 
frame) {
-        final int symbol = var.getSymbol();
+    protected boolean defineVariable(final ASTVar variable, final LexicalFrame 
frame) {
+        final int symbol = variable.getSymbol();
         if (symbol < 0) {
             return false;
         }
-        if (var.isRedefined()) {
+        if (variable.isRedefined()) {
             return false;
         }
-        return frame.defineSymbol(symbol, var.isCaptured());
+        return frame.defineSymbol(symbol, variable.isCaptured());
     }
 
     /**
@@ -508,6 +512,9 @@ public abstract class InterpreterBase extends ParserVisitor 
{
         return node;
     }
 
+    /**
+     * @deprecated
+     */
     @Deprecated
     protected JexlNode findNullOperand(final RuntimeException xrt, final 
JexlNode node, final Object left, final Object right) {
         return findNullOperand(node, left, right);
@@ -689,7 +696,7 @@ public abstract class InterpreterBase extends ParserVisitor 
{
      * @return true if cancelled, false otherwise
      */
     protected boolean isCancelled() {
-        return cancelled.get() | Thread.currentThread().isInterrupted();
+        return cancelled.get() || Thread.currentThread().isInterrupted();
     }
 
     /**
@@ -790,11 +797,11 @@ public abstract class InterpreterBase extends 
ParserVisitor {
     /**
      * Triggered when a variable is lexically known as being redefined.
      * @param node  the node where the error originated from
-     * @param var   the variable name
+     * @param variable   the variable name
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    protected Object redefinedVariable(final JexlNode node, final String var) {
-        return variableError(node, var, VariableIssue.REDEFINED);
+    protected Object redefinedVariable(final JexlNode node, final String 
variable) {
+        return variableError(node, variable, VariableIssue.REDEFINED);
     }
 
     /**
@@ -862,11 +869,11 @@ public abstract class InterpreterBase extends 
ParserVisitor {
                             if (functor != null) {
                                 // wrap the namespace in a NamespaceFunctor to 
shield us from the actual
                                 // number of arguments to call it with.
-                                final Object ns = namespace;
+                                final Object nsFinal = namespace;
                                 // make it a class (not a lambda!) so 
instanceof (see *2) will catch it
                                 namespace = (NamespaceFunctor) context -> 
withContext
-                                        ? ctor.tryInvoke(null, ns, context)
-                                        : ctor.tryInvoke(null, ns);
+                                        ? ctor.tryInvoke(null, nsFinal, 
context)
+                                        : ctor.tryInvoke(null, nsFinal);
                                 if (cacheable && ctor.isCacheable()) {
                                     nsNode.jjtSetValue(namespace);
                                 }
@@ -1026,11 +1033,11 @@ public abstract class InterpreterBase extends 
ParserVisitor {
     /**
      * Triggered when a variable is lexically known as undefined.
      * @param node  the node where the error originated from
-     * @param var   the variable name
+     * @param variable   the variable name
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    protected Object undefinedVariable(final JexlNode node, final String var) {
-        return variableError(node, var, VariableIssue.UNDEFINED);
+    protected Object undefinedVariable(final JexlNode node, final String 
variable) {
+        return variableError(node, variable, VariableIssue.UNDEFINED);
     }
 
     /**
@@ -1081,27 +1088,27 @@ public abstract class InterpreterBase extends 
ParserVisitor {
     /**
      * Triggered when a variable can not be resolved.
      * @param node  the node where the error originated from
-     * @param var   the variable name
+     * @param variable   the variable name
      * @param undef whether the variable is undefined or null
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    protected Object unsolvableVariable(final JexlNode node, final String var, 
final boolean undef) {
-        return variableError(node, var, undef? VariableIssue.UNDEFINED : 
VariableIssue.NULLVALUE);
+    protected Object unsolvableVariable(final JexlNode node, final String 
variable, final boolean undef) {
+        return variableError(node, variable, undef? VariableIssue.UNDEFINED : 
VariableIssue.NULLVALUE);
     }
 
     /**
      * Triggered when a variable generates an issue.
      * @param node  the node where the error originated from
-     * @param var   the variable name
+     * @param variable   the variable name
      * @param issue the issue type
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    protected Object variableError(final JexlNode node, final String var, 
final VariableIssue issue) {
+    protected Object variableError(final JexlNode node, final String variable, 
final VariableIssue issue) {
         if (isStrictEngine() && !isTernaryProtected(node)) {
-            throw new JexlException.Variable(node, var, issue);
+            throw new JexlException.Variable(node, variable, issue);
         }
         if (logger.isDebugEnabled()) {
-            logger.debug(JexlException.variableError(node, var, issue));
+            logger.debug(JexlException.variableError(node, variable, issue));
         }
         return null;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java 
b/src/main/java/org/apache/commons/jexl3/internal/Operator.java
similarity index 78%
rename from src/main/java/org/apache/commons/jexl3/internal/Operators.java
rename to src/main/java/org/apache/commons/jexl3/internal/Operator.java
index 8a5bd25d..faa755f6 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operator.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl3.internal;
 
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -27,15 +28,15 @@ import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.internal.introspection.MethodExecutor;
 import org.apache.commons.jexl3.internal.introspection.MethodKey;
-import org.apache.commons.jexl3.internal.introspection.Uberspect;
 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 final class Operators implements JexlArithmetic.Uberspect {
+public final class Operator implements JexlOperator.Uberspect {
     private static final String METHOD_IS_EMPTY = "isEmpty";
     private static final String METHOD_SIZE = "size";
     private static final String METHOD_CONTAINS = "contains";
@@ -57,36 +58,76 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
             EnumSet.of(JexlOperator.GET_AND_INCREMENT, 
JexlOperator.GET_AND_DECREMENT);
 
     /** The uberspect. */
-    private final Uberspect uberspect;
+    private final JexlUberspect uberspect;
     /** The arithmetic instance being analyzed. */
     private final JexlArithmetic arithmetic;
     /** The set of overloaded operators. */
     private final Set<JexlOperator> overloads;
+    /** The delegate if built as a 3.4 legacy. */
+    private final JexlArithmetic.Uberspect delegate;
     /** Caching state: -1 unknown, 0 false, 1 true. */
     private volatile int caching = -1;
 
+    /**
+     * Creates an instance.
+     * <p>Mostly used as a compatibility measure by delegating instead of 
extending.</p>
+     *
+     * @param theUberspect  the uberspect instance
+     * @param theArithmetic the arithmetic instance used to delegate operator 
overloads
+     */
+    public Operator(final JexlUberspect theUberspect, final JexlArithmetic 
theArithmetic) {
+        this.uberspect = theUberspect;
+        this.arithmetic = theArithmetic;
+        this.overloads = Collections.emptySet();
+        this.delegate = theUberspect.getArithmetic(theArithmetic);
+    }
+
     /**
      * Creates an instance.
      * @param theUberspect the uberspect instance
      * @param theArithmetic the arithmetic instance
      * @param theOverloads  the overloaded operators
      */
-    public Operators(final Uberspect theUberspect, final JexlArithmetic 
theArithmetic, final Set<JexlOperator> theOverloads) {
+    public Operator(final JexlUberspect theUberspect,
+                    final JexlArithmetic theArithmetic,
+                    final Set<JexlOperator> theOverloads) {
+        this(theUberspect, theArithmetic, theOverloads, -1);
+    }
+
+    /**
+     * Creates an instance.
+     * @param theUberspect the uberspect instance
+     * @param theArithmetic the arithmetic instance
+     * @param theOverloads  the overloaded operators
+     * @param theCache the caching state
+     */
+    public Operator(final JexlUberspect theUberspect,
+                    final JexlArithmetic theArithmetic,
+                    final Set<JexlOperator> theOverloads,
+                    final int theCache) {
         this.uberspect = theUberspect;
         this.arithmetic = theArithmetic;
         this.overloads = theOverloads;
+        this.delegate = null;
+        this.caching = theCache;
     }
 
     @Override
     public JexlMethod getOperator(final JexlOperator operator, final Object... 
args) {
-        return overloads.contains(operator) && args != null && args.length == 
operator.getArity()
-                ? uberspectOperator(arithmetic, operator, args)
-                : null;
+        if (delegate != null) {
+            return delegate.getOperator(operator, args);
+        }
+        if (overloads.contains(operator) && args != null && args.length == 
operator.getArity()) {
+            return uberspectOperator(arithmetic, operator, args);
+        }
+        return null;
     }
 
     @Override
     public boolean overloads(final JexlOperator operator) {
-        return overloads.contains(operator);
+        return delegate != null
+            ? delegate.overloads(operator)
+            : overloads.contains(operator);
     }
 
     /**
@@ -165,9 +206,11 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
      * @param ref     the node where the error originated from
      * @param operator the operator symbol
      * @param cause    the cause of error (if any)
+     * @param alt      what to return if not strict
+     * @param <T>      the return type
      * @return throws JexlException if strict and not silent, null otherwise
      */
-    private Object operatorError(final JexlCache.Reference ref, final 
JexlOperator operator, final Throwable cause) {
+    private <T> T operatorError(final JexlCache.Reference ref, final 
JexlOperator operator, final Throwable cause, T alt) {
         JexlNode node = (ref instanceof JexlNode) ? (JexlNode) ref : null;
         Engine engine = (Engine) JexlEngine.getThreadEngine();
         if (engine == null || engine.isStrict()) {
@@ -176,7 +219,7 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         if (engine.logger.isDebugEnabled()) {
             engine.logger.debug(JexlException.operatorError(node, 
operator.getOperatorSymbol()), cause);
         }
-        return null;
+        return alt;
     }
 
     /**
@@ -200,7 +243,7 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
 
     /**
      * Checks whether a method returns a boolean or a Boolean.
-     * @param vm the JexlMethod (may be null)
+     * @param vm the JexlMethod (can be null)
      * @return true of false
      */
     private boolean returnsBoolean(final JexlMethod vm) {
@@ -213,7 +256,7 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
 
     /**
      * Checks whether a method returns an int or an Integer.
-     * @param vm the JexlMethod (may be null)
+     * @param vm the JexlMethod (can be null)
      * @return true of false
      */
     private boolean returnsInteger(final JexlMethod vm) {
@@ -224,16 +267,8 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         return false;
     }
 
-    /**
-     * Check for emptiness 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 emptiness of
-     * @return the evaluation result
-     */
-    Object empty(final JexlCache.Reference node, final Object object) {
+    @Override
+    public Object empty(final JexlCache.Reference node, final Object object) {
         if (object == null) {
             return true;
         }
@@ -250,8 +285,8 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
                 if (returnsBoolean(vm)) {
                     try {
                         result = vm.invoke(object, 
InterpreterBase.EMPTY_PARAMS);
-                    } catch (final Exception xany) {
-                        operatorError(node, JexlOperator.EMPTY, xany);
+                    } catch (final Exception any) {
+                        return operatorError(node, JexlOperator.EMPTY, any, 
false);
                     }
                 }
             }
@@ -259,16 +294,8 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         return !(result instanceof Boolean) || (Boolean) result;
     }
 
-    /**
-     * Calculate the {@code size} of various types:
-     * Collection, Array, Map, String, and anything that has an 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
-     */
-    Object size(final JexlCache.Reference node, final Object object) {
+    @Override
+    public Object size(final JexlCache.Reference node, final Object object) {
         if (object == null) {
             return 0;
         }
@@ -284,8 +311,8 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
                 if (returnsInteger(vm)) {
                     try {
                         result = vm.invoke(object, 
InterpreterBase.EMPTY_PARAMS);
-                    } catch (final Exception xany) {
-                        operatorError(node, JexlOperator.SIZE, xany);
+                    } catch (final Exception any) {
+                        return operatorError(node, JexlOperator.SIZE, any, 0);
                     }
                 }
             }
@@ -293,19 +320,8 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         return result instanceof Number ? ((Number) result).intValue() : 0;
     }
 
-    /**
-     * 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 operator    the calling operator, =~ or !~
-     * @param right the left operand
-     * @param left  the right operand
-     * @return true if left matches right, false otherwise
-     */
-    boolean contains(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
+    @Override
+    public boolean contains(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
         final boolean contained;
         try {
             // try operator overload
@@ -332,21 +348,13 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
             }
             // not-contains is !contains
             return (JexlOperator.CONTAINS == operator) == contained;
-        } catch (final Exception xrt) {
-            operatorError(node, operator, xrt);
-            return false;
+        } catch (final Exception any) {
+            return operatorError(node, operator, any, false);
         }
     }
 
-    /**
-     * 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
-     */
-    boolean startsWith(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
+    @Override
+    public boolean startsWith(final JexlCache.Reference node, final 
JexlOperator operator, final Object left, final Object right) {
         final boolean starts;
         try {
             // try operator overload
@@ -373,21 +381,13 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
             }
             // not-startswith is !starts-with
             return (JexlOperator.STARTSWITH == operator) == starts;
-        } catch (final Exception xrt) {
-            operatorError(node, operator, xrt);
-            return false;
+        } catch (final Exception any) {
+            return operatorError(node, operator, any, false);
         }
     }
 
-    /**
-     * 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
-     */
-    boolean endsWith(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
+    @Override
+    public boolean endsWith(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
         try {
             final boolean ends;
             // try operator overload
@@ -414,26 +414,13 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
             }
             // not-endswith is !ends-with
             return (JexlOperator.ENDSWITH == operator) == ends;
-        } catch (final Exception xrt) {
-            operatorError(node, operator, xrt);
-            return false;
+        } catch (final Exception any) {
+            return operatorError(node, operator, any, false);
         }
     }
 
-    /**
-     * Evaluates an assign operator.
-     * <p>
-     * This takes care of finding and caching the operator method when 
appropriate.
-     * If an overloads returns a value not-equal to TRY_FAILED, it means the 
side-effect is complete.
-     * Otherwise, {@code 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 JexlEngine.TRY_FAILED if no operation was performed,
-     *         the value to use as the side effect argument otherwise
-     */
-    Object tryAssignOverload(final JexlNode node,
+    @Override
+    public Object tryAssignOverload(final JexlCache.Reference node,
                              final JexlOperator operator,
                              final Consumer<Object> assignFun,
                              final Object... args) {
@@ -462,12 +449,14 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
             if (result != JexlEngine.TRY_FAILED) {
                 assignFun.accept(result);
                 // postfix implies return initial argument value
-                return POSTFIX_OPS.contains(operator) ? args[0] : result;
+                if (POSTFIX_OPS.contains(operator)) {
+                    result = args[0];
+                }
             }
-        } catch (final Exception xany) {
-            operatorError(node, operator, xany);
+            return result;
+        } catch (final Exception any) {
+            return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
         }
-        return JexlEngine.TRY_FAILED;
     }
 
     /**
@@ -500,32 +489,25 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         }
     }
 
-    /**
-     * Attempts to call an operator.
-     * <p>
-     *     This performs the null argument control against the strictness of 
the operator.
-     * </p>
-     * <p>
-     * This takes care of finding and caching the operator method when 
appropriate.
-     * </p>
-     * @param node     the syntactic node
-     * @param operator the operator
-     * @param args     the arguments
-     * @return the result of the operator evaluation or TRY_FAILED
-     */
-    Object tryOverload(final JexlCache.Reference node, final JexlOperator 
operator, final Object... args) {
+    @Override
+    public Object tryOverload(final JexlCache.Reference node, final 
JexlOperator operator, final Object... args) {
         controlNullOperands(arithmetic, operator, args);
         try {
             return tryEval(isCaching() ? node : null, operator, args);
-        } catch (final Exception xany) {
+        } catch (final Exception any) {
             // ignore return if lenient, will return try_failed
-            operatorError(node, operator, xany);
+            return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
         }
-        return JexlEngine.TRY_FAILED;
     }
 
-    @Override
-    public Object tryEval(JexlCache.Reference node, JexlOperator operator, 
Object...args) {
+    /**
+     * Tries operator evaluation, handles method resolution caching.
+     * @param node the node
+     * @param operator the operator
+     * @param args the operator arguments
+     * @return the operator evaluation result or TRY_FAILED
+     */
+    private Object tryEval(final JexlCache.Reference node, final JexlOperator 
operator, final Object...args) {
         if (node != null) {
             final Object cached = node.getCache();
             if (cached instanceof JexlMethod) {
@@ -578,11 +560,11 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         if (CMP_OPS.contains(operator) && args.length == 2) {
             JexlMethod cmp = getOperator(JexlOperator.COMPARE, args);
             if (cmp != null) {
-                return new Operators.CompareMethod(operator, cmp);
+                return new Operator.CompareMethod(operator, cmp);
             }
             cmp = getOperator(JexlOperator.COMPARE, args[1], args[0]);
             if (cmp != null) {
-                return new Operators.AntiCompareMethod(operator, cmp);
+                return new Operator.AntiCompareMethod(operator, cmp);
             }
         }
         return null;
@@ -597,7 +579,7 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
         protected final JexlOperator operator;
         protected final JexlMethod compare;
 
-        CompareMethod(JexlOperator op, JexlMethod m) {
+        CompareMethod(final JexlOperator op, final JexlMethod m) {
             operator = op;
             compare = m;
         }
@@ -624,13 +606,15 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
 
         @Override
         public Object tryInvoke(String name, Object arithmetic, Object... 
params) throws JexlException.TryFailed {
-            Object cmp = 
compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params);
-            if (cmp instanceof Integer) {
-                return operate((int) cmp);
-            }
-            return JexlEngine.TRY_FAILED;
+            final Object cmp = 
compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params);
+            return cmp instanceof Integer? operate((int) cmp) : 
JexlEngine.TRY_FAILED;
         }
 
+        /**
+         * From compare() int result to operator boolean result.
+         * @param cmp the compare result
+         * @return the operator applied
+         */
         protected boolean operate(final int cmp) {
             switch(operator) {
                 case EQ: return cmp == 0;
@@ -645,24 +629,23 @@ public final class Operators implements 
JexlArithmetic.Uberspect {
     }
 
     /**
-     * The reverse compare swaps left and right arguments and changes the sign 
of the
-     * comparison result.
+     * The reverse compare swaps left and right arguments and exploits the 
symmetry
+     * of compare as in: compare(a, b) &lt=&gt -compare(b, a)
      */
-    private class AntiCompareMethod extends CompareMethod {
-
-        AntiCompareMethod(JexlOperator op, JexlMethod m) {
+    private static class AntiCompareMethod extends CompareMethod {
+        AntiCompareMethod(final JexlOperator op, final JexlMethod m) {
             super(op, m);
         }
 
         @Override
-        public Object tryInvoke(String name, Object arithmetic, Object... 
params) throws JexlException.TryFailed {
-            Object cmp = 
compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params[1], 
params[0]);
-            return cmp instanceof Integer? operate(-(int) cmp) : 
JexlEngine.TRY_FAILED;
+        public Object invoke(Object arithmetic, Object... params) throws 
Exception {
+            return operate(-(int) compare.invoke(arithmetic, params[1], 
params[0]));
         }
 
         @Override
-        public Object invoke(Object arithmetic, Object... params) throws 
Exception {
-            return operate(-(int) compare.invoke(arithmetic, params[1], 
params[0]));
+        public Object tryInvoke(String name, Object arithmetic, Object... 
params) throws JexlException.TryFailed {
+            final Object cmp = 
compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params[1], 
params[0]);
+            return cmp instanceof Integer? operate(-(int) cmp) : 
JexlEngine.TRY_FAILED;
         }
     }
 }
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 332800e6..8cb9b914 100644
--- 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -20,6 +20,7 @@ import java.lang.ref.Reference;
 import java.lang.ref.SoftReference;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Enumeration;
 import java.util.Iterator;
@@ -32,7 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlOperator;
-import org.apache.commons.jexl3.internal.Operators;
+import org.apache.commons.jexl3.internal.Operator;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
@@ -103,7 +104,6 @@ public class Uberspect implements JexlUberspect {
      * If the reference has been collected, this method will recreate the 
underlying introspector.</p>
      * @return the introspector
      */
-    // CSOFF: DoubleCheckedLocking
     protected final Introspector base() {
         Introspector intro = ref.get();
         if (intro == null) {
@@ -120,45 +120,56 @@ public class Uberspect implements JexlUberspect {
         }
         return intro;
     }
-    // CSON: DoubleCheckedLocking
 
-    @Override
-    public Operators getArithmetic(final JexlArithmetic arithmetic) {
-        Operators jau = null;
-        if (arithmetic != null) {
-            final Class<? extends JexlArithmetic> aclass = 
arithmetic.getClass();
-            final Set<JexlOperator> ops = operatorMap.computeIfAbsent(aclass, 
k -> {
-                final Set<JexlOperator> newOps = 
EnumSet.noneOf(JexlOperator.class);
-                // deal only with derived classes
-                if (!JexlArithmetic.class.equals(aclass)) {
-                    for (final JexlOperator op : JexlOperator.values()) {
-                        final Method[] methods = 
getMethods(arithmetic.getClass(), op.getMethodName());
-                        if (methods != null) {
-                            for (final Method method : methods) {
-                                final Class<?>[] parms = 
method.getParameterTypes();
-                                if (parms.length != op.getArity()) {
-                                    continue;
-                                }
-                                // filter method that is an actual overload:
-                                // - not inherited (not declared by base class)
-                                // - nor overridden (not present in base class)
-                                if 
(!JexlArithmetic.class.equals(method.getDeclaringClass())) {
-                                    try {
-                                        
JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
-                                    } catch (final NoSuchMethodException 
xmethod) {
-                                        // method was not found in 
JexlArithmetic; this is an operator definition
-                                        newOps.add(op);
-                                    }
+    /**
+     * Computes which operators have an overload implemented in the arithmetic.
+     * <p>This is used to speed up resolution and avoid introspection when 
possible.</p>
+     * @param arithmetic the arithmetic instance
+     * @return the set of overloaded operators
+     */
+    Set<JexlOperator> getOverloads(final JexlArithmetic arithmetic) {
+        final Class<? extends JexlArithmetic> aclass = arithmetic.getClass();
+        return operatorMap.computeIfAbsent(aclass, k -> {
+            final Set<JexlOperator> newOps = 
EnumSet.noneOf(JexlOperator.class);
+            // deal only with derived classes
+            if (!JexlArithmetic.class.equals(aclass)) {
+                for (final JexlOperator op : JexlOperator.values()) {
+                    final Method[] methods = getMethods(arithmetic.getClass(), 
op.getMethodName());
+                    if (methods != null) {
+                        for (final Method method : methods) {
+                            final Class<?>[] parms = 
method.getParameterTypes();
+                            if (parms.length != op.getArity()) {
+                                continue;
+                            }
+                            // filter method that is an actual overload:
+                            // - not inherited (not declared by base class)
+                            // - nor overridden (not present in base class)
+                            if 
(!JexlArithmetic.class.equals(method.getDeclaringClass())) {
+                                try {
+                                    
JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
+                                } catch (final NoSuchMethodException xmethod) {
+                                    // method was not found in JexlArithmetic; 
this is an operator definition
+                                    newOps.add(op);
                                 }
                             }
                         }
                     }
                 }
-                return newOps;
-            });
-            jau = new Operators(this, arithmetic, ops);
-        }
-        return jau;
+            }
+            return newOps;
+        });
+    }
+
+    @Override
+    public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic 
arithmetic) {
+        Set<JexlOperator> operators = arithmetic == null ? 
Collections.emptySet() : getOverloads(arithmetic);
+        return operators.isEmpty()? null : new Operator(this, arithmetic, 
operators);
+    }
+
+    @Override
+    public Operator getOperator(final JexlArithmetic arithmetic) {
+        Set<JexlOperator> operators = arithmetic == null ? 
Collections.emptySet() : getOverloads(arithmetic);
+        return new Operator(this, arithmetic, operators);
     }
 
     /**
@@ -173,7 +184,9 @@ public class Uberspect implements JexlUberspect {
 
     @Override
     public ClassLoader getClassLoader() {
-        return loader.get();
+        synchronized (this) {
+            return loader.get();
+        }
     }
 
     @Override
diff --git 
a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java 
b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
index cf7d0c67..5f8f9fe8 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
@@ -202,9 +202,21 @@ public interface JexlUberspect {
      * @param arithmetic the arithmetic instance
      * @return the arithmetic uberspect or null if no operator method were 
overridden
      * @since 3.0
+     * @see #getOperator(JexlArithmetic) 
      */
     JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic);
 
+    /**
+     * Gets an arithmetic operator executor for a given arithmetic instance.
+     *
+     * @param arithmetic the arithmetic instance
+     * @return an operator uberspect instance
+     * @since 3.4.1
+     */
+    default JexlOperator.Uberspect getOperator(JexlArithmetic arithmetic) {
+        return null;
+    }
+
     /**
      * Seeks a class by name using this uberspect class-loader.
      * @param className the class name
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index 969e2640..28ab6e6c 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -118,7 +118,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
             return setDateValue(date, identifier, value);
         }
 
-        protected Object setDateValue(final Date date, final String key, final 
Object value) throws Exception {
+        protected Object setDateValue(final Date date, final String key, final 
Object value) {
             final Calendar cal = Calendar.getInstance(UTC);
             cal.setTime(date);
             switch (key) {
@@ -248,24 +248,24 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         asserter.setStrict(false);
     }
 
-    @Test public void test373a() {
+    @Test void test373a() {
         testSelfAssignOperators("y.add(x++)", 42, 42, 43);
     }
 
-    @Test public void test373b() {
+    @Test void test373b() {
         testSelfAssignOperators("y.add(++x)", 42, 43, 43);
     }
 
-    @Test public void test373c() {
+    @Test void test373c() {
         testSelfAssignOperators("y.add(x--)", 42, 42, 41);
     }
 
-    @Test public void test373d() {
+    @Test void test373d() {
         testSelfAssignOperators("y.add(--x)", 42, 41, 41);
     }
 
     @Test
-    public void test391() throws Exception {
+    void test391() throws Exception {
         // with literals
         for(final String src : Arrays.asList(
                 "2 =~ [1, 2, 3, 4]",
@@ -292,7 +292,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testDateArithmetic() throws Exception {
+    void testDateArithmetic() {
         final Date d = new Date();
         final JexlContext jc = new MapContext();
         final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new 
DateArithmetic(true)).create();
@@ -307,7 +307,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testFormatArithmetic() throws Exception {
+    void testFormatArithmetic() {
         final Calendar cal = Calendar.getInstance(UTC);
         cal.set(1969, Calendar.AUGUST, 20);
         final Date x0 = cal.getTime();
@@ -351,7 +351,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testFormatArithmeticJxlt() throws Exception {
+    void testFormatArithmeticJxlt() throws Exception {
         final Map<String, Object> ns = new HashMap<>();
         ns.put("calc", Aggregate.class);
         final Calendar cal = Calendar.getInstance(UTC);
@@ -388,7 +388,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testIncrementOperatorOnNull() throws Exception {
+    void testIncrementOperatorOnNull() {
         final JexlEngine jexl = new JexlBuilder().strict(false).create();
         JexlScript script;
         Object result;
@@ -402,7 +402,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
 
     @Test
     @SuppressWarnings("unchecked")
-    public void testInterval() throws Exception {
+    void testInterval() {
         final Map<String, Object> ns = new HashMap<>();
         ns.put("calc", Aggregate.class);
         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
@@ -440,7 +440,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testMatch() throws Exception {
+    void testMatch() throws Exception {
         // check in/not-in on array, list, map, set and duck-type collection
         final int[] ai = {2, 4, 42, 54};
         final List<Integer> al = new ArrayList<>();
@@ -457,8 +457,8 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         final Set<Integer> as = ad.values;
         final Object[] vars = {ai, al, am, ad, as, ic};
 
-        for (final Object var : vars) {
-            asserter.setVariable("container", var);
+        for (final Object variable : vars) {
+            asserter.setVariable("container", variable);
             for (final int x : ai) {
                 asserter.setVariable("x", x);
                 asserter.assertExpression("x =~ container", Boolean.TRUE);
@@ -469,7 +469,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testNotStartsEndsWith() throws Exception {
+    void testNotStartsEndsWith() throws Exception {
         asserter.setVariable("x", "foobar");
         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
@@ -489,7 +489,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testNotStartsEndsWithString() throws Exception {
+    void testNotStartsEndsWithString() throws Exception {
         asserter.setVariable("x", "foobar");
         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
@@ -499,7 +499,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testNotStartsEndsWithStringBuilder() throws Exception {
+    void testNotStartsEndsWithStringBuilder() throws Exception {
         asserter.setVariable("x", new StringBuilder("foobar"));
         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
@@ -509,7 +509,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testNotStartsEndsWithStringDot() throws Exception {
+    void testNotStartsEndsWithStringDot() throws Exception {
         asserter.setVariable("x.y", "foobar");
         asserter.assertExpression("x.y !^ 'foo'", Boolean.FALSE);
         asserter.assertExpression("x.y !$ 'foo'", Boolean.TRUE);
@@ -519,12 +519,12 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testOperatorError() throws Exception {
-        testOperatorError(true);
-        testOperatorError(false);
+    void testOperatorError() throws Exception {
+        runOperatorError(true);
+        runOperatorError(false);
     }
 
-    private void testOperatorError(final boolean silent) throws Exception {
+    private void runOperatorError(final boolean silent) {
         final CaptureLog log = new CaptureLog();
         final DateContext jc = new DateContext();
         final Date d = new Date();
@@ -547,7 +547,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testRegexp() throws Exception {
+    void testRegexp() throws Exception {
         asserter.setVariable("str", "abc456");
         asserter.assertExpression("str =~ '.*456'", Boolean.TRUE);
         asserter.assertExpression("str !~ 'ABC.*'", Boolean.TRUE);
@@ -577,7 +577,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testRegexp2() throws Exception {
+    void testRegexp2() throws Exception {
         asserter.setVariable("str", "abc456");
         asserter.assertExpression("str =~ ~/.*456/", Boolean.TRUE);
         asserter.assertExpression("str !~ ~/ABC.*/", Boolean.TRUE);
@@ -600,7 +600,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         assertEquals(y0, y.get(0), "y0");
     }
     @Test
-    public void testStartsEndsWith() throws Exception {
+    void testStartsEndsWith() throws Exception {
         asserter.setVariable("x", "foobar");
         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
@@ -619,7 +619,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         asserter.assertExpression("x =^ [42, 54]", Boolean.TRUE);
     }
     @Test
-    public void testStartsEndsWithString() throws Exception {
+    void testStartsEndsWithString() throws Exception {
         asserter.setVariable("x", "foobar");
         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
@@ -629,7 +629,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testStartsEndsWithStringBuilder() throws Exception {
+    void testStartsEndsWithStringBuilder() throws Exception {
         asserter.setVariable("x", new StringBuilder("foobar"));
         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
@@ -639,7 +639,7 @@ public class ArithmeticOperatorTest extends JexlTestCase {
     }
 
     @Test
-    public void testStartsEndsWithStringDot() throws Exception {
+    void testStartsEndsWithStringDot() throws Exception {
         asserter.setVariable("x.y", "foobar");
         asserter.assertExpression("x.y =^ 'foo'", Boolean.TRUE);
         asserter.assertExpression("x.y =$ 'foo'", Boolean.FALSE);
@@ -651,24 +651,25 @@ public class ArithmeticOperatorTest extends JexlTestCase {
 
     /**
      * A comparator using an evaluated expression on objects as comparison 
arguments.
+     * <p>Lifetime is the sort method; it is thus safe to encapsulate the 
context</p>
      */
-    public static class PropertyComparator implements JexlCache.Reference, 
Comparator<Object> {
+     private static class PropertyComparator implements JexlCache.Reference, 
Comparator<Object> {
         private final JexlContext context = JexlEngine.getThreadContext();
         private final JexlArithmetic arithmetic;
-        private final JexlArithmetic.Uberspect uber;
+        private final JexlOperator.Uberspect operator;
         private final JexlScript expr;
         private Object cache;
 
         PropertyComparator(JexlArithmetic jexla, JexlScript expr) {
             this.arithmetic = jexla;
-            this.uber = 
JexlEngine.getThreadEngine().getUberspect().getArithmetic(arithmetic);
+            this.operator = 
JexlEngine.getThreadEngine().getUberspect().getOperator(arithmetic);
             this.expr = expr;
         }
         @Override
         public int compare(Object o1, Object o2) {
             final Object left = expr.execute(context, o1);
             final Object right = expr.execute(context, o2);
-            Object result = uber.tryEval(this, JexlOperator.COMPARE, left, 
right);
+            Object result = operator.tryOverload(this, JexlOperator.COMPARE, 
left, right);
             if (result instanceof Integer) {
                 return (int) result;
             }
@@ -715,7 +716,10 @@ public class ArithmeticOperatorTest extends JexlTestCase {
 
     @Test
     void testSortArray() {
-        final JexlEngine jexl = new JexlBuilder().arithmetic(new 
SortingArithmetic(true)).safe(false).strict(true).silent(false).create();
+        final JexlEngine jexl = new JexlBuilder()
+                .cache(32)
+                .arithmetic(new SortingArithmetic(true))
+                .silent(false).create();
         // test data, json like
         final String src = 
"[{'id':1,'name':'John','type':9},{'id':2,'name':'Doe','type':7},{'id':3,'name':'Doe','type':10}]";
         final Object a =  jexl.createExpression(src).evaluate(null);
diff --git 
a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java 
b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
index 9c25ed58..cdf38ed1 100644
--- a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
+++ b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
@@ -56,7 +56,7 @@ public class ReferenceUberspect implements JexlUberspect {
     /** A static signature for method(). */
     private static final Object[] EMPTY_PARAMS = {};
     /**
-     * Discovers a an optional getter.
+     * Discovers an optional getter.
      * <p>The method to be found should be named "{find}{P,p}property and 
return an Optional&lt;?&gt;.</p>
      *
      * @param is the uberspector
@@ -212,8 +212,13 @@ public class ReferenceUberspect implements JexlUberspect {
     }
 
     @Override
-    public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic 
arithmetic) {
-        return uberspect.getArithmetic(arithmetic);
+    public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {
+        return getOperator(arithmetic);
+    }
+
+    @Override
+    public JexlOperator.Uberspect getOperator(final JexlArithmetic arithmetic) 
{
+        return uberspect.getOperator(arithmetic);
     }
 
     @Override

Reply via email to