Skip to content

Commit

Permalink
JEXL-428 : Added JexlOperator.Uberspect interface;
Browse files Browse the repository at this point in the history
- Operator(s).java minor refactor & 3.4 compatibility improvements;
  • Loading branch information
henrib committed Oct 14, 2024
1 parent d7ee8e6 commit 3802c3b
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 258 deletions.
39 changes: 12 additions & 27 deletions src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,23 +157,11 @@ public interface Uberspect {
* 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.
*
Expand Down Expand Up @@ -413,8 +401,7 @@ protected Number asLongNumber(final boolean strict, final Object value) {
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;
Expand Down Expand Up @@ -1043,7 +1030,7 @@ protected boolean isFloatingPointNumber(final Object val) {
}
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);
}
Expand Down Expand Up @@ -1187,6 +1174,7 @@ public final Object logicalNot(final Object arg) {
* 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) {
Expand Down Expand Up @@ -1591,10 +1579,7 @@ public JexlArithmetic options(final JexlContext context) {
@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();
Expand All @@ -1603,10 +1588,10 @@ public JexlArithmetic options(final JexlEngine.Options options) {
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;
Expand Down Expand Up @@ -2046,7 +2031,7 @@ public BigDecimal toBigDecimal(final Object val) {
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);
Expand Down Expand Up @@ -2106,7 +2091,7 @@ public BigInteger toBigInteger(final Object val) {
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);
Expand Down Expand Up @@ -2198,7 +2183,7 @@ public double toDouble(final Object val) {
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.;
Expand Down Expand Up @@ -2252,7 +2237,7 @@ public int toInteger(final Object val) {
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;
Expand Down Expand Up @@ -2303,7 +2288,7 @@ public long toLong(final Object val) {
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;
Expand Down
116 changes: 115 additions & 1 deletion src/main/java/org/apache/commons/jexl3/JexlOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.apache.commons.jexl3;

import java.util.function.Consumer;

/**
* The JEXL operators.
*
Expand All @@ -36,7 +38,7 @@
* <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>
Expand Down Expand Up @@ -544,4 +546,116 @@ public final String getOperatorSymbol() {
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);
}
}
Loading

0 comments on commit 3802c3b

Please sign in to comment.