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) <=> -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<?>.</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