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 1b9d1051 JEXL-384: cleaner strict cast handling, closer to original
proposal;
1b9d1051 is described below
commit 1b9d10513f8963230efe39aec73b7383d386345d
Author: henrib <[email protected]>
AuthorDate: Tue Nov 8 20:06:06 2022 +0100
JEXL-384: cleaner strict cast handling, closer to original proposal;
---
.../org/apache/commons/jexl3/JexlArithmetic.java | 1032 +++++++++++---------
.../apache/commons/jexl3/internal/Interpreter.java | 2 +-
.../org/apache/commons/jexl3/Arithmetic360.java | 9 +-
3 files changed, 586 insertions(+), 457 deletions(-)
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 9f6eca41..21bd8595 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -22,7 +22,6 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
@@ -81,9 +80,6 @@ public class JexlArithmetic {
/** Whether this JexlArithmetic instance behaves in strict or lenient
mode. */
private final boolean strict;
- /** Whether this JexlArithmetic instance allows null as argument to cast
methods - toXXX(). */
- private final boolean strictCast;
-
/** The big decimal math context. */
private final MathContext mathContext;
@@ -123,17 +119,6 @@ public class JexlArithmetic {
// ignore
}
this.ctor = actor;
- boolean cast = strict;
- // if isStrict is not overridden, we are in strict-cast mode
- if (cast) {
- try {
- Method istrict = getClass().getMethod("isStrict",
JexlOperator.class);
- cast = JexlArithmetic.class == istrict.getDeclaringClass();
- } catch (Exception e) {
- // ignore
- }
- }
- this.strictCast = cast;
}
/**
@@ -380,198 +365,565 @@ public class JexlArithmetic {
}
/**
- * Checks whether this JexlArithmetic instance
- * strictly considers null as an error when used as operand unexpectedly.
+ * Coerce to a primitive boolean.
+ * <p>Double.NaN, null, "false" and empty string coerce to false.</p>
*
- * @return true if strict, false if lenient
+ * @param val value to coerce
+ * @return the boolean value if coercion is possible, true if value was
not null.
*/
- public boolean isStrict() {
- return strict;
+ public boolean toBoolean(final Object val) {
+ return toBoolean(strict, val);
}
/**
- * Checks whether this JexlArithmetic instance
- * strictly considers null as an error when used as operand of a cast
method (toXXX())..
+ * Coerce to a primitive int.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @return true if strict-cast, false if lenient
+ * @param val value to coerce
+ * @return the value coerced to int
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- public boolean isStrictCast() {
- return strictCast;
+ public int toInteger(final Object val) {
+ return toInteger(strict, val);
}
/**
- * Checks whether this arithmetic considers a given operator as strict or
null-safe.
- * <p>When an operator is strict, it does <em>not</em> accept null
arguments when the arithmetic is strict.
- * If null-safe (ie not-strict), the operator does accept null arguments
even if the arithmetic itself is strict.</p>
- * <p>The default implementation considers equal/not-equal operators as
null-safe so one can check for null as in
- * <code>if (myvar == null) {...}</code>. Note that this operator is used
for equal and not-equal syntax. The complete
- * list of operators that are not strict are (==, [], []=, ., .=, empty,
size, contains). </p>
- * <p>
- * An arithmetic refining its strict behavior handling for more
operators must declare which by overriding
- * this method.
- * </p>
- * <p>
- * If this method is overridden, the arithmetic instance is
<em>NOT</em> in strict-cast mode. Tp restore the
- * strict-cast behavior, override the {@link #isStrictCast()} method/
- * </p>
- * @param operator the operator to check for null-argument(s) handling
- * @return true if operator considers null arguments as errors, false if
operator has appropriate semantics
- * for null argument(s)
+ * Coerce to a primitive long.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
+ *
+ * @param val value to coerce
+ * @return the value coerced to long
+ * @throws ArithmeticException if value is null and mode is strict or if
coercion is not possible
*/
- public boolean isStrict(JexlOperator operator) {
- if (operator != null) {
- switch (operator) {
- case EQ:
- case ARRAY_GET:
- case ARRAY_SET:
- case PROPERTY_GET:
- case PROPERTY_SET:
- case EMPTY:
- case SIZE:
- case CONTAINS:
- return false;
- }
- }
- return isStrict();
+ public long toLong(final Object val) {
+ return toLong(strict, val);
}
/**
- * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ * Coerce to a BigInteger.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @return the math context
+ * @param val the object to be coerced.
+ * @return a BigDecimal
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- public MathContext getMathContext() {
- return mathContext;
+ public BigInteger toBigInteger(final Object val) {
+ return toBigInteger(strict, val);
}
/**
- * The BigDecimal scale used for comparison and coericion operations.
+ * Coerce to a primitive double.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @return the scale
+ * @param val value to coerce.
+ * @return The double coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- public int getMathScale() {
- return mathScale;
+ public double toDouble(final Object val) {
+ return toDouble(strict, val);
}
/**
- * Ensure a big decimal is rounded by this arithmetic scale and rounding
mode.
+ * Coerce to a BigDecimal.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @param number the big decimal to round
- * @return the rounded big decimal
+ * @param val the object to be coerced.
+ * @return a BigDecimal.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- protected BigDecimal roundBigDecimal(final BigDecimal number) {
- final int mscale = getMathScale();
- if (mscale >= 0) {
- return number.setScale(mscale, getMathContext().getRoundingMode());
- }
- return number;
+ public BigDecimal toBigDecimal(final Object val) {
+ return toBigDecimal(strict, val);
}
/**
- * The result of +,/,-,*,% when both operands are null.
+ * Coerce to a string.
+ * <p>Double.NaN coerce to the empty string.</p>
*
- * @return Integer(0) if lenient
- * @throws ArithmeticException if strict-cast
+ * @param val value to coerce.
+ * @return The String coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- protected Object controlNullNullOperands() {
- if (isStrictCast()) {
- throw new NullOperand();
- }
- return 0;
+ public String toString(final Object val) {
+ return toString(strict, val);
}
/**
- * Throw a NPE if arithmetic is strict-cast.
- * <p>This method is called by the cast methods ({@link
#toBoolean(Object)}, {@link #toInteger(Object)},
- * {@link #toDouble(Object)}, {@link #toString(Object)}, {@link
#toBigInteger(Object)}, {@link #toBigDecimal(Object)})
- * when they encounter a null argument.</p>
+ * Throws an NullOperand exception if arithmetic is strict-cast.
+ * <p>This method is called by the cast methods ({@link
#toBoolean(boolean, Object)},
+ * {@link #toInteger(boolean, Object)}, {@link #toDouble(boolean, Object)},
+ * {@link #toString(boolean, Object)}, {@link #toBigInteger(boolean,
Object)},
+ * {@link #toBigDecimal(boolean, Object)}) when they encounter a null
argument.</p>
*
- * @throws ArithmeticException if strict
+ * @param strictCast whether strict cast is required
+ * @throws JexlArithmetic.NullOperand if strict-cast
*/
- protected void controlNullOperand() {
- if (isStrictCast()) {
+ protected void controlNullOperand(boolean strictCast) {
+ if (strictCast) {
throw new NullOperand();
}
}
/**
- * The float regular expression pattern.
- * <p>
- * The decimal and exponent parts are optional and captured allowing to
determine if the number is a real
- * by checking whether one of these 2 capturing groups is not empty.
+ * The result of +,/,-,*,% when both operands are null.
+ * @param strictCast whether strict-cast is required
+ * @return Integer(0) if lenient
+ * @throws JexlArithmetic.NullOperand if strict-cast
*/
- public static final Pattern FLOAT_PATTERN =
Pattern.compile("^[+-]?\\d*(\\.\\d*)?([eE][+-]?\\d+)?$");
+ protected Object controlNullNullOperands(boolean strictCast) {
+ if (strictCast ) {
+ throw new NullOperand();
+ }
+ return 0;
+ }
/**
- * Test if the passed value is a floating point number, i.e. a float,
double
- * or string with ( "." | "E" | "e").
+ * Coerce to a primitive boolean.
+ * <p>Double.NaN, null, "false" and empty string coerce to false.</p>
*
- * @param val the object to be tested
- * @return true if it is, false otherwise.
+ * @param val value to coerce
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @return the boolean value if coercion is possible, true if value was
not null.
*/
- protected boolean isFloatingPointNumber(final Object val) {
- if (val instanceof Float || val instanceof Double) {
- return true;
+ protected boolean toBoolean(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return false;
}
- if (val instanceof CharSequence) {
- final Matcher m = FLOAT_PATTERN.matcher((CharSequence) val);
- // first 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);
+ if (val instanceof Boolean) {
+ return ((Boolean) val);
}
- return false;
+ if (val instanceof Number) {
+ final double number = toDouble(strict, val);
+ return !Double.isNaN(number) && number != 0.d;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get();
+ }
+ if (val instanceof String) {
+ final String strval = val.toString();
+ return !strval.isEmpty() && !"false".equals(strval);
+ }
+ // non-null value is true
+ return true;
}
/**
- * Is Object a floating point number.
+ * Coerce to a primitive int.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @param o Object to be analyzed.
- * @return true if it is a Float or a Double.
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val value to coerce
+ * @return the value coerced to int
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- protected boolean isFloatingPoint(final Object o) {
- return o instanceof Float || o instanceof Double;
+ protected int toInteger(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return 0;
+ }
+ if (val instanceof Double) {
+ final double dval = (Double) val;
+ return Double.isNaN(dval)? 0 : (int) dval;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).intValue();
+ }
+ if (val instanceof String) {
+ return parseInteger((String) val);
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1 : 0;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1 : 0;
+ }
+ if (val instanceof Character) {
+ return ((Character) val);
+ }
+ throw new ArithmeticException("Integer coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
}
/**
- * Is Object a whole number.
+ * Coerce to a primitive long.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @param o Object to be analyzed.
- * @return true if Integer, Long, Byte, Short or Character.
- */
- protected boolean isNumberable(final Object o) {
- return o instanceof Integer
- || o instanceof Long
- || o instanceof Byte
- || o instanceof Short
- || o instanceof Character;
- }
-
- /**
- * The last method called before returning a result from a script
execution.
- * @param returned the returned value
- * @return the controlled returned value
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val value to coerce
+ * @return the value coerced to long
+ * @throws ArithmeticException if value is null and mode is strict or if
coercion is not possible
*/
- public Object controlReturn(Object returned) {
- return returned;
+ protected long toLong(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return 0L;
+ }
+ if (val instanceof Double) {
+ final double dval = (Double) val;
+ return Double.isNaN(dval)? 0L : (long) dval;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).longValue();
+ }
+ if (val instanceof String) {
+ return parseLong((String) val);
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1L : 0L;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1L : 0L;
+ }
+ if (val instanceof Character) {
+ return ((Character) val);
+ }
+ throw new ArithmeticException("Long coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
}
/**
- * Given a Number, return the value using the smallest type the result
- * will fit into.
- * <p>This works hand in hand with parameter 'widening' in java
- * method calls, e.g. a call to substring(int,int) with an int and a long
- * will fail, but a call to substring(int,int) with an int and a short will
- * succeed.</p>
+ * Coerce to a BigInteger.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
*
- * @param original the original number.
- * @return a value of the smallest type the original number will fit into.
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val the object to be coerced.
+ * @return a BigDecimal
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
*/
- public Number narrow(final Number original) {
- return narrowNumber(original, null);
- }
-
- /**
- * Whether we consider the narrow class as a potential candidate for
narrowing the source.
+ protected BigInteger toBigInteger(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return BigInteger.ZERO;
+ }
+ if (val instanceof BigInteger) {
+ return (BigInteger) val;
+ }
+ if (val instanceof Double) {
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return BigInteger.ZERO;
+ }
+ return BigInteger.valueOf(dval.longValue());
+ }
+ if (val instanceof BigDecimal) {
+ return ((BigDecimal) val).toBigInteger();
+ }
+ if (val instanceof Number) {
+ return BigInteger.valueOf(((Number) val).longValue());
+ }
+ if (val instanceof Boolean) {
+ return BigInteger.valueOf(((Boolean) val) ? 1L : 0L);
+ }
+ if (val instanceof AtomicBoolean) {
+ return BigInteger.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
+ }
+ if (val instanceof String) {
+ return parseBigInteger((String) val);
+ }
+ if (val instanceof Character) {
+ final int i = ((Character) val);
+ return BigInteger.valueOf(i);
+ }
+ throw new ArithmeticException("BigInteger coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a BigDecimal.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
+ *
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val the object to be coerced.
+ * @return a BigDecimal.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
+ */
+ protected BigDecimal toBigDecimal(final boolean strict, final Object val) {
+ if (val instanceof BigDecimal) {
+ return roundBigDecimal((BigDecimal) val);
+ }
+ if (val == null) {
+ controlNullOperand(strict);
+ return BigDecimal.ZERO;
+ }
+ if (val instanceof Double) {
+ if (Double.isNaN(((Double) val))) {
+ return BigDecimal.ZERO;
+ }
+ return roundBigDecimal(new BigDecimal(val.toString(),
getMathContext()));
+ }
+ if (val instanceof Number) {
+ return roundBigDecimal(new BigDecimal(val.toString(),
getMathContext()));
+ }
+ if (val instanceof Boolean) {
+ return BigDecimal.valueOf(((Boolean) val) ? 1. : 0.);
+ }
+ if (val instanceof AtomicBoolean) {
+ return BigDecimal.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
+ }
+ if (val instanceof String) {
+ final String string = (String) val;
+ if ("".equals(string)) {
+ return BigDecimal.ZERO;
+ }
+ return roundBigDecimal(new BigDecimal(string, getMathContext()));
+ }
+ if (val instanceof Character) {
+ final int i = ((Character) val);
+ return new BigDecimal(i);
+ }
+ throw new ArithmeticException("BigDecimal coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a primitive double.
+ * <p>Double.NaN, null and empty string coerce to zero.</p>
+ * <p>Boolean false is 0, true is 1.</p>
+ *
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val value to coerce.
+ * @return The double coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
+ */
+ protected double toDouble(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return 0;
+ }
+ if (val instanceof Double) {
+ return ((Double) val);
+ }
+ if (val instanceof Number) {
+ //The below construct is used rather than
((Number)val).doubleValue() to ensure
+ //equality between comparing new Double( 6.4 / 3 ) and the jexl
expression of 6.4 / 3
+ return Double.parseDouble(String.valueOf(val));
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1. : 0.;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1. : 0.;
+ }
+ if (val instanceof String) {
+ return parseDouble((String) val);
+ }
+ if (val instanceof Character) {
+ return ((Character) val);
+ }
+ throw new ArithmeticException("Double coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a string.
+ * <p>Double.NaN coerce to the empty string.</p>
+ *
+ * @param strict true if the calling operator or casting is strict, false
otherwise
+ * @param val value to coerce.
+ * @return The String coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
+ */
+ protected String toString(final boolean strict, final Object val) {
+ if (val == null) {
+ controlNullOperand(strict);
+ return "";
+ }
+ if (!(val instanceof Double)) {
+ return val.toString();
+ }
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return "";
+ }
+ return dval.toString();
+ }
+ /**
+ * Checks whether this JexlArithmetic instance
+ * strictly considers null as an error when used as operand unexpectedly.
+ *
+ * @return true if strict, false if lenient
+ */
+ public boolean isStrict() {
+ return strict;
+ }
+
+ /**
+ * Checks whether this arithmetic considers a given operator as strict or
null-safe.
+ * <p>When an operator is strict, it does <em>not</em> accept null
arguments when the arithmetic is strict.
+ * If null-safe (ie not-strict), the operator does accept null arguments
even if the arithmetic itself is strict.</p>
+ * <p>The default implementation considers equal/not-equal operators as
null-safe so one can check for null as in
+ * <code>if (myvar == null) {...}</code>. Note that this operator is used
for equal and not-equal syntax. The complete
+ * list of operators that are not strict are (==, [], []=, ., .=, empty,
size, contains). </p>
+ * <p>An arithmetic refining its strict behavior handling for more
operators must declare which by overriding
+ * this method.</p>
+ * @param operator the operator to check for null-argument(s) handling
+ * @return true if operator considers null arguments as errors, false if
operator has appropriate semantics
+ * for null argument(s)
+ */
+ public boolean isStrict(JexlOperator operator) {
+ if (operator != null) {
+ switch (operator) {
+ case EQ:
+ case ARRAY_GET:
+ case ARRAY_SET:
+ case PROPERTY_GET:
+ case PROPERTY_SET:
+ case EMPTY:
+ case SIZE:
+ case CONTAINS:
+ return false;
+ }
+ }
+ return isStrict();
+ }
+
+ /**
+ * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ *
+ * @return the math context
+ */
+ public MathContext getMathContext() {
+ return mathContext;
+ }
+
+ /**
+ * The BigDecimal scale used for comparison and coericion operations.
+ *
+ * @return the scale
+ */
+ public int getMathScale() {
+ return mathScale;
+ }
+
+ /**
+ * Ensure a big decimal is rounded by this arithmetic scale and rounding
mode.
+ *
+ * @param number the big decimal to round
+ * @return the rounded big decimal
+ */
+ protected BigDecimal roundBigDecimal(final BigDecimal number) {
+ final int mscale = getMathScale();
+ if (mscale >= 0) {
+ return number.setScale(mscale, getMathContext().getRoundingMode());
+ }
+ return number;
+ }
+
+ /**
+ * The result of +,/,-,*,% when both operands are null.
+ *
+ * @return Integer(0) if lenient
+ * @throws JexlArithmetic.NullOperand if strict
+ * @deprecated 3.3
+ */
+ @Deprecated
+ protected Object controlNullNullOperands() {
+ if (isStrict()) {
+ throw new NullOperand();
+ }
+ return 0;
+ }
+
+ /**
+ * Throws an NullOperand exception if arithmetic is strict-cast.
+ *
+ * @throws JexlArithmetic.NullOperand if strict
+ * @deprecated 3.3
+ */
+ @Deprecated
+ protected void controlNullOperand() {
+ if (isStrict()) {
+ throw new NullOperand();
+ }
+ }
+
+ /**
+ * The float regular expression pattern.
+ * <p>
+ * The decimal and exponent parts are optional and captured allowing to
determine if the number is a real
+ * by checking whether one of these 2 capturing groups is not empty.
+ */
+ public static final Pattern FLOAT_PATTERN =
Pattern.compile("^[+-]?\\d*(\\.\\d*)?([eE][+-]?\\d+)?$");
+
+ /**
+ * Test if the passed value is a floating point number, i.e. a float,
double
+ * or string with ( "." | "E" | "e").
+ *
+ * @param val the object to be tested
+ * @return true if it is, false otherwise.
+ */
+ protected boolean isFloatingPointNumber(final Object val) {
+ if (val instanceof Float || val instanceof Double) {
+ return true;
+ }
+ if (val instanceof CharSequence) {
+ final Matcher m = FLOAT_PATTERN.matcher((CharSequence) val);
+ // first 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);
+ }
+ return false;
+ }
+
+ /**
+ * Is Object a floating point number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if it is a Float or a Double.
+ */
+ protected boolean isFloatingPoint(final Object o) {
+ return o instanceof Float || o instanceof Double;
+ }
+
+ /**
+ * Is Object a whole number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if Integer, Long, Byte, Short or Character.
+ */
+ protected boolean isNumberable(final Object o) {
+ return o instanceof Integer
+ || o instanceof Long
+ || o instanceof Byte
+ || o instanceof Short
+ || o instanceof Character;
+ }
+
+ /**
+ * The last method called before returning a result from a script
execution.
+ * @param returned the returned value
+ * @return the controlled returned value
+ */
+ public Object controlReturn(Object returned) {
+ return returned;
+ }
+
+ /**
+ * Given a Number, return the value using the smallest type the result
+ * will fit into.
+ * <p>This works hand in hand with parameter 'widening' in java
+ * method calls, e.g. a call to substring(int,int) with an int and a long
+ * will fail, but a call to substring(int,int) with an int and a short will
+ * succeed.</p>
+ *
+ * @param original the original number.
+ * @return a value of the smallest type the original number will fit into.
+ */
+ public Number narrow(final Number original) {
+ return narrowNumber(original, null);
+ }
+
+ /**
+ * Whether we consider the narrow class as a potential candidate for
narrowing the source.
*
* @param narrow the target narrow class
* @param source the original source class
@@ -854,22 +1206,23 @@ public class JexlArithmetic {
}
return narrowLong(left, right, result);
}
+ final boolean strictCast = isStrict(JexlOperator.ADD);
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal)
{
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
final BigDecimal result = l.add(r, getMathContext());
return narrowBigDecimal(left, right, result);
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) ||
isFloatingPointNumber(right)) {
- final double l = toDouble(left);
- final double r = toDouble(right);
+ final double l = toDouble(strictCast, left);
+ final double r = toDouble(strictCast, right);
return l + r;
}
// otherwise treat as (big) integers
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
final BigInteger result = l.add(r);
return narrowBigInteger(left, right, result);
} catch (final ArithmeticException nfe) {
@@ -903,10 +1256,11 @@ public class JexlArithmetic {
final long result = x / y;
return narrowLong(left, right, result);
}
+ final boolean strictCast = isStrict(JexlOperator.DIVIDE);
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
@@ -915,16 +1269,16 @@ public class JexlArithmetic {
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
- final double l = toDouble(left);
- final double r = toDouble(right);
+ final double l = toDouble(strictCast, left);
+ final double r = toDouble(strictCast, right);
if (r == 0.0) {
throw new ArithmeticException("/");
}
return l / r;
}
// otherwise treat as integers
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
@@ -956,10 +1310,11 @@ public class JexlArithmetic {
final long result = x % y;
return narrowLong(left, right, result);
}
+ final boolean strictCast = isStrict(JexlOperator.MOD);
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
@@ -968,16 +1323,16 @@ public class JexlArithmetic {
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
- final double l = toDouble(left);
- final double r = toDouble(right);
+ final double l = toDouble(strictCast, left);
+ final double r = toDouble(strictCast, right);
if (r == 0.0) {
throw new ArithmeticException("%");
}
return l % r;
}
// otherwise treat as integers
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
@@ -1026,22 +1381,23 @@ public class JexlArithmetic {
}
return narrowLong(left, right, result);
}
+ final boolean strictCast = isStrict(JexlOperator.MULTIPLY);
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
final BigDecimal result = l.multiply(r, getMathContext());
return narrowBigDecimal(left, right, result);
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
- final double l = toDouble(left);
- final double r = toDouble(right);
+ final double l = toDouble(strictCast, left);
+ final double r = toDouble(strictCast, right);
return l * r;
}
// otherwise treat as integers
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
final BigInteger result = l.multiply(r);
return narrowBigInteger(left, right, result);
}
@@ -1070,22 +1426,23 @@ public class JexlArithmetic {
}
return narrowLong(left, right, result);
}
+ final boolean strictCast = isStrict(JexlOperator.SUBTRACT);
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
final BigDecimal result = l.subtract(r, getMathContext());
return narrowBigDecimal(left, right, result);
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
- final double l = toDouble(left);
- final double r = toDouble(right);
+ final double l = toDouble(strictCast, left);
+ final double r = toDouble(strictCast, right);
return l - r;
}
// otherwise treat as integers
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
final BigInteger result = l.subtract(r);
return narrowBigInteger(left, right, result);
}
@@ -1189,6 +1546,17 @@ public class JexlArithmetic {
return true;
}
+ /**
+ * Test if a condition is true or false.
+ * @param object the object to use as condition
+ * @return true or false
+ * @since 3.3
+ */
+ public boolean testPredicate(final Object object) {
+ final boolean strictCast = isStrict(JexlOperator.CONDITION);
+ return toBoolean(strictCast, object);
+ }
+
/**
* Test if left contains right (right matches/in left).
* <p>Beware that this method arguments are the opposite of the operator
arguments.
@@ -1406,7 +1774,8 @@ public class JexlArithmetic {
* @return ~val
*/
public Object complement(final Object val) {
- final long l = toLong(val);
+ final boolean strictCast = isStrict(JexlOperator.COMPLEMENT);
+ final long l = toLong(strictCast, val);
return ~l;
}
@@ -1417,7 +1786,8 @@ public class JexlArithmetic {
* @return !val
*/
public Object not(final Object val) {
- return !toBoolean(val);
+ final boolean strictCast = isStrict(JexlOperator.NOT);
+ return !toBoolean(strictCast, val);
}
/**
@@ -1459,6 +1829,21 @@ public class JexlArithmetic {
return l >>> r;
}
+ /**
+ * @deprecated 3.3
+ */
+ @Deprecated
+ protected int compare(final Object left, final Object right, final String
symbol) {
+ JexlOperator operator;
+ try {
+ operator = JexlOperator.valueOf(symbol);
+ } catch(IllegalArgumentException xill) {
+ // ignore
+ operator = JexlOperator.EQ;
+ }
+ return compare(left, right, operator);
+ }
+
/**
* Performs a comparison.
*
@@ -1468,25 +1853,26 @@ public class JexlArithmetic {
* @return -1 if left < right; +1 if left > right; 0 if left == right
* @throws ArithmeticException if either left or right is null
*/
- protected int compare(final Object left, final Object right, final String
operator) {
+ protected int compare(final Object left, final Object right, final
JexlOperator operator) {
+ final boolean strictCast = isStrict(operator);
if (left != null && right != null) {
if (left instanceof BigDecimal || right instanceof BigDecimal) {
- final BigDecimal l = toBigDecimal(left);
- final BigDecimal r = toBigDecimal(right);
+ final BigDecimal l = toBigDecimal(strictCast, left);
+ final BigDecimal r = toBigDecimal(strictCast, right);
return l.compareTo(r);
}
if (left instanceof BigInteger || right instanceof BigInteger) {
try {
- final BigInteger l = toBigInteger(left);
- final BigInteger r = toBigInteger(right);
+ final BigInteger l = toBigInteger(strictCast, left);
+ final BigInteger r = toBigInteger(strictCast, right);
return l.compareTo(r);
} catch(ArithmeticException xconvert) {
// ignore it, continue in sequence
}
}
if (isFloatingPoint(left) || isFloatingPoint(right)) {
- final double lhs = toDouble(left);
- final double rhs = toDouble(right);
+ final double lhs = toDouble(strictCast, left);
+ final double rhs = toDouble(strictCast, right);
if (Double.isNaN(lhs)) {
if (Double.isNaN(rhs)) {
return 0;
@@ -1501,8 +1887,8 @@ public class JexlArithmetic {
}
if (isNumberable(left) || isNumberable(right)) {
try {
- final long lhs = toLong(left);
- final long rhs = toLong(right);
+ final long lhs = toLong(strictCast, left);
+ final long rhs = toLong(strictCast, right);
return Long.compare(lhs, rhs);
} catch(ArithmeticException xconvert) {
// ignore it, continue in sequence
@@ -1511,7 +1897,7 @@ public class JexlArithmetic {
if (left instanceof String || right instanceof String) {
return toString(left).compareTo(toString(right));
}
- if ("==".equals(operator)) {
+ if (JexlOperator.EQ == operator) {
return left.equals(right) ? 0 : -1;
}
if (left instanceof Comparable<?>) {
@@ -1537,10 +1923,11 @@ public class JexlArithmetic {
if (left == null || right == null) {
return false;
}
+ final boolean strictCast = isStrict(JexlOperator.EQ);
if (left instanceof Boolean || right instanceof Boolean) {
- return toBoolean(left) == toBoolean(right);
+ return toBoolean(left) == toBoolean(strictCast, right);
}
- return compare(left, right, "==") == 0;
+ return compare(left, right, JexlOperator.EQ) == 0;
}
/**
@@ -1554,7 +1941,7 @@ public class JexlArithmetic {
if ((left == right) || (left == null) || (right == null)) {
return false;
}
- return compare(left, right, "<") < 0;
+ return compare(left, right, JexlOperator.LT) < 0;
}
@@ -1569,7 +1956,7 @@ public class JexlArithmetic {
if ((left == right) || left == null || right == null) {
return false;
}
- return compare(left, right, ">") > 0;
+ return compare(left, right, JexlOperator.GT) > 0;
}
/**
@@ -1586,7 +1973,7 @@ public class JexlArithmetic {
if (left == null || right == null) {
return false;
}
- return compare(left, right, "<=") <= 0;
+ return compare(left, right, JexlOperator.LTE) <= 0;
}
/**
@@ -1603,115 +1990,9 @@ public class JexlArithmetic {
if (left == null || right == null) {
return false;
}
- return compare(left, right, ">=") >= 0;
- }
-
- /**
- * Coerce to a primitive boolean.
- * <p>Double.NaN, null, "false" and empty string coerce to false.</p>
- *
- * @param val value to coerce
- * @return the boolean value if coercion is possible, true if value was
not null.
- */
- public boolean toBoolean(final Object val) {
- if (val == null) {
- controlNullOperand();
- return false;
- }
- if (val instanceof Boolean) {
- return ((Boolean) val);
- }
- if (val instanceof Number) {
- final double number = toDouble(val);
- return !Double.isNaN(number) && number != 0.d;
- }
- if (val instanceof AtomicBoolean) {
- return ((AtomicBoolean) val).get();
- }
- if (val instanceof String) {
- final String strval = val.toString();
- return !strval.isEmpty() && !"false".equals(strval);
- }
- // non-null value is true
- return true;
- }
-
- /**
- * Coerce to a primitive int.
- * <p>Double.NaN, null and empty string coerce to zero.</p>
- * <p>Boolean false is 0, true is 1.</p>
- *
- * @param val value to coerce
- * @return the value coerced to int
- * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
- */
- public int toInteger(final Object val) {
- if (val == null) {
- controlNullOperand();
- return 0;
- }
- if (val instanceof Double) {
- final double dval = (Double) val;
- return Double.isNaN(dval)? 0 : (int) dval;
- }
- if (val instanceof Number) {
- return ((Number) val).intValue();
- }
- if (val instanceof String) {
- return parseInteger((String) val);
- }
- if (val instanceof Boolean) {
- return ((Boolean) val) ? 1 : 0;
- }
- if (val instanceof AtomicBoolean) {
- return ((AtomicBoolean) val).get() ? 1 : 0;
- }
- if (val instanceof Character) {
- return ((Character) val);
- }
-
- throw new ArithmeticException("Integer coercion: "
- + val.getClass().getName() + ":(" + val + ")");
- }
-
- /**
- * Coerce to a primitive long.
- * <p>Double.NaN, null and empty string coerce to zero.</p>
- * <p>Boolean false is 0, true is 1.</p>
- *
- * @param val value to coerce
- * @return the value coerced to long
- * @throws ArithmeticException if value is null and mode is strict or if
coercion is not possible
- */
- public long toLong(final Object val) {
- if (val == null) {
- controlNullOperand();
- return 0L;
- }
- if (val instanceof Double) {
- final double dval = (Double) val;
- return Double.isNaN(dval)? 0L : (long) dval;
- }
- if (val instanceof Number) {
- return ((Number) val).longValue();
- }
- if (val instanceof String) {
- return parseLong((String) val);
- }
- if (val instanceof Boolean) {
- return ((Boolean) val) ? 1L : 0L;
- }
- if (val instanceof AtomicBoolean) {
- return ((AtomicBoolean) val).get() ? 1L : 0L;
- }
- if (val instanceof Character) {
- return ((Character) val);
- }
- throw new ArithmeticException("Long coercion: "
- + val.getClass().getName() + ":(" + val + ")");
+ return compare(left, right, JexlOperator.GTE) >= 0;
}
-
/**
* Convert a string to a double.
* <>Empty string is considered as NaN.</>
@@ -1781,161 +2062,6 @@ public class JexlArithmetic {
return BigInteger.valueOf(parseLong(arg));
}
- /**
- * Coerce to a BigInteger.
- * <p>Double.NaN, null and empty string coerce to zero.</p>
- * <p>Boolean false is 0, true is 1.</p>
- *
- * @param val the object to be coerced.
- * @return a BigDecimal
- * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
- */
- public BigInteger toBigInteger(final Object val) {
- if (val == null) {
- controlNullOperand();
- return BigInteger.ZERO;
- }
- if (val instanceof BigInteger) {
- return (BigInteger) val;
- }
- if (val instanceof Double) {
- final Double dval = (Double) val;
- if (Double.isNaN(dval)) {
- return BigInteger.ZERO;
- }
- return BigInteger.valueOf(dval.longValue());
- }
- if (val instanceof BigDecimal) {
- return ((BigDecimal) val).toBigInteger();
- }
- if (val instanceof Number) {
- return BigInteger.valueOf(((Number) val).longValue());
- }
- if (val instanceof Boolean) {
- return BigInteger.valueOf(((Boolean) val) ? 1L : 0L);
- }
- if (val instanceof AtomicBoolean) {
- return BigInteger.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
- }
- if (val instanceof String) {
- return parseBigInteger((String) val);
- }
- if (val instanceof Character) {
- final int i = ((Character) val);
- return BigInteger.valueOf(i);
- }
- throw new ArithmeticException("BigInteger coercion: "
- + val.getClass().getName() + ":(" + val + ")");
- }
-
- /**
- * Coerce to a BigDecimal.
- * <p>Double.NaN, null and empty string coerce to zero.</p>
- * <p>Boolean false is 0, true is 1.</p>
- *
- * @param val the object to be coerced.
- * @return a BigDecimal.
- * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
- */
- public BigDecimal toBigDecimal(final Object val) {
- if (val instanceof BigDecimal) {
- return roundBigDecimal((BigDecimal) val);
- }
- if (val == null) {
- controlNullOperand();
- return BigDecimal.ZERO;
- }
- if (val instanceof Double) {
- if (Double.isNaN(((Double) val))) {
- return BigDecimal.ZERO;
- }
- return roundBigDecimal(new BigDecimal(val.toString(),
getMathContext()));
- }
- if (val instanceof Number) {
- return roundBigDecimal(new BigDecimal(val.toString(),
getMathContext()));
- }
- if (val instanceof Boolean) {
- return BigDecimal.valueOf(((Boolean) val) ? 1. : 0.);
- }
- if (val instanceof AtomicBoolean) {
- return BigDecimal.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
- }
- if (val instanceof String) {
- final String string = (String) val;
- if ("".equals(string)) {
- return BigDecimal.ZERO;
- }
- return roundBigDecimal(new BigDecimal(string, getMathContext()));
- }
- if (val instanceof Character) {
- final int i = ((Character) val);
- return new BigDecimal(i);
- }
- throw new ArithmeticException("BigDecimal coercion: "
- + val.getClass().getName() + ":(" + val + ")");
- }
-
- /**
- * Coerce to a primitive double.
- * <p>Double.NaN, null and empty string coerce to zero.</p>
- * <p>Boolean false is 0, true is 1.</p>
- *
- * @param val value to coerce.
- * @return The double coerced value.
- * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
- */
- public double toDouble(final Object val) {
- if (val == null) {
- controlNullOperand();
- return 0;
- }
- if (val instanceof Double) {
- return ((Double) val);
- }
- if (val instanceof Number) {
- //The below construct is used rather than
((Number)val).doubleValue() to ensure
- //equality between comparing new Double( 6.4 / 3 ) and the jexl
expression of 6.4 / 3
- return Double.parseDouble(String.valueOf(val));
- }
- if (val instanceof Boolean) {
- return ((Boolean) val) ? 1. : 0.;
- }
- if (val instanceof AtomicBoolean) {
- return ((AtomicBoolean) val).get() ? 1. : 0.;
- }
- if (val instanceof String) {
- return parseDouble((String) val);
- }
- if (val instanceof Character) {
- return ((Character) val);
- }
- throw new ArithmeticException("Double coercion: "
- + val.getClass().getName() + ":(" + val + ")");
- }
-
- /**
- * Coerce to a string.
- * <p>Double.NaN coerce to the empty string.</p>
- *
- * @param val value to coerce.
- * @return The String coerced value.
- * @throws ArithmeticException if val is null and mode is strict or if
coercion is not possible
- */
- public String toString(final Object val) {
- if (val == null) {
- controlNullOperand();
- return "";
- }
- if (!(val instanceof Double)) {
- return val.toString();
- }
- final Double dval = (Double) val;
- if (Double.isNaN(dval)) {
- return "";
- }
- return dval.toString();
- }
-
/**
* Use or overload and() instead.
* @param lhs left hand side
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index d3433e1b..29cad932 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -538,7 +538,7 @@ public class Interpreter extends InterpreterBase {
private boolean testPredicate(JexlNode node, Object condition) {
final Object predicate = operators.tryOverload(node,
JexlOperator.CONDITION, condition);
- return arithmetic.toBoolean(predicate != JexlEngine.TRY_FAILED?
predicate : condition);
+ return arithmetic.testPredicate(predicate != JexlEngine.TRY_FAILED?
predicate : condition);
}
@Override
diff --git a/src/test/java/org/apache/commons/jexl3/Arithmetic360.java
b/src/test/java/org/apache/commons/jexl3/Arithmetic360.java
index aacde4b1..f6ef69a9 100644
--- a/src/test/java/org/apache/commons/jexl3/Arithmetic360.java
+++ b/src/test/java/org/apache/commons/jexl3/Arithmetic360.java
@@ -157,7 +157,8 @@ public class Arithmetic360 extends JexlArithmetic {
*/
public Object shiftLeft(Object left, Object right) {
if (left == null && right == null) {
- return controlNullNullOperands();
+ boolean strictCast = isStrict(JexlOperator.SHIFTLEFT);
+ return controlNullNullOperands(strictCast);
}
final int r = toInteger(right);
Number l = asIntNumber(left);
@@ -180,7 +181,8 @@ public class Arithmetic360 extends JexlArithmetic {
*/
public Object shiftRight(Object left, Object right) {
if (left == null && right == null) {
- return controlNullNullOperands();
+ boolean strictCast = isStrict(JexlOperator.SHIFTRIGHT);
+ return controlNullNullOperands(strictCast);
}
final int r = toInteger(right);
Number l = asIntNumber(left);
@@ -203,7 +205,8 @@ public class Arithmetic360 extends JexlArithmetic {
*/
public Object shiftRightUnsigned(Object left, Object right) {
if (left == null && right == null) {
- return controlNullNullOperands();
+ boolean strictCast = isStrict(JexlOperator.SHIFTRIGHTU);
+ return controlNullNullOperands(strictCast);
}
final int r = toInteger(right);
Number l = asIntNumber(left);