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 2d2300e0 JEXL-428: operators improvements; - use compare method as 
base for operators when possible; - cache resolution using method key; 
JEXL-429: namespace syntax disambiguation; - added feature flag 
(namespaceIdentifier); - variable used as function name added hint of non-ns 
call;
2d2300e0 is described below

commit 2d2300e0fea55e1da432d3ef1b04ba49afc5b025
Author: Henrib <hbies...@gmail.com>
AuthorDate: Wed Sep 25 07:44:56 2024 +0200

    JEXL-428: operators improvements;
    - use compare method as base for operators when possible;
    - cache resolution using method key;
    JEXL-429: namespace syntax disambiguation;
    - added feature flag (namespaceIdentifier);
    - variable used as function name added hint of non-ns call;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  37 ++-
 .../java/org/apache/commons/jexl3/JexlCache.java   |  17 ++
 .../org/apache/commons/jexl3/JexlFeatures.java     |  37 ++-
 .../commons/jexl3/internal/InterpreterBase.java    |   8 +-
 .../apache/commons/jexl3/internal/Operators.java   | 299 +++++++++++++--------
 .../jexl3/internal/introspection/MethodKey.java    |   2 +-
 .../jexl3/internal/introspection/Uberspect.java    |  58 +---
 .../org/apache/commons/jexl3/parser/JexlNode.java  |  13 +-
 .../apache/commons/jexl3/parser/JexlParser.java    |  15 +-
 .../commons/jexl3/ArithmeticOperatorTest.java      | 222 +++++++++++++++
 .../org/apache/commons/jexl3/Issues400Test.java    | 130 +++------
 11 files changed, 558 insertions(+), 280 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index a9c3f137..8c8e894c 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -157,10 +157,22 @@ public class JexlArithmetic {
          * Gets the most specific method for an operator.
          *
          * @param operator the operator
-         * @param arg      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... arg);
+        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.
@@ -476,9 +488,9 @@ public class JexlArithmetic {
     protected Boolean collectionContains(final Object collection, final Object 
value) {
         // convert arrays if needed
         final Object left = arrayWrap(collection);
-        if (left instanceof Collection<?>) {
+        if (left instanceof Collection) {
             final Object right = arrayWrap(value);
-            if (right instanceof Collection<?>) {
+            if (right instanceof Collection) {
                 return ((Collection<?>) left).containsAll((Collection<?>) 
right);
             }
             return ((Collection<?>) left).contains(value);
@@ -798,12 +810,23 @@ public class JexlArithmetic {
                 final Comparable<Object> comparable = (Comparable<Object>) 
left;
                 try {
                     return comparable.compareTo(right);
-                } catch(final ClassCastException castException) {
+                } catch (final ClassCastException castException) {
+                    // ignore it, continue in sequence
+                }
+            }
+            if (right instanceof Comparable<?>) {
+                @SuppressWarnings("unchecked") // OK because of instanceof 
check above
+                final Comparable<Object> comparable = (Comparable<Object>) 
right;
+                try {
+                    return -comparable.compareTo(left);
+                } catch (final ClassCastException castException) {
                     // ignore it, continue in sequence
                 }
             }
         }
-        throw new ArithmeticException("Object comparison:(" + left + " " + 
operator + " " + right + ")");
+        throw new ArithmeticException("Object comparison:(" + left +
+                " " + operator.getOperatorSymbol()
+                + " " + right + ")");
     }
 
     /**
@@ -971,7 +994,7 @@ public class JexlArithmetic {
      * Check for emptiness of various types: Number, Collection, Array, Map, 
String.
      *
      * @param object the object to check the emptiness of
-     * @param def the default value if object emptyness can not be determined
+     * @param def the default value if object emptiness can not be determined
      * @return the boolean or null if there is no arithmetic solution
      */
     public Boolean isEmpty(final Object object, final Boolean def) {
diff --git a/src/main/java/org/apache/commons/jexl3/JexlCache.java 
b/src/main/java/org/apache/commons/jexl3/JexlCache.java
index 6e783480..f5475d9a 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlCache.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlCache.java
@@ -76,4 +76,21 @@ public interface JexlCache<K, V> {
      * @return the cache size
      */
     int size();
+
+    /**
+     * A cached reference.
+     */
+    interface Reference {
+        /**
+         * Gets the referenced object.
+         * @return the referenced object
+         */
+        Object getCache();
+
+        /**
+         * Sets the referenced object.
+         * @param cache the referenced object
+         */
+        void setCache(Object cache);
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 7ec7f1a2..c05b776c 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -53,6 +53,7 @@ import java.util.function.Predicate;
  * <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code 
x -> x + x}
  * <li>Fat-arrow: use the  fat-arrow, ie {@code =>} for lambdas as in {@code x 
=> x + x}
  * <li>Namespace pragma: whether the {@code #pragma jexl.namespace.ns 
namespace} syntax is allowed</li>
+ * <li>Namespace identifier: whether the {@code ns:fun(...)} parser treats the 
ns:fun as one identifier, no spaces allowed</li>
  * <li>Import pragma: whether the {@code #pragma jexl.import 
fully.qualified.class.name} syntax is allowed</li>
  * <li>Comparator names: whether the comparator operator names can be used (as 
in {@code gt} for &gt;,
  * {@code lt} for &lt;, ...)</li>
@@ -71,7 +72,7 @@ public final class JexlFeatures {
         "register", "reserved variable", "local variable", "assign/modify",
         "global assign/modify", "array reference", "create instance", "loop", 
"function",
         "method call", "set/map/array literal", "pragma", "annotation", 
"script", "lexical", "lexicalShade",
-        "thin-arrow", "fat-arrow", "namespace pragma", "import pragma", 
"comparator names", "pragma anywhere",
+        "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", 
"import pragma", "comparator names", "pragma anywhere",
         "const capture", "ref capture"
     };
     /** Registers feature ordinal. */
@@ -112,16 +113,18 @@ public final class JexlFeatures {
     public static final int FAT_ARROW = 17;
     /** Namespace pragma feature ordinal. */
     public static final int NS_PRAGMA = 18;
+    /** Namespace syntax as an identifier (no space). */
+    public static final int NS_IDENTIFIER = 19;
     /** Import pragma feature ordinal. */
-    public static final int IMPORT_PRAGMA = 19;
+    public static final int IMPORT_PRAGMA = 20;
     /** Comparator names (legacy) syntax. */
-    public static final int COMPARATOR_NAMES = 20;
+    public static final int COMPARATOR_NAMES = 21;
     /** The pragma anywhere feature ordinal. */
-    public static final int PRAGMA_ANYWHERE = 21;
+    public static final int PRAGMA_ANYWHERE = 22;
     /** Captured variables are const. */
-    public static final int CONST_CAPTURE = 22;
+    public static final int CONST_CAPTURE = 23;
     /** Captured variables are reference. */
-    public static final int REF_CAPTURE = 23;
+    public static final int REF_CAPTURE = 24;
     /**
      * All features.
      * N.B. ensure this is updated if additional features are added.
@@ -574,6 +577,21 @@ public final class JexlFeatures {
         return this;
     }
 
+    /**
+     * Sets whether namespace as identifier syntax is enabled.
+     * <p>
+     * When enabled, a namespace call must be of the form 
<code>ns:fun(...)</code> with no
+     * spaces between the namespace name and the function.
+     * </p>
+     * @param flag true to enable, false to disable
+     * @return this features instance
+     * @since 3.4.1
+     */
+    public JexlFeatures namespaceIdentifier(final boolean flag) {
+        setFeature(NS_IDENTIFIER, flag);
+        return this;
+    }
+
     /**
      * @return the declared namespaces test.
      */
@@ -828,6 +846,13 @@ public final class JexlFeatures {
     public boolean supportsNamespacePragma() {
         return getFeature(NS_PRAGMA);
     }
+    /**
+     * @return true if namespace identifier syntax is enabled, false otherwise
+     * @since 3.4.1
+     */
+    public boolean supportsNamespaceIdentifier() {
+        return getFeature(NS_IDENTIFIER);
+    }
 
     /**
      * @return true if creating new instances is enabled, false otherwise
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 22e1e872..bd40e865 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -304,22 +304,16 @@ public abstract class InterpreterBase extends 
ParserVisitor {
     protected final JexlOptions options;
     /** Cache executors. */
     protected final boolean cache;
-
     /** Cancellation support. */
     protected final AtomicBoolean cancelled;
-
     /** The namespace resolver. */
     protected final JexlContext.NamespaceResolver ns;
-
     /** The class name resolver. */
     protected final JexlContext.ClassNameResolver fqcnSolver;
-
     /** The operators evaluation delegate. */
     protected final Operators 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. */
     protected Map<String, Object> functors;
 
@@ -355,7 +349,7 @@ public abstract class InterpreterBase extends ParserVisitor 
{
         this.cancelled = acancel != null ? acancel : new AtomicBoolean();
         this.functions = options.getNamespaces();
         this.functors = null;
-        this.operators = new Operators(this);
+        this.operators = (Operators) uberspect.getArithmetic(arithmetic);
         // the import package facility
         final Collection<String> imports = options.getImports();
         this.fqcnSolver = imports.isEmpty()
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java 
b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index 93e391ff..6417d27a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -21,27 +21,27 @@ import java.util.Set;
 import java.util.function.Consumer;
 
 import org.apache.commons.jexl3.JexlArithmetic;
+import org.apache.commons.jexl3.JexlCache;
 import org.apache.commons.jexl3.JexlEngine;
 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.introspection.JexlMethod;
-import org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import org.apache.commons.jexl3.parser.JexlNode;
 
 /**
  * Helper class to deal with operator overloading and specifics.
  * @since 3.0
  */
-public final class Operators {
-    /**
-     * Helper for postfix assignment operators.
-     * @param operator the operator
-     * @return true if operator is a postfix operator (x++, y--)
-     */
-    private static boolean isPostfix(final JexlOperator operator) {
-        return operator == JexlOperator.GET_AND_INCREMENT || operator == 
JexlOperator.GET_AND_DECREMENT;
-    }
+public final class Operators implements JexlArithmetic.Uberspect {
+    /** The uberspect. */
+    protected final Uberspect uberspect;
+    /** The arithmetic instance being analyzed. */
+    protected final JexlArithmetic arithmetic;
+    /** The set of overloaded operators. */
+    protected final Set<JexlOperator> overloads;
 
     /**
      * The comparison operators.
@@ -50,21 +50,35 @@ public final class Operators {
     private static final Set<JexlOperator> CMP_OPS =
             EnumSet.of(JexlOperator.GT, JexlOperator.LT, JexlOperator.EQ, 
JexlOperator.GTE, JexlOperator.LTE);
 
-    /** The owner. */
-    private final InterpreterBase interpreter;
-
-    /** The overloaded arithmetic operators. */
-    private final JexlArithmetic.Uberspect operators;
+    /**
+     * The postfix operators.
+     * <p>Used to determine the returned value in assignment.</p>
+     */
+    private static final Set<JexlOperator> POSTFIX_OPS =
+            EnumSet.of(JexlOperator.GET_AND_INCREMENT, 
JexlOperator.GET_AND_DECREMENT);
 
     /**
-     * Constructs a new instance.
-     * @param owner the owning interpreter
+     * Creates an instance.
+     * @param theUberspect the uberspect instance
+     * @param theArithmetic the arithmetic instance
+     * @param theOverloads  the overloaded operators
      */
-    Operators(final InterpreterBase owner) {
-        final JexlArithmetic arithmetic = owner.arithmetic;
-        final JexlUberspect uberspect = owner.uberspect;
-        this.interpreter = owner;
-        this.operators = uberspect.getArithmetic(arithmetic);
+    public Operators(final Uberspect theUberspect, final JexlArithmetic 
theArithmetic, final Set<JexlOperator> theOverloads) {
+        this.uberspect = theUberspect;
+        this.arithmetic = theArithmetic;
+        this.overloads = theOverloads;
+    }
+
+    @Override
+    public JexlMethod getOperator(final JexlOperator operator, final Object... 
args) {
+        return overloads.contains(operator) && args != null
+                ? uberspectOperator(arithmetic, operator, args)
+                : null;
+    }
+
+    @Override
+    public boolean overloads(final JexlOperator operator) {
+        return overloads.contains(operator);
     }
 
     /**
@@ -78,6 +92,29 @@ public final class Operators {
         return operator.getArity() == 1 && args.length > 1 ? new 
Object[]{args[0]} : args;
     }
 
+    /**
+     * Attempts finding a method in left and eventually narrowing right.
+     * @param methodName the method name
+     * @param right the left argument in the operator
+     * @param left the right argument in the operator
+     * @return a boolean is call was possible, null otherwise
+     * @throws Exception if invocation fails
+     */
+    private Boolean booleanDuckCall(final String methodName, final Object 
left, final Object right) throws Exception {
+        JexlMethod vm = uberspect.getMethod(left, methodName, right);
+        if (returnsBoolean(vm)) {
+            return (Boolean) vm.invoke(left, right);
+        }
+        final Object[] argv = { right };
+        if (arithmetic.narrowArguments(argv)) {
+            vm = uberspect.getMethod(left, methodName, argv);
+            if (returnsBoolean(vm)) {
+                return (Boolean) vm.invoke(left, argv);
+            }
+        }
+        return null;
+    }
+
     /**
      * Throw a NPE if operator is strict and one of the arguments is null.
      * @param arithmetic the JEXL arithmetic instance
@@ -99,26 +136,39 @@ public final class Operators {
     }
 
     /**
-     * Attempts finding a method in left and eventually narrowing right.
-     * @param methodName the method name
-     * @param right the left argument in the operator
-     * @param left the right argument in the operator
-     * @return a boolean is call was possible, null otherwise
-     * @throws Exception if invocation fails
+     * Triggered when an operator fails.
+     * @param ref     the node where the error originated from
+     * @param operator the operator symbol
+     * @param cause    the cause of error (if any)
+     * @return throws JexlException if strict and not silent, null otherwise
      */
-    private Boolean booleanDuckCall(final String methodName, final Object 
left, final Object right) throws Exception {
-        final JexlUberspect uberspect = interpreter.uberspect;
-        JexlMethod vm = uberspect.getMethod(left, methodName, right);
-        if (returnsBoolean(vm)) {
-            return (Boolean) vm.invoke(left, right);
+    private Object operatorError(final JexlCache.Reference ref, final 
JexlOperator operator, final Throwable cause) {
+        JexlNode node = (ref instanceof JexlNode) ? (JexlNode) ref : null;
+        Engine engine = (Engine) JexlEngine.getThreadEngine();
+        if (engine == null || engine.isStrict()) {
+            throw new JexlException.Operator(node, 
operator.getOperatorSymbol(), cause);
         }
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
-        final Object[] argv = { right };
-        if (arithmetic.narrowArguments(argv)) {
-            vm = uberspect.getMethod(left, methodName, argv);
-            if (returnsBoolean(vm)) {
-                return (Boolean) vm.invoke(left, argv);
-            }
+        if (engine.logger.isDebugEnabled()) {
+            engine.logger.debug(JexlException.operatorError(node, 
operator.getOperatorSymbol()), cause);
+        }
+        return null;
+    }
+
+    /**
+     * Seeks an implementation of an operator method in an arithmetic instance.
+     * <p>Method must <em><>not/em belong to JexlArithmetic</p>
+     * @param arithmetic the arithmetic instance
+     * @param operator the operator
+     * @param args the arguments
+     * @return a JexlMethod instance or null
+     */
+    private JexlMethod uberspectOperator(final JexlArithmetic arithmetic,
+                                       final JexlOperator operator,
+                                       final Object... args) {
+        final JexlMethod me = uberspect.getMethod(arithmetic, 
operator.getMethodName(), args);
+        if (!(me instanceof MethodExecutor) ||
+            !JexlArithmetic.class.equals(((MethodExecutor) 
me).getMethod().getDeclaringClass())) {
+            return me;
         }
         return null;
     }
@@ -158,18 +208,16 @@ public final class Operators {
      * @param object the object to check the emptiness of
      * @return the evaluation result
      */
-    Object empty(final JexlNode node, final Object object) {
+    Object empty(final JexlCache.Reference node, final Object object) {
         if (object == null) {
             return true;
         }
-        Object result = operators.overloads(JexlOperator.EMPTY)
+        Object result = overloads(JexlOperator.EMPTY)
                 ? tryOverload(node, JexlOperator.EMPTY, object)
                 : JexlEngine.TRY_FAILED;
         if (result == JexlEngine.TRY_FAILED) {
-            final JexlArithmetic arithmetic = interpreter.arithmetic;
             result = arithmetic.isEmpty(object, null);
             if (result == null) {
-                final JexlUberspect uberspect = interpreter.uberspect;
                 result = false;
                 // check if there is an isEmpty method on the object that 
returns a
                 // boolean and if so, just use it
@@ -178,7 +226,7 @@ public final class Operators {
                     try {
                         result = vm.invoke(object, 
InterpreterBase.EMPTY_PARAMS);
                     } catch (final Exception xany) {
-                        return interpreter.operatorError(node, 
JexlOperator.EMPTY, xany);
+                        return operatorError(node, JexlOperator.EMPTY, xany);
                     }
                 }
             }
@@ -195,18 +243,16 @@ public final class Operators {
      * @param object the object to get the size of
      * @return the evaluation result
      */
-    Object size(final JexlNode node, final Object object) {
+    Object size(final JexlCache.Reference node, final Object object) {
         if (object == null) {
             return 0;
         }
-        Object result = operators.overloads(JexlOperator.SIZE)
+        Object result = overloads(JexlOperator.SIZE)
                 ? tryOverload(node, JexlOperator.SIZE, object)
                 : JexlEngine.TRY_FAILED;
         if (result == JexlEngine.TRY_FAILED) {
-            final JexlArithmetic arithmetic = interpreter.arithmetic;
             result = arithmetic.size(object, null);
             if (result == null) {
-                final JexlUberspect uberspect = interpreter.uberspect;
                 // check if there is a size method on the object that returns 
an
                 // integer and if so, just use it
                 final JexlMethod vm = uberspect.getMethod(object, "size", 
InterpreterBase.EMPTY_PARAMS);
@@ -214,7 +260,7 @@ public final class Operators {
                     try {
                         result = vm.invoke(object, 
InterpreterBase.EMPTY_PARAMS);
                     } catch (final Exception xany) {
-                        interpreter.operatorError(node, JexlOperator.SIZE, 
xany);
+                        operatorError(node, JexlOperator.SIZE, xany);
                     }
                 }
             }
@@ -234,12 +280,11 @@ public final class Operators {
      * @param left  the right operand
      * @return true if left matches right, false otherwise
      */
-    boolean contains(final JexlNode node, final JexlOperator operator, final 
Object left, final Object right) {
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
+    boolean contains(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
         final boolean contained;
         try {
             // try operator overload
-            final Object result = operators.overloads(JexlOperator.CONTAINS)
+            final Object result = overloads(JexlOperator.CONTAINS)
                     ? tryOverload(node, JexlOperator.CONTAINS, left, right)
                     : null;
             if (result instanceof Boolean) {
@@ -260,9 +305,10 @@ public final class Operators {
                     }
                 }
             }
+            // not-contains is !contains
             return (JexlOperator.CONTAINS == operator) == contained;
         } catch (final Exception xrt) {
-            interpreter.operatorError(node, operator, xrt);
+            operatorError(node, operator, xrt);
             return false;
         }
     }
@@ -275,12 +321,11 @@ public final class Operators {
      * @param right    the right operand
      * @return true if left starts with right, false otherwise
      */
-    boolean startsWith(final JexlNode node, final JexlOperator operator, final 
Object left, final Object right) {
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
+    boolean startsWith(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
         final boolean starts;
         try {
             // try operator overload
-            final Object result = operators.overloads(JexlOperator.STARTSWITH)
+            final Object result = overloads(JexlOperator.STARTSWITH)
                     ? tryOverload(node, JexlOperator.STARTSWITH, left, right)
                     : null;
             if (result instanceof Boolean) {
@@ -301,9 +346,10 @@ public final class Operators {
                     }
                 }
             }
+            // not-startswith is !starts-with
             return (JexlOperator.STARTSWITH == operator) == starts;
         } catch (final Exception xrt) {
-            interpreter.operatorError(node, operator, xrt);
+            operatorError(node, operator, xrt);
             return false;
         }
     }
@@ -316,12 +362,11 @@ public final class Operators {
      * @param right    the right operand
      * @return true if left ends with right, false otherwise
      */
-    boolean endsWith(final JexlNode node, final JexlOperator operator, final 
Object left, final Object right) {
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
+    boolean endsWith(final JexlCache.Reference node, final JexlOperator 
operator, final Object left, final Object right) {
         try {
             final boolean ends;
             // try operator overload
-            final Object result = operators.overloads(JexlOperator.ENDSWITH)
+            final Object result = overloads(JexlOperator.ENDSWITH)
                 ? tryOverload(node, JexlOperator.ENDSWITH, left, right)
                 : null;
             if (result instanceof Boolean) {
@@ -342,9 +387,10 @@ public final class Operators {
                     }
                 }
             }
+            // not-endswith is !ends-with
             return (JexlOperator.ENDSWITH == operator) == ends;
         } catch (final Exception xrt) {
-            interpreter.operatorError(node, operator, xrt);
+            operatorError(node, operator, xrt);
             return false;
         }
     }
@@ -366,14 +412,13 @@ public final class Operators {
                                final JexlOperator operator,
                                final Consumer<Object> assignFun,
                                final Object...args) {
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
         if (args.length < operator.getArity()) {
             return JexlEngine.TRY_FAILED;
         }
         Object result;
         try {
         // try to call overload with side effect; the object is modified
-        if (operators.overloads(operator)) {
+        if (overloads(operator)) {
             result = tryOverload(node, operator, arguments(operator, args));
             if (result != JexlEngine.TRY_FAILED) {
                 return result; // 1
@@ -381,11 +426,11 @@ public final class Operators {
         }
         // try to call base overload (ie + for +=)
         final JexlOperator base = operator.getBaseOperator();
-        if (base != null && operators.overloads(base)) {
+        if (base != null && overloads(base)) {
             result = tryOverload(node, base, arguments(base, args));
             if (result != JexlEngine.TRY_FAILED) {
                 assignFun.accept(result);
-                return isPostfix(operator) ? args[0] : result; // 2
+                return POSTFIX_OPS.contains(operator) ? args[0] : result; // 2
             }
         }
         // default implementation for self-* operators
@@ -445,7 +490,7 @@ public final class Operators {
             assignFun.accept(result);
             return result; // 5
         } catch (final Exception xany) {
-            interpreter.operatorError(node, operator, xany);
+            operatorError(node, operator, xany);
         }
         return JexlEngine.TRY_FAILED;
     }
@@ -463,51 +508,56 @@ public final class Operators {
      * @param args     the arguments
      * @return the result of the operator evaluation or TRY_FAILED
      */
-    Object tryOverload(final JexlNode node, final JexlOperator operator, final 
Object... args) {
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
+    Object tryOverload(final JexlCache.Reference node, final JexlOperator 
operator, final Object... args) {
         controlNullOperands(arithmetic, operator, args);
+        Engine engine = (Engine) JexlEngine.getThreadEngine();
         try {
-            final boolean cache = interpreter.cache;
-            if (cache) {
-                final Object cached = node.jjtGetValue();
-                if (cached instanceof JexlMethod) {
-                    // we found a method on previous call; try and reuse it 
(*1)
-                    final JexlMethod me = (JexlMethod) cached;
-                    final Object eval = me.tryInvoke(operator.getMethodName(), 
arithmetic, args);
-                    if (!me.tryFailed(eval)) {
-                        return eval;
-                    }
-                } else if (cached instanceof MethodKey) {
-                    // check for a fail-fast, we tried to find an overload 
before but could not (*2)
-                    final MethodKey cachedKey = (MethodKey) cached;
-                    final MethodKey key = new 
MethodKey(operator.getMethodName(), args);
-                    if (key.equals(cachedKey)) {
-                        return JexlEngine.TRY_FAILED;
-                    }
+            return tryEval(engine == null || engine.cache != null ? node : 
null, operator, args);
+        } catch (final Exception xany) {
+            // ignore return if lenient, will return try_failed
+            operatorError(node, operator, xany);
+        }
+        return JexlEngine.TRY_FAILED;
+    }
+
+    @Override
+    public Object tryEval(JexlCache.Reference node, JexlOperator operator, 
Object...args) {
+        if (node != null) {
+            final Object cached = node.getCache();
+            if (cached instanceof JexlMethod) {
+                // we found a method on previous call; try and reuse it (*1)
+                final JexlMethod me = (JexlMethod) cached;
+                final Object eval = me.tryInvoke(operator.getMethodName(), 
arithmetic, args);
+                if (!me.tryFailed(eval)) {
+                    return eval;
                 }
-            }
-            // trying to find an operator overload
-            JexlMethod vm = operators.overloads(operator) ? 
operators.getOperator(operator, args) : null;
-            // no direct overload, any special case ?
-            if (vm == null) {
-               vm = getAlternateOverload(operator, args);
-            }
-            // *1: found a method, try it and cache it if successful
-            if (vm != null) {
-                final Object result = vm.tryInvoke(operator.getMethodName(), 
arithmetic, args);
-                if (cache && !vm.tryFailed(result)) {
-                    node.jjtSetValue(vm);
+            } else if (cached instanceof MethodKey) {
+                // check for a fail-fast, we tried to find an overload before 
but could not (*2)
+                final MethodKey cachedKey = (MethodKey) cached;
+                final MethodKey key = new MethodKey(operator.getMethodName(), 
args);
+                if (key.equals(cachedKey)) {
+                    return JexlEngine.TRY_FAILED;
                 }
-                return result;
             }
-            // *2: could not find an overload for this operator and arguments, 
keep track of the fail
-            if (cache) {
-                MethodKey key = new MethodKey(operator.getMethodName(), args);
-                node.jjtSetValue(key);
+        }
+        // trying to find an operator overload
+        JexlMethod vm = getOperator(operator, args);
+        // no direct overload, any special case ?
+        if (vm == null) {
+            vm = getAlternateOverload(operator, args);
+        }
+        // *1: found a method, try it and cache it if successful
+        if (vm != null) {
+            final Object result = vm.tryInvoke(operator.getMethodName(), 
arithmetic, args);
+            if (node != null && !vm.tryFailed(result)) {
+                node.setCache(vm);
             }
-        } catch (final Exception xany) {
-            // ignore return if lenient, will return try_failed
-            interpreter.operatorError(node, operator, xany);
+            return result;
+        }
+        if (node != null) {
+            // *2: could not find an overload for this operator and arguments, 
keep track of the fail
+            MethodKey key = new MethodKey(operator.getMethodName(), args);
+            node.setCache(key);
         }
         return JexlEngine.TRY_FAILED;
     }
@@ -521,10 +571,14 @@ public final class Operators {
      */
     private JexlMethod getAlternateOverload(final JexlOperator operator, final 
Object... args) {
         // comparison operators may use the compare overload in derived 
arithmetic
-        if (CMP_OPS.contains(operator)) {
-            JexlMethod cmp = operators.getOperator(JexlOperator.COMPARE, args);
+        if (CMP_OPS.contains(operator) && args.length == 2) {
+            JexlMethod cmp = getOperator(JexlOperator.COMPARE, args);
+            if (cmp != null) {
+                return new Operators.CompareMethod(operator, cmp);
+            }
+            cmp = getOperator(JexlOperator.COMPARE, args[1], args[0]);
             if (cmp != null) {
-                return new CompareMethod(operator, cmp);
+                return new Operators.AntiCompareMethod(operator, cmp);
             }
         }
         return null;
@@ -573,7 +627,7 @@ public final class Operators {
             return JexlEngine.TRY_FAILED;
         }
 
-        private boolean operate(final int cmp) {
+        protected boolean operate(final int cmp) {
             switch(operator) {
                 case EQ: return cmp == 0;
                 case LT: return cmp < 0;
@@ -584,4 +638,29 @@ public final class Operators {
             throw new ArithmeticException("unexpected operator " + operator);
         }
     }
+
+    /**
+     * The reverse compare swaps left and right arguments and changes the sign 
of the
+     * comparison result.
+     */
+    private class AntiCompareMethod extends CompareMethod {
+
+        AntiCompareMethod(JexlOperator op, 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]);
+            if (cmp instanceof Integer) {
+                return operate(-(int) cmp);
+            }
+            return JexlEngine.TRY_FAILED;
+        }
+
+        @Override
+        public Object invoke(Object arithmetic, Object... params) throws 
Exception {
+            return operate(-(int) compare.invoke(arithmetic, params[1], 
params[0]));
+        }
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
index 360878f1..36bcca68 100644
--- 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
+++ 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
@@ -589,7 +589,7 @@ public final class MethodKey {
      * @param aMethod the method to generate the key from
      * @param args    the intended method arguments
      */
-    public MethodKey(final String aMethod, final Object[] args) {
+    public MethodKey(final String aMethod, final Object... args) {
         // !! keep this in sync with the other ctor (hash code) !!
         this.method = aMethod;
         int hash = this.method.hashCode();
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 3454d359..332800e6 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
@@ -32,6 +32,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.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
@@ -49,38 +50,6 @@ import org.apache.commons.logging.LogFactory;
  * @since 1.0
  */
 public class Uberspect implements JexlUberspect {
-    /**
-     * The concrete Uberspect Arithmetic class.
-     */
-    protected class ArithmeticUberspect implements JexlArithmetic.Uberspect {
-        /** The arithmetic instance being analyzed. */
-        private final JexlArithmetic arithmetic;
-        /** The set of overloaded operators. */
-        private final Set<JexlOperator> overloads;
-
-        /**
-         * Creates an instance.
-         * @param theArithmetic the arithmetic instance
-         * @param theOverloads  the overloaded operators
-         */
-        ArithmeticUberspect(final JexlArithmetic theArithmetic, final 
Set<JexlOperator> theOverloads) {
-            this.arithmetic = theArithmetic;
-            this.overloads = theOverloads;
-        }
-
-        @Override
-        public JexlMethod getOperator(final JexlOperator operator, final 
Object... args) {
-            return overloads.contains(operator) && args != null
-                    ? uberspectOperator(arithmetic, operator, args)
-                    : null;
-        }
-
-        @Override
-        public boolean overloads(final JexlOperator operator) {
-            return overloads.contains(operator);
-        }
-    }
-
     /** Publicly exposed special failure object returned by tryInvoke. */
     public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
     /** The logger to use for all warnings and errors. */
@@ -154,8 +123,8 @@ public class Uberspect implements JexlUberspect {
     // CSON: DoubleCheckedLocking
 
     @Override
-    public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic 
arithmetic) {
-        JexlArithmetic.Uberspect jau = null;
+    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 -> {
@@ -187,30 +156,11 @@ public class Uberspect implements JexlUberspect {
                 }
                 return newOps;
             });
-            jau = new ArithmeticUberspect(arithmetic, ops);
+            jau = new Operators(this, arithmetic, ops);
         }
         return jau;
     }
 
-    /**
-     * Seeks an implementation of an operator method in an arithmetic instance.
-     * <p>Method must <em><>not/em belong to JexlArithmetic</p>
-     * @param arithmetic the arithmetic instance
-     * @param operator the operator
-     * @param args the arguments
-     * @return a JexlMethod instance or null
-     */
-    final JexlMethod uberspectOperator(final JexlArithmetic arithmetic,
-                                       final JexlOperator operator,
-                                       final Object... args) {
-        final JexlMethod me = getMethod(arithmetic, operator.getMethodName(), 
args);
-        if (!(me instanceof MethodExecutor) ||
-            !JexlArithmetic.class.equals(((MethodExecutor) 
me).getMethod().getDeclaringClass())) {
-            return me;
-        }
-        return null;
-    }
-
     /**
      * Gets a class by name through this introspector class loader.
      * @param className the class name
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
index 2b505913..b44e70de 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -17,6 +17,7 @@
 package org.apache.commons.jexl3.parser;
 
 import org.apache.commons.jexl3.JexlArithmetic;
+import org.apache.commons.jexl3.JexlCache;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JxltEngine;
 import org.apache.commons.jexl3.introspection.JexlMethod;
@@ -28,7 +29,7 @@ import org.apache.commons.jexl3.introspection.JexlPropertySet;
  *
  * @since 2.0
  */
-public abstract class JexlNode extends SimpleNode {
+public abstract class JexlNode extends SimpleNode implements 
JexlCache.Reference {
     /**
      * A marker interface for constants.
      * @param <T> the literal type
@@ -58,6 +59,16 @@ public abstract class JexlNode extends SimpleNode {
         void setExpression(JxltEngine.Expression expr);
     }
 
+    @Override
+    public Object getCache() {
+        return jjtGetValue();
+    }
+
+    @Override
+    public void setCache(Object cache) {
+        jjtSetValue(cache);
+    }
+
     /**
      * An info bound to its node.
      * <p>Used to parse expressions for templates.
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index 11018604..d0e5df66 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -628,11 +628,18 @@ public abstract class JexlParser extends StringParser {
         if (!"(".equals(paren.image)) {
             return false;
         }
-        // if namespace name is shared with a variable name, use syntactic hint
-        final String name = ns.image;
-        if (isVariable(name)) {
+        // namespace as identifier means no spaces in between ns, colon and 
fun, no matter what
+        if (featureController.getFeatures().supportsNamespaceIdentifier()) {
+            return colon.beginColumn - 1 == ns.endColumn
+                    && colon.endColumn == fun.beginColumn - 1;
+        }
+        // if namespace name is shared with a variable name
+        // or if fun is a variable name (likely a function call),
+        // use syntactic hint
+        if (isVariable(ns.image) || isVariable(fun.image)) {
             // the namespace sticks to the colon as in 'ns:fun()' (vs 'ns : 
fun()')
-            return colon.beginColumn - 1 == ns.endColumn && (colon.endColumn 
== fun.beginColumn - 1 || isNamespace(name));
+            return colon.beginColumn - 1 == ns.endColumn
+                    && (colon.endColumn == fun.beginColumn - 1 || 
isNamespace(ns.image));
         }
         return true;
     }
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index b023d2b2..c42c591b 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -18,16 +18,22 @@
 package org.apache.commons.jexl3;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.StringWriter;
+import java.math.MathContext;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,6 +45,7 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TimeZone;
 import java.util.TreeSet;
+import java.util.regex.Pattern;
 
 import org.apache.commons.jexl3.junit.Asserter;
 import org.junit.jupiter.api.BeforeEach;
@@ -641,4 +648,219 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         asserter.assertExpression("x.y =$ 'foo'", Boolean.TRUE);
     }
 
+
+    /**
+     * A comparator using an evaluated expression on objects as comparison 
arguments.
+     */
+    public 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 JexlScript expr;
+        private Object cache;
+
+        PropertyComparator(JexlArithmetic jexla, JexlScript expr) {
+            this.arithmetic = jexla;
+            this.uber = 
JexlEngine.getThreadEngine().getUberspect().getArithmetic(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);
+            if (result instanceof Integer) {
+                return (int) result;
+            }
+            return arithmetic.compare(left, right, JexlOperator.COMPARE);
+        }
+
+        @Override
+        public Object getCache() {
+            return cache;
+        }
+
+        @Override
+        public void setCache(Object cache) {
+            this.cache = cache;
+        }
+    }
+
+    public static class SortingArithmetic extends JexlArithmetic {
+        public SortingArithmetic(boolean strict) {
+            this( strict, null, Integer.MIN_VALUE);
+        }
+
+        private SortingArithmetic(boolean strict, MathContext context, int 
scale) {
+            super(strict, context, scale);
+        }
+
+        public int compare(Integer left, Integer right) {
+            return left.compareTo(right);
+        }
+
+        public int compare(String left, String right) {
+            return left.compareTo(right);
+        }
+
+        /**
+         * Sorts an array using a script to evaluate the property used to 
compare elements.
+         * @param array the elements array
+         * @param expr the property evaluation lambda
+         */
+        public void sort(final Object[] array, final JexlScript expr) {
+            Arrays.sort(array, new PropertyComparator(this, expr));
+        }
+    }
+
+    @Test
+    void testSortArray() {
+        final JexlEngine jexl = new JexlBuilder().arithmetic(new 
SortingArithmetic(true)).safe(false).strict(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);
+        assertNotNull(a);
+        // row 0 and 1 are not ordered
+        final Map[] m = (Map[]) a;
+        assertEquals(9, m[0].get("type"));
+        assertEquals(7, m[1].get("type"));
+        // sort the elements on the type
+        jexl.createScript("array.sort( e -> e.type )", "array").execute(null, 
a);
+        // row 0 and 1 are now ordered
+        assertEquals(7, m[0].get("type"));
+        assertEquals(9, m[1].get("type"));
+    }
+
+
+    public static class MatchingArithmetic extends JexlArithmetic {
+        public MatchingArithmetic(final boolean astrict) {
+            super(astrict);
+        }
+
+        public boolean contains(final Pattern[] container, final String str) {
+            for(final Pattern pattern : container) {
+                if (pattern.matcher(str).matches()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    @Test
+    void testPatterns() {
+        final JexlEngine jexl = new JexlBuilder().arithmetic(new 
MatchingArithmetic(true)).create();
+        final JexlScript script = jexl.createScript("str =~ [~/abc.*/, 
~/def.*/]", "str");
+        assertTrue((boolean) script.execute(null, "abcdef"));
+        assertTrue((boolean) script.execute(null, "defghi"));
+        assertFalse((boolean) script.execute(null, "ghijkl"));
+    }
+
+
+    public static class Arithmetic428 extends JexlArithmetic {
+        public Arithmetic428(boolean strict) {
+            this( strict, null, Integer.MIN_VALUE);
+        }
+
+        private Arithmetic428(boolean strict, MathContext context, int scale) {
+            super(strict, context, scale);
+        }
+
+        public int compare(Instant lhs, String str) {
+            Instant rhs = Instant.parse(str);
+            return lhs.compareTo(rhs);
+        }
+    }
+
+    @Test
+    void test428() {
+        // see JEXL-428
+        final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new 
Arithmetic428(true)).create();
+        final String rhsstr ="2024-09-09T10:42:42.00Z";
+        final Instant rhs = Instant.parse(rhsstr);
+        final String lhs = "2020-09-09T01:24:24.00Z";
+        JexlScript script;
+        script = jexl.createScript("x < y", "x", "y");
+        final JexlScript s0 = script;
+        assertThrows(JexlException.class, () -> s0.execute(null, 42, rhs));
+        assertTrue((boolean) script.execute(null, lhs, rhs));
+        assertTrue((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, rhs, lhs));
+        assertFalse((boolean) script.execute(null, rhs, lhs));
+        assertTrue((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, rhs, lhs));
+
+        script = jexl.createScript("x <= y", "x", "y");
+        final JexlScript s1 = script;
+        assertThrows(JexlException.class, () -> s1.execute(null, 42, rhs));
+        assertTrue((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, rhs, lhs));
+
+        script = jexl.createScript("x >= y", "x", "y");
+        final JexlScript s2 = script;
+        assertThrows(JexlException.class, () -> s2.execute(null, 42, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertTrue((boolean) script.execute(null, rhs, lhs));
+        assertTrue((boolean) script.execute(null, rhs, lhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertTrue((boolean) script.execute(null, rhs, lhs));
+
+        script = jexl.createScript("x > y", "x", "y");
+        final JexlScript s3 = script;
+        assertThrows(JexlException.class, () -> s3.execute(null, 42, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertTrue((boolean) script.execute(null, rhs, lhs));
+
+        script = jexl.createScript("x == y", "x", "y");
+        assertFalse((boolean) script.execute(null, 42, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+        assertTrue((boolean) script.execute(null, rhs, rhsstr));
+        assertTrue((boolean) script.execute(null, rhsstr, rhs));
+        assertFalse((boolean) script.execute(null, lhs, rhs));
+
+        script = jexl.createScript("x != y", "x", "y");
+        assertTrue((boolean) script.execute(null, 42, rhs));
+        assertTrue((boolean) script.execute(null, lhs, rhs));
+        assertFalse((boolean) script.execute(null, rhs, rhsstr));
+    }
+
+    public static class Arithmetic429 extends JexlArithmetic {
+        public Arithmetic429(boolean astrict) {
+            super(astrict);
+        }
+
+        public int compare(String lhs, Number rhs) {
+            return lhs.compareTo(rhs.toString());
+        }
+    }
+
+    @Test
+    void test429a() {
+        final JexlEngine jexl = new JexlBuilder()
+                .arithmetic(new Arithmetic429(true))
+                .cache(32)
+                .create();
+        String src;
+        JexlScript script;
+        src = "'1.1' > 0";
+        script = jexl.createScript(src);
+        assertTrue((boolean) script.execute(null));
+        src = "1.2 <= '1.20'";
+        script = jexl.createScript(src);
+        assertTrue((boolean) script.execute(null));
+        src = "1.2 >= '1.2'";
+        script = jexl.createScript(src);
+        assertTrue((boolean) script.execute(null));
+        src = "1.2 < '1.2'";
+        script = jexl.createScript(src);
+        assertFalse((boolean) script.execute(null));
+        src = "1.2 > '1.2'";
+        script = jexl.createScript(src);
+        assertFalse((boolean) script.execute(null));
+        src = "1.20 == 'a'";
+        script = jexl.createScript(src);
+        assertFalse((boolean) script.execute(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 1a255098..81e7cd55 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -28,18 +28,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
-import java.math.MathContext;
-import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.regex.Pattern;
 
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.junit.jupiter.api.Test;
@@ -449,98 +445,52 @@ public class Issues400Test {
         assertEquals(42, r);
     }
 
-    public static class SortingContext extends MapContext {
-        /**
-         * Sorts an array using a script to evaluate the property used to 
compare elements.
-         * @param array the elements array
-         * @param expr the property evaluation lambda
-         */
-        public void sort(final Object[] array, final JexlScript expr) {
-            final Comparator<Object> cmp = (o1, o2) -> {
-                final Comparable left = (Comparable<?>) 
expr.execute(SortingContext.this, o1);
-                final Comparable right = (Comparable<?>) 
expr.execute(SortingContext.this, o2);
-                return left.compareTo(right);
-            };
-            Arrays.sort(array, cmp);
+    public static class Ns429 {
+        public int f(final int x) {
+            return x * 10000 + 42;
         }
     }
 
     @Test
-    void testSortArray() {
-        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(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);
-        assertNotNull(a);
-        // row 0 and 1 are not ordered
-        final Map[] m = (Map[]) a;
-        assertEquals(9, m[0].get("type"));
-        assertEquals(7, m[1].get("type"));
-        // sort the elements on the type
-        jexl.createScript("array.sort( e -> e.type )", "array").execute(new 
SortingContext(), a);
-        // row 0 and 1 are now ordered
-        assertEquals(7, m[0].get("type"));
-        assertEquals(9, m[1].get("type"));
-    }
-
-
-    public static class MatchingArithmetic extends JexlArithmetic {
-        public MatchingArithmetic(final boolean astrict) {
-            super(astrict);
-        }
-
-        public boolean contains(final Pattern[] container, final String str) {
-            for(final Pattern pattern : container) {
-                if (pattern.matcher(str).matches()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    @Test
-    void testPatterns() {
-        final JexlEngine jexl = new JexlBuilder().arithmetic(new 
MatchingArithmetic(true)).create();
-        final JexlScript script = jexl.createScript("str =~ [~/abc.*/, 
~/def.*/]", "str");
-        assertTrue((boolean) script.execute(null, "abcdef"));
-        assertTrue((boolean) script.execute(null, "defghi"));
-        assertFalse((boolean) script.execute(null, "ghijkl"));
-    }
-
-
-    public static class Arithmetic428 extends JexlArithmetic {
-        public Arithmetic428(boolean strict) {
-            this( strict, null, Integer.MIN_VALUE);
-        }
-
-        private Arithmetic428(boolean strict, MathContext context, int scale) {
-            super(strict, context, scale);
-        }
-
-        public int compare(Instant lhs, String str) {
-            Instant rhs = Instant.parse(str);
-            return lhs.compareTo(rhs);
-        }
-
-        public int compare(String str, Instant date) {
-            return -compare(date, str);
-        }
+    void test429a() {
+        MapContext ctxt = new MapContext();
+        //ctxt.set("b", 1);
+        JexlFeatures features = JexlFeatures.createDefault();
+        final JexlEngine jexl = new JexlBuilder()
+                .features(features)
+                .safe(false).strict(true).silent(false).create();
+        JexlScript f = jexl.createScript("x -> x");
+        ctxt.set("f", f);
+        String src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
+                +"b ? b : f(2);";
+        JexlScript script = jexl.createScript(src, "b");
+        assertEquals(1, (int) script.execute(ctxt, 1));
+
+        src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
+                +"b ? b:f(2) : 1;";
+        script = jexl.createScript(src, "b");
+        assertEquals(20042, (int) script.execute(ctxt, 1));
     }
 
     @Test
-    void testIssue428() {
-        final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new 
Arithmetic428(true)).create();
-        Instant rhs = Instant.parse("2024-09-09T10:42:42.00Z");
-        String lhs = "2020-09-09T01:24:24.00Z";
-        JexlScript script;
-        script = jexl.createScript("x < y", "x", "y");
-        assertTrue((boolean) script.execute(null, lhs, rhs));
-        assertTrue((boolean) script.execute(null, lhs, rhs));
-        assertFalse((boolean) script.execute(null, rhs, lhs));
-        assertFalse((boolean) script.execute(null, rhs, lhs));
-        assertTrue((boolean) script.execute(null, lhs, rhs));
-        assertFalse((boolean) script.execute(null, rhs, lhs));
+    void test429b() {
+        MapContext ctxt = new MapContext();
+        ctxt.set("b", 1);
+        JexlFeatures features = JexlFeatures.createDefault();
+        features.namespaceIdentifier(true);
+        final JexlEngine jexl = new JexlBuilder()
+                .features(features)
+                .safe(false).strict(true).silent(false).create();
+        JexlScript f = jexl.createScript("x -> x");
+        ctxt.set("f", f);
+        String src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
+                +"b ? b : f(2);";
+        JexlScript script = jexl.createScript(src);
+        assertEquals(1, (int) script.execute(ctxt));
+
+        src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
+                +"b ? b:f(2) : 1;";
+        script = jexl.createScript(src);
+        assertEquals(20042, (int) script.execute(ctxt));
     }
-
 }

Reply via email to