JexlArithmetic.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jexl3;
import static java.lang.StrictMath.floor;
import static org.apache.commons.jexl3.JexlOperator.EQ;
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;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.jexl3.introspection.JexlMethod;
/**
* Perform arithmetic, implements JexlOperator methods.
*
* <p>This is the class to derive to implement new operator behaviors.</p>
*
* <p>The 5 base arithmetic operators (+, - , *, /, %) follow the same evaluation rules regarding their arguments.</p>
* <ol>
* <li>If both are null, result is 0 if arithmetic (or operator) is non-strict, ArithmeticException is thrown
* otherwise</li>
* <li>If both arguments are numberable - any kind of integer including boolean -, coerce both to Long and coerce
* result to the most precise argument class (boolean < byte < short < int < long);
* if long operation would cause overflow, return a BigInteger</li>
* <li>If either argument is a BigDecimal, coerce both to BigDecimal, operator returns BigDecimal</li>
* <li>If either argument is a floating point number, coerce both to Double, operator returns Double</li>
* <li>Else treat as BigInteger, perform operation and narrow result to the most precise argument class
* </li>
* </ol>
*
* Note that the only exception thrown by JexlArithmetic is and must be ArithmeticException.
*
* @see JexlOperator
* @since 2.0
*/
public class JexlArithmetic {
/**
* Helper interface used when creating an array literal.
*
* <p>The default implementation creates an array and attempts to type it strictly.</p>
*
* <ul>
* <li>If all objects are of the same type, the array returned will be an array of that same type</li>
* <li>If all objects are Numbers, the array returned will be an array of Numbers</li>
* <li>If all objects are convertible to a primitive type, the array returned will be an array
* of the primitive type</li>
* </ul>
*/
public interface ArrayBuilder {
/**
* Adds a literal to the array.
*
* @param value the item to add
*/
void add(Object value);
/**
* Creates the actual "array" instance.
*
* @param extended true when the last argument is ', ...'
* @return the array
*/
Object create(boolean extended);
}
/** Marker class for coercion operand exceptions. */
public static class CoercionException extends ArithmeticException {
private static final long serialVersionUID = 202402081150L;
/**
* Simple ctor.
* @param msg the exception message
*/
public CoercionException(final String msg) {
super(msg);
}
}
/**
* Helper interface used when creating a map literal.
* <p>The default implementation creates a java.util.HashMap.</p>
*/
public interface MapBuilder {
/**
* Creates the actual "map" instance.
*
* @return the map
*/
Object create();
/**
* Adds a new entry to the map.
*
* @param key the map entry key
* @param value the map entry value
*/
void put(Object key, Object value);
}
/** Marker class for null operand exceptions. */
public static class NullOperand extends ArithmeticException {
private static final long serialVersionUID = 4720876194840764770L;
}
/**
* Helper interface used when creating a set literal.
* <p>The default implementation creates a java.util.HashSet.</p>
*/
public interface SetBuilder {
/**
* Adds a literal to the set.
*
* @param value the item to add
*/
void add(Object value);
/**
* Creates the actual "set" instance.
*
* @return the set
*/
Object create();
}
/**
* The interface that uberspects JexlArithmetic classes.
* <p>This allows overloaded operator methods discovery.</p>
*/
public interface Uberspect {
/**
* Gets the most specific method for an operator.
*
* @param operator the operator
* @param arg the arguments
* @return the most specific method or null if no specific override could be found
*/
JexlMethod getOperator(JexlOperator operator, Object... arg);
/**
* Checks whether this uberspect has overloads for a given operator.
*
* @param operator the operator to check
* @return true if an overload exists, false otherwise
*/
boolean overloads(JexlOperator operator);
}
/** Double.MAX_VALUE as BigDecimal. */
protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
/** -Double.MAX_VALUE as BigDecimal. */
protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(-Double.MAX_VALUE);
/** Long.MAX_VALUE as BigInteger. */
protected static final BigInteger BIGI_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
/** Long.MIN_VALUE as BigInteger. */
protected static final BigInteger BIGI_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
/** Default BigDecimal scale. */
protected static final int BIGD_SCALE = -1;
/**
* 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+)?$");
/**
* Attempts transformation of potential array in an abstract list or leave as is.
* <p>An array (as in int[]) is not convenient to call methods so when encountered we turn them into lists</p>
* @param container an array or on object
* @return an abstract list wrapping the array instance or the initial argument
* @see org.apache.commons.jexl3.internal.introspection.ArrayListWrapper
*/
private static Object arrayWrap(final Object container) {
return container.getClass().isArray()
? new org.apache.commons.jexl3.internal.introspection.ArrayListWrapper(container)
: container;
}
private static boolean computeCompare321(final JexlArithmetic arithmetic) {
Class<?> arithmeticClass = arithmetic.getClass();
while(arithmeticClass != JexlArithmetic.class) {
try {
final Method cmp = arithmeticClass.getDeclaredMethod("compare", Object.class, Object.class, String.class);
if (cmp.getDeclaringClass() != JexlArithmetic.class) {
return true;
}
} catch (final NoSuchMethodException xany) {
arithmeticClass = arithmeticClass.getSuperclass();
}
}
return false;
}
/**
* Checks if the product of the arguments overflows a {@code long}.
* <p>see java8 Math.multiplyExact
* @param x the first value
* @param y the second value
* @param r the product
* @return true if product fits a long, false if it overflows
*/
@SuppressWarnings("MagicNumber")
protected static boolean isMultiplyExact(final long x, final long y, final long r) {
final long ax = Math.abs(x);
final long ay = Math.abs(y);
// Some bits greater than 2^31 that might cause overflow
// Check the result using the divide operator
// and check for the special case of Long.MIN_VALUE * -1
return !((ax | ay) >>> Integer.SIZE - 1 != 0
&& (y != 0 && r / y != x
|| x == Long.MIN_VALUE && y == -1));
}
/** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
private final boolean strict;
/** The big decimal math context. */
private final MathContext mathContext;
/** The big decimal scale. */
private final int mathScale;
/** The dynamic constructor. */
private final Constructor<? extends JexlArithmetic> ctor;
/**
* Determines if the compare method(Object, Object, String) is overriden in this class or one of its
* superclasses.
*/
private final boolean compare321 = computeCompare321(this);
/**
* Creates a JexlArithmetic.
* <p>If you derive your own arithmetic, implement the
* other constructor that may be needed when dealing with options.
*
* @param astrict whether this arithmetic is strict or lenient
*/
public JexlArithmetic(final boolean astrict) {
this(astrict, null, Integer.MIN_VALUE);
}
/**
* Creates a JexlArithmetic.
* <p>The constructor to define in derived classes.
*
* @param astrict whether this arithmetic is lenient or strict
* @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
* @param bigdScale the scale used for big decimals.
*/
public JexlArithmetic(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
this.strict = astrict;
this.mathContext = bigdContext == null ? MathContext.DECIMAL128 : bigdContext;
this.mathScale = bigdScale == Integer.MIN_VALUE ? BIGD_SCALE : bigdScale;
Constructor<? extends JexlArithmetic> actor = null;
try {
actor = getClass().getConstructor(boolean.class, MathContext.class, int.class);
} catch (final Exception xany) {
// ignore
}
this.ctor = actor;
}
/**
* Add two values together.
* <p>
* If any numeric add fails on coercion to the appropriate type,
* treat as Strings and do concatenation.
* </p>
*
* @param left left argument
* @param right right argument
* @return left + right.
*/
public Object add(final Object left, final Object right) {
if (left == null && right == null) {
return controlNullNullOperands(JexlOperator.ADD);
}
final boolean strconcat = strict
? left instanceof String || right instanceof String
: left instanceof String && right instanceof String;
if (!strconcat) {
try {
final boolean strictCast = isStrict(JexlOperator.ADD);
// if both (non-null) args fit as long
final Number ln = asLongNumber(strictCast, left);
final Number rn = asLongNumber(strictCast, right);
if (ln != null && rn != null) {
final long x = ln.longValue();
final long y = rn.longValue();
final long result = x + y;
// detect overflow, see java8 Math.addExact
if (((x ^ result) & (y ^ result)) < 0) {
return BigInteger.valueOf(x).add(BigInteger.valueOf(y));
}
return narrowLong(left, right, result);
}
// if either are BigDecimal, use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
return l.add(r, getMathContext());
}
// if either are floating point (double or float), use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
final double l = toDouble(strictCast, left);
final double r = toDouble(strictCast, right);
return l + r;
}
// otherwise treat as BigInteger
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) {
// ignore and continue in sequence
}
}
return (left == null ? "" : toString(left)).concat(right == null ? "" : toString(right));
}
/**
* Performs a bitwise and.
*
* @param left the left operand
* @param right the right operator
* @return left & right
*/
public Object and(final Object left, final Object right) {
final long l = toLong(left);
final long r = toLong(right);
return l & r;
}
/**
* Creates an array builder.
* @param size the number of elements in the array
* @return an array builder instance
* @deprecated since 3.3.1
*/
@Deprecated
public ArrayBuilder arrayBuilder(final int size) {
return arrayBuilder(size, false);
}
/**
* Called by the interpreter when evaluating a literal array.
*
* @param size the number of elements in the array
* @param extended whether the map is extended or not
* @return the array builder
*/
public ArrayBuilder arrayBuilder(final int size, final boolean extended) {
return new org.apache.commons.jexl3.internal.ArrayBuilder(size, extended);
}
/**
* Checks if value class is a number that can be represented exactly in a long.
* <p>For convenience, booleans are converted as 1/0 (true/false).</p>
*
* @param strict whether null argument is converted as 0 or remains null
* @param value argument
* @return a non-null value if argument can be represented by a long
*/
protected Number asLongNumber(final boolean strict, final Object value) {
if (value instanceof Long
|| value instanceof Integer
|| value instanceof Short
|| value instanceof Byte) {
return (Number) value;
}
if (value instanceof Boolean) {
final Boolean b = (Boolean) value;
return b ? 1L : 0L;
}
if (value instanceof AtomicBoolean) {
final AtomicBoolean b = (AtomicBoolean) value;
return b.get() ? 1L : 0L;
}
if (value == null && !strict) {
return 0L;
}
return null;
}
/**
* Checks if value class is a number that can be represented exactly in a long.
* <p>For convenience, booleans are converted as 1/0 (true/false).</p>
*
* @param value argument
* @return a non-null value if argument can be represented by a long
*/
protected Number asLongNumber(final Object value) {
return asLongNumber(strict, value);
}
/**
* Use or overload and() instead.
* @param lhs left hand side
* @param rhs right hand side
* @return lhs & rhs
* @see JexlArithmetic#and
* @deprecated 3.0
*/
@Deprecated
public final Object bitwiseAnd(final Object lhs, final Object rhs) {
return and(lhs, rhs);
}
/**
* Use or overload or() instead.
*
* @param lhs left hand side
* @param rhs right hand side
* @return lhs | rhs
* @see JexlArithmetic#or
* @deprecated 3.0
*/
@Deprecated
public final Object bitwiseOr(final Object lhs, final Object rhs) {
return or(lhs, rhs);
}
/**
* Use or overload xor() instead.
*
* @param lhs left hand side
* @param rhs right hand side
* @return lhs ^ rhs
* @see JexlArithmetic#xor
* @deprecated 3.0
*/
@Deprecated
public final Object bitwiseXor(final Object lhs, final Object rhs) {
return xor(lhs, rhs);
}
/**
* Checks whether a potential collection contains another.
* <p>Made protected to make it easier to override if needed.</p>
* @param collection the container which can be a collection or an array (even of primitive)
* @param value the value which can be a collection or an array (even of primitive) or a singleton
* @return test result or null if there is no arithmetic solution
*/
protected Boolean collectionContains(final Object collection, final Object value) {
// convert arrays if needed
final Object left = arrayWrap(collection);
if (left instanceof Collection<?>) {
final Object right = arrayWrap(value);
if (right instanceof Collection<?>) {
return ((Collection<?>) left).containsAll((Collection<?>) right);
}
return ((Collection<?>) left).contains(value);
}
return null;
}
/**
* Performs a comparison.
*
* @param left the left operand
* @param right the right operator
* @param operator the operator
* @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 JexlOperator operator) {
// this is a temporary way of allowing pre-3.3 code that overrode compare() to still call
// the user method. This method will merge with doCompare in 3.4 and the compare321 flag will disappear.
return compare321
? compare(left, right, operator.toString())
: doCompare(left, right, operator);
}
/**
* Any override of this method (pre 3.3) should be modified to match the new signature.
* @param left left operand
* @param right right operand
* @param symbol the operator symbol
* @return -1 if left < right; +1 if left > right; 0 if left == right
* {@link JexlArithmetic#compare(Object, Object, JexlOperator)}
* @deprecated 3.3
*/
@Deprecated
protected int compare(final Object left, final Object right, final String symbol) {
JexlOperator operator;
try {
operator = JexlOperator.valueOf(symbol);
} catch (final IllegalArgumentException xill) {
// ignore
operator = EQ;
}
return doCompare(left, right, operator);
}
/**
* Performs a bitwise complement.
*
* @param val the operand
* @return ~val
*/
public Object complement(final Object val) {
final boolean strictCast = isStrict(JexlOperator.COMPLEMENT);
final long l = toLong(strictCast, val);
return ~l;
}
/**
* Test if left contains right (right matches/in left).
* <p>Beware that this "contains " method arguments order is the opposite of the
* "in/matches" operator arguments.
* <code>x =~ y</code> means <code>y contains x</code> thus <code>contains(x, y)</code>.</p>
* <p>When this method returns null during evaluation, the operator code continues trying to find
* one through the uberspect.</p>
* @param container the container
* @param value the value
* @return test result or null if there is no arithmetic solution
*/
public Boolean contains(final Object container, final Object value) {
if (value == null && container == null) {
//if both are null L == R
return true;
}
if (value == null || container == null) {
// we know both aren't null, therefore L != R
return false;
}
// use arithmetic / pattern matching ?
if (container instanceof java.util.regex.Pattern) {
return ((java.util.regex.Pattern) container).matcher(value.toString()).matches();
}
if (container instanceof CharSequence) {
return value.toString().matches(container.toString());
}
// try contains on map key
if (container instanceof Map<?, ?>) {
if (value instanceof Map<?, ?>) {
return ((Map<?, ?>) container).keySet().containsAll(((Map<?, ?>) value).keySet());
}
return ((Map<?, ?>) container).containsKey(value);
}
// try contains on collection
return collectionContains(container, value);
}
/**
* 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;
}
/**
* The result of +,/,-,*,% when both operands are null.
* @param operator the actual operator
* @return Integer(0) if lenient
* @throws JexlArithmetic.NullOperand if strict-cast
* @since 3.3
*/
protected Object controlNullNullOperands(final JexlOperator operator) {
if (isStrict(operator)) {
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();
}
}
/**
* 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>
*
* @param strictCast whether strict cast is required
* @param defaultValue the default value to return, if not strict
* @param <T> the value type
* @return the default value is strict is false
* @throws JexlArithmetic.NullOperand if strict-cast
* @since 3.3
*/
protected <T> T controlNullOperand(final boolean strictCast, final T defaultValue) {
if (strictCast) {
throw new NullOperand();
}
return defaultValue;
}
/**
* 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(final Object returned) {
return returned;
}
/**
* Creates a literal range.
* <p>The default implementation only accepts integers and longs.</p>
*
* @param from the included lower bound value (null if none)
* @param to the included upper bound value (null if none)
* @return the range as an iterable
* @throws ArithmeticException as an option if creation fails
*/
public Iterable<?> createRange(final Object from, final Object to) throws ArithmeticException {
final long lfrom = toLong(from);
final long lto = toLong(to);
if (lfrom >= Integer.MIN_VALUE && lfrom <= Integer.MAX_VALUE
&& lto >= Integer.MIN_VALUE && lto <= Integer.MAX_VALUE) {
return org.apache.commons.jexl3.internal.IntegerRange.create((int) lfrom, (int) lto);
}
return org.apache.commons.jexl3.internal.LongRange.create(lfrom, lto);
}
/**
* Creates a JexlArithmetic instance.
* Called by options(...) method when another instance of the same class of arithmetic is required.
* @see #options(org.apache.commons.jexl3.JexlEngine.Options)
*
* @param astrict whether this arithmetic is lenient or strict
* @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
* @param bigdScale the scale used for big decimals.
* @return default is a new JexlArithmetic instance
* @since 3.1
*/
protected JexlArithmetic createWithOptions(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
if (ctor != null) {
try {
return ctor.newInstance(astrict, bigdContext, bigdScale);
} catch (IllegalAccessException | IllegalArgumentException
| InstantiationException | InvocationTargetException xany) {
// it was worth the try
}
}
return new JexlArithmetic(astrict, bigdContext, bigdScale);
}
/**
* Decrements argument by 1.
* @param val the argument
* @return val - 1
*/
public Object decrement(final Object val) {
return increment(val, -1);
}
/**
* Divide the left value by the right.
*
* @param left left argument
* @param right right argument
* @return left / right
* @throws ArithmeticException if right == 0
*/
public Object divide(final Object left, final Object right) {
if (left == null && right == null) {
return controlNullNullOperands(JexlOperator.DIVIDE);
}
final boolean strictCast = isStrict(JexlOperator.DIVIDE);
// if both (non-null) args fit as long
final Number ln = asLongNumber(strictCast, left);
final Number rn = asLongNumber(strictCast, right);
if (ln != null && rn != null) {
final long x = ln.longValue();
final long y = rn.longValue();
if (y == 0L) {
throw new ArithmeticException("/");
}
final long result = x / y;
return narrowLong(left, right, result);
}
// if either are BigDecimal, use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
return l.divide(r, getMathContext());
}
// if either are floating point (double or float), use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(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 BigInteger
final BigInteger l = toBigInteger(strictCast, left);
final BigInteger r = toBigInteger(strictCast, right);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
final BigInteger result = l.divide(r);
return narrowBigInteger(left, right, result);
}
private int doCompare(final Object left, final Object right, final JexlOperator operator) {
final boolean strictCast = isStrict(operator);
if (left != null && right != null) {
try {
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
return l.compareTo(r);
}
if (left instanceof BigInteger || right instanceof BigInteger) {
final BigInteger l = toBigInteger(strictCast, left);
final BigInteger r = toBigInteger(strictCast, right);
return l.compareTo(r);
}
if (isFloatingPoint(left) || isFloatingPoint(right)) {
final double lhs = toDouble(strictCast, left);
final double rhs = toDouble(strictCast, right);
if (Double.isNaN(lhs)) {
if (Double.isNaN(rhs)) {
return 0;
}
return -1;
}
if (Double.isNaN(rhs)) {
// lhs is not NaN
return +1;
}
return Double.compare(lhs, rhs);
}
if (isNumberable(left) || isNumberable(right)) {
final long lhs = toLong(strictCast, left);
final long rhs = toLong(strictCast, right);
return Long.compare(lhs, rhs);
}
if (left instanceof String || right instanceof String) {
return toString(left).compareTo(toString(right));
}
} catch (final CoercionException ignore) {
// ignore it, continue in sequence
}
if (EQ == operator) {
return left.equals(right) ? 0 : -1;
}
if (left instanceof Comparable<?>) {
@SuppressWarnings("unchecked") // OK because of instanceof check above
final Comparable<Object> comparable = (Comparable<Object>) left;
try {
return comparable.compareTo(right);
} catch(final ClassCastException castException) {
// ignore it, continue in sequence
}
}
}
throw new ArithmeticException("Object comparison:(" + left + " " + operator + " " + right + ")");
}
/**
* Check for emptiness of various types: Number, Collection, Array, Map, String.
* <p>Override or overload this method to add new signatures to the size operators.
* @param object the object to check the emptiness of
* @return the boolean or false if object is not null
* @since 3.2
*/
public Boolean empty(final Object object) {
return object == null || isEmpty(object, false);
}
/**
* Test if left ends with right.
*
* @param left left argument
* @param right right argument
* @return left $= right if there is no arithmetic solution
*/
public Boolean endsWith(final Object left, final Object right) {
if (left == null && right == null) {
//if both are null L == R
return true;
}
if (left == null || right == null) {
// we know both aren't null, therefore L != R
return false;
}
if (left instanceof CharSequence) {
return toString(left).endsWith(toString(right));
}
return null;
}
/**
* Test if left and right are equal.
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean equals(final Object left, final Object right) {
if (left == right) {
return true;
}
if (left == null || right == null) {
return false;
}
final boolean strictCast = isStrict(EQ);
if (left instanceof Boolean || right instanceof Boolean) {
return toBoolean(left) == toBoolean(strictCast, right);
}
return compare(left, right, EQ) == 0;
}
/**
* 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 coercion operations.
*
* @return the scale
*/
public int getMathScale() {
return mathScale;
}
/**
* Test if left > right.
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean greaterThan(final Object left, final Object right) {
if (left == right || left == null || right == null) {
return false;
}
return compare(left, right, JexlOperator.GT) > 0;
}
/**
* Test if left >= right.
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean greaterThanOrEqual(final Object left, final Object right) {
if (left == right) {
return true;
}
if (left == null || right == null) {
return false;
}
return compare(left, right, JexlOperator.GTE) >= 0;
}
/**
* Increments argument by 1.
* @param val the argument
* @return val + 1
*/
public Object increment(final Object val) {
return increment(val, 1);
}
/**
* Add value to number argument.
* @param val the number
* @param incr the value to add
* @return val + incr
*/
protected Object increment(final Object val, final int incr) {
if (val == null) {
return incr;
}
if (val instanceof Integer) {
return (Integer) val + incr;
}
if (val instanceof Double) {
return (Double) val + incr;
}
if (val instanceof Long) {
return (Long) val + incr;
}
if (val instanceof BigDecimal) {
final BigDecimal bd = (BigDecimal) val;
return bd.add(BigDecimal.valueOf(incr), this.mathContext);
}
if (val instanceof BigInteger) {
final BigInteger bi = (BigInteger) val;
return bi.add(BigInteger.valueOf(incr));
}
if (val instanceof Float) {
return (Float) val + incr;
}
if (val instanceof Short) {
return (short) ((Short) val + incr);
}
if (val instanceof Byte) {
return (byte) ((Byte) val + incr);
}
throw new ArithmeticException("Object "+(incr < 0? "decrement":"increment")+":(" + val + ")");
}
/**
* Check for emptiness of various types: Number, Collection, Array, Map, String.
*
* @param object the object to check the emptiness of
* @return the boolean or null if there is no arithmetic solution
*/
public Boolean isEmpty(final Object object) {
return isEmpty(object, object == null);
}
/**
* Check for emptiness of various types: Number, Collection, Array, Map, String.
*
* @param object the object to check the emptiness of
* @param def the default value if object emptyness can not be determined
* @return the boolean or null if there is no arithmetic solution
*/
public Boolean isEmpty(final Object object, final Boolean def) {
if (object != null) {
if (object instanceof Number) {
final double d = ((Number) object).doubleValue();
return Double.isNaN(d) || d == 0.d;
}
if (object instanceof CharSequence) {
return ((CharSequence) object).length() == 0;
}
if (object.getClass().isArray()) {
return Array.getLength(object) == 0;
}
if (object instanceof Collection<?>) {
return ((Collection<?>) object).isEmpty();
}
// Map isn't a collection
if (object instanceof Map<?, ?>) {
return ((Map<?, ?>) object).isEmpty();
}
}
return def;
}
/**
* 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;
}
/**
* 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;
}
/**
* Whether negate called with a given argument will always return the same result.
* <p>This is used to determine whether negate results on number literals can be cached.
* If the result on calling negate with the same constant argument may change between calls,
* which means the function is not deterministic, this method must return false.
* @return true if negate is idempotent, false otherwise
*/
public boolean isNegateStable() {
return true;
}
/**
* Checks if an operand is considered null.
* @param value the operand
* @return true if operand is considered null
*/
protected boolean isNullOperand(final Object value) {
return value == null;
}
/**
* 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;
}
/**
* Whether positivize called with a given argument will always return the same result.
* <p>This is used to determine whether positivize results on number literals can be cached.
* If the result on calling positivize with the same constant argument may change between calls,
* which means the function is not deterministic, this method must return false.
* @return true if positivize is idempotent, false otherwise
*/
public boolean isPositivizeStable() {
return true;
}
/**
* 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(final JexlOperator operator) {
if (operator != null) {
switch (operator) {
case EQ:
case EQSTRICT:
case ARRAY_GET:
case ARRAY_SET:
case PROPERTY_GET:
case PROPERTY_SET:
case EMPTY:
case SIZE:
case CONTAINS:
return false;
default:
return isStrict();
}
}
return isStrict();
}
/**
* Test if left < right.
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean lessThan(final Object left, final Object right) {
if (left == right || left == null || right == null) {
return false;
}
return compare(left, right, JexlOperator.LT) < 0;
}
/**
* Test if left <= right.
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean lessThanOrEqual(final Object left, final Object right) {
if (left == right) {
return true;
}
if (left == null || right == null) {
return false;
}
return compare(left, right, JexlOperator.LTE) <= 0;
}
/**
* Use or overload not() instead.
*
* @param arg argument
* @return !arg
* @see JexlArithmetic#not
* @deprecated 3.0
*/
@Deprecated
public final Object logicalNot(final Object arg) {
return not(arg);
}
/**
* Creates a map-builder.
* @param size the number of elements in the map
* @return a map-builder instance
*/
@Deprecated
public MapBuilder mapBuilder(final int size) {
return mapBuilder(size, false);
}
/**
* Called by the interpreter when evaluating a literal map.
*
* @param size the number of elements in the map
* @param extended whether the map is extended or not
* @return the map builder
*/
public MapBuilder mapBuilder(final int size, final boolean extended) {
return new org.apache.commons.jexl3.internal.MapBuilder(size, extended);
}
/**
* Use or overload contains() instead.
*
* @param lhs left hand side
* @param rhs right hand side
* @return contains(rhs, lhs)
* @see JexlArithmetic#contains
* @deprecated 3.0
*/
@Deprecated
public final Object matches(final Object lhs, final Object rhs) {
return contains(rhs, lhs);
}
/**
* left value modulo right.
*
* @param left left argument
* @param right right argument
* @return left % right
* @throws ArithmeticException if right == 0.0
*/
public Object mod(final Object left, final Object right) {
if (left == null && right == null) {
return controlNullNullOperands(JexlOperator.MOD);
}
final boolean strictCast = isStrict(JexlOperator.MOD);
// if both (non-null) args fit as long
final Number ln = asLongNumber(strictCast, left);
final Number rn = asLongNumber(strictCast, right);
if (ln != null && rn != null) {
final long x = ln.longValue();
final long y = rn.longValue();
if (y == 0L) {
throw new ArithmeticException("%");
}
final long result = x % y;
return narrowLong(left, right, result);
}
// if either are BigDecimal, use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
return l.remainder(r, getMathContext());
}
// if either are floating point (double or float), use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(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 BigInteger
final BigInteger l = toBigInteger(strictCast, left);
final BigInteger r = toBigInteger(strictCast, right);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
final BigInteger result = l.mod(r);
return narrowBigInteger(left, right, result);
}
/**
* Multiply the left value by the right.
*
* @param left left argument
* @param right right argument
* @return left * right.
*/
public Object multiply(final Object left, final Object right) {
if (left == null && right == null) {
return controlNullNullOperands(JexlOperator.MULTIPLY);
}
final boolean strictCast = isStrict(JexlOperator.MULTIPLY);
// if both (non-null) args fit as long
final Number ln = asLongNumber(strictCast, left);
final Number rn = asLongNumber(strictCast, right);
if (ln != null && rn != null) {
final long x = ln.longValue();
final long y = rn.longValue();
final long result = x * y;
// detect overflow
if (!isMultiplyExact(x, y, result)) {
return BigInteger.valueOf(x).multiply(BigInteger.valueOf(y));
}
return narrowLong(left, right, result);
}
// if either are BigDecimal, use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
return l.multiply(r, getMathContext());
}
// if either are floating point (double or float), use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
final double l = toDouble(strictCast, left);
final double r = toDouble(strictCast, right);
return l * r;
}
// otherwise treat as BigInteger
final BigInteger l = toBigInteger(strictCast, left);
final BigInteger r = toBigInteger(strictCast, right);
final BigInteger result = l.multiply(r);
return narrowBigInteger(left, right, result);
}
/**
* Narrows a double to a float if there is no information loss.
* @param value the double value
* @param narrow the target narrow class
* @return the narrowed or initial number
*/
private Number narrow(final Class<?> narrow, final double value) {
return narrowAccept(narrow, Float.class) && (float) value == value
? (float) value
: value;
}
/**
* 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
* @return true if attempt to narrow source to target is accepted
*/
protected boolean narrowAccept(final Class<?> narrow, final Class<?> source) {
return narrow == null || narrow.equals(source);
}
/**
* Replace all numbers in an arguments array with the smallest type that will fit.
*
* @param args the argument array
* @return true if some arguments were narrowed and args array is modified,
* false if no narrowing occurred and args array has not been modified
*/
public boolean narrowArguments(final Object[] args) {
boolean narrowed = false;
if (args != null) {
for (int a = 0; a < args.length; ++a) {
final Object arg = args[a];
if (arg instanceof Number) {
final Number narg = (Number) arg;
final Number narrow = narrow(narg);
if (!narg.equals(narrow)) {
args[a] = narrow;
narrowed = true;
}
}
}
}
return narrowed;
}
/**
* Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits and
* one of the arguments is numberable.
*
* @param lhs the left-hand side operand that lead to the bigd result
* @param rhs the right-hand side operand that lead to the bigd result
* @param big the BigDecimal to narrow
* @return an Integer or Long if narrowing is possible, the original BigDecimal otherwise
*/
protected Number narrowBigDecimal(final Object lhs, final Object rhs, final BigDecimal big) {
if (isNumberable(lhs) || isNumberable(rhs)) {
try {
final long l = big.longValueExact();
// coerce to int when possible (int being so often used in method parms)
if ((int) l == l) {
return (int) l;
}
return l;
} catch (final ArithmeticException xa) {
// ignore, no exact value possible
}
}
return big;
}
/**
* Given a BigInteger, narrow it to an Integer or Long if it fits and the arguments
* class allow it.
* <p>
* The rules are:
* if either arguments is a BigInteger, no narrowing will occur
* if either arguments is a Long, no narrowing to Integer will occur
* </p>
*
* @param lhs the left-hand side operand that lead to the bigi result
* @param rhs the right-hand side operand that lead to the bigi result
* @param big the BigInteger to narrow
* @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
*/
protected Number narrowBigInteger(final Object lhs, final Object rhs, final BigInteger big) {
if (isNumberable(lhs) || isNumberable(rhs)) {
try {
final long l = big.longValueExact();
// coerce to int when possible (int being so often used in method parms)
if ((int) l == l) {
return (int) l;
}
return l;
} catch (final ArithmeticException xa) {
// ignore, no exact value possible
}
}
return big;
}
/**
* Given a long, attempt to narrow it to an int.
* <p>Narrowing will only occur if no operand is a Long.
* @param lhs the left hand side operand that lead to the long result
* @param rhs the right hand side operand that lead to the long result
* @param r the long to narrow
* @return an Integer if narrowing is possible, the original Long otherwise
*/
protected Number narrowLong(final Object lhs, final Object rhs, final long r) {
if (!(lhs instanceof Long || rhs instanceof Long) && (int) r == r) {
return (int) r;
}
return r;
}
/**
* Given a Number, return the value attempting to narrow it to a target class.
*
* @param original the original number
* @param narrow the attempted target class
* @return the narrowed number or the source if no narrowing was possible
*/
public Number narrowNumber(final Number original, final Class<?> narrow) {
if (original != null) {
final long value;
if (original instanceof BigDecimal) {
final BigDecimal big = (BigDecimal) original;
try {
// can it be represented as a long?
value = big.longValueExact();
// continue in sequence to try and further reduce
} catch (final ArithmeticException xa) {
// if it is bigger than a double, it can not be narrowed
if (big.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0
|| big.compareTo(BIGD_DOUBLE_MIN_VALUE) < 0) {
return original;
}
// represent as double
return narrow(narrow, original.doubleValue());
}
// this continues with value as long
} else {
if (isFloatingPoint(original)) {
final double doubleValue = original.doubleValue();
// if it is not equivalent to a Long...
if ((long) doubleValue != doubleValue) {
return narrow(narrow, doubleValue);
}
// else it can be represented as a long
} else if (original instanceof BigInteger) {
final BigInteger bigi = (BigInteger) original;
// if it is bigger than a Long, it can not be narrowed
if (!BigInteger.valueOf(bigi.longValue()).equals(bigi)) {
return original;
}
// else it can be represented as a long
}
value = original.longValue();
}
// it can be represented as a long; determine the smallest possible numberable representation
if (narrowAccept(narrow, Byte.class) && (byte) value == value) {
// it will fit in a byte
return (byte) value;
}
if (narrowAccept(narrow, Short.class) && (short) value == value) {
return (short) value;
}
if (narrowAccept(narrow, Integer.class) && (int) value == value) {
return (int) value;
}
}
return original;
}
/**
* Negates a value (unary minus for numbers).
*
* @see #isNegateStable()
* @param val the value to negate
* @return the negated value
*/
public Object negate(final Object val) {
if (val == null) {
return null;
}
if (val instanceof Integer) {
return -((Integer) val);
}
if (val instanceof Double) {
return - ((Double) val);
}
if (val instanceof Long) {
return -((Long) val);
}
if (val instanceof BigDecimal) {
return ((BigDecimal) val).negate();
}
if (val instanceof BigInteger) {
return ((BigInteger) val).negate();
}
if (val instanceof Float) {
return -((Float) val);
}
if (val instanceof Short) {
return (short) -((Short) val);
}
if (val instanceof Byte) {
return (byte) -((Byte) val);
}
if (val instanceof Boolean) {
return !(Boolean) val;
}
if (val instanceof AtomicBoolean) {
return !((AtomicBoolean) val).get();
}
throw new ArithmeticException("Object negate:(" + val + ")");
}
/**
* Performs a logical not.
*
* @param val the operand
* @return !val
*/
public Object not(final Object val) {
final boolean strictCast = isStrict(JexlOperator.NOT);
return !toBoolean(strictCast, val);
}
/**
* Apply options to this arithmetic which eventually may create another instance.
* @see #createWithOptions(boolean, java.math.MathContext, int)
*
* @param context the context that may extend {@link JexlContext.OptionsHandle} to use
* @return a new arithmetic instance or this
* @since 3.1
*/
public JexlArithmetic options(final JexlContext context) {
if (context instanceof JexlContext.OptionsHandle) {
return options(((JexlContext.OptionsHandle) context).getEngineOptions());
}
if (context instanceof JexlEngine.Options) {
return options((JexlEngine.Options) context);
}
return this;
}
/**
* Apply options to this arithmetic which eventually may create another instance.
* @see #createWithOptions(boolean, java.math.MathContext, int)
*
* @param options the {@link JexlEngine.Options} to use
* @return an arithmetic with those options set
* @deprecated 3.2
*/
@Deprecated
public JexlArithmetic options(final JexlEngine.Options options) {
if (options != null) {
Boolean ostrict = options.isStrictArithmetic();
if (ostrict == null) {
ostrict = isStrict();
}
MathContext bigdContext = options.getArithmeticMathContext();
if (bigdContext == null) {
bigdContext = getMathContext();
}
int bigdScale = options.getArithmeticMathScale();
if (bigdScale == Integer.MIN_VALUE) {
bigdScale = getMathScale();
}
if (ostrict != isStrict()
|| bigdScale != getMathScale()
|| bigdContext != getMathContext()) {
return createWithOptions(ostrict, bigdContext, bigdScale);
}
}
return this;
}
/**
* Apply options to this arithmetic which eventually may create another instance.
* @see #createWithOptions(boolean, java.math.MathContext, int)
*
* @param options the {@link JexlEngine.Options} to use
* @return an arithmetic with those options set
*/
public JexlArithmetic options(final JexlOptions options) {
if (options != null) {
final boolean ostrict = options.isStrictArithmetic();
MathContext bigdContext = options.getMathContext();
if (bigdContext == null) {
bigdContext = getMathContext();
}
int bigdScale = options.getMathScale();
if (bigdScale == Integer.MIN_VALUE) {
bigdScale = getMathScale();
}
if (ostrict != isStrict()
|| bigdScale != getMathScale()
|| bigdContext != getMathContext()) {
return createWithOptions(ostrict, bigdContext, bigdScale);
}
}
return this;
}
/**
* Performs a bitwise or.
*
* @param left the left operand
* @param right the right operator
* @return left | right
*/
public Object or(final Object left, final Object right) {
final long l = toLong(left);
final long r = toLong(right);
return l | r;
}
/**
* Convert a string to a BigDecimal.
* <>Empty string is considered as 0.</>
* @param arg the arg
* @return a BigDecimal
* @throws CoercionException if the string can not be coerced into a BigDecimal
*/
private BigDecimal parseBigDecimal(final String arg) throws ArithmeticException {
try {
return arg.isEmpty()? BigDecimal.ZERO : new BigDecimal(arg, getMathContext());
} catch (final NumberFormatException e) {
final ArithmeticException arithmeticException = new CoercionException("BigDecimal coercion: ("+ arg +")");
arithmeticException.initCause(e);
throw arithmeticException;
}
}
/**
* Converts a string to a big integer.
* <>Empty string is considered as 0.</>
* @param arg the arg
* @return a big integer
* @throws ArithmeticException if the string can not be coerced into a big integer
*/
private BigInteger parseBigInteger(final String arg) throws ArithmeticException {
try {
return arg.isEmpty()? BigInteger.ZERO : new BigInteger(arg);
} catch (final NumberFormatException e) {
final ArithmeticException arithmeticException = new CoercionException("BigDecimal coercion: ("+ arg +")");
arithmeticException.initCause(e);
throw arithmeticException;
}
}
/**
* Convert a string to a double.
* <>Empty string is considered as NaN.</>
* @param arg the arg
* @return a double
* @throws ArithmeticException if the string can not be coerced into a double
*/
private double parseDouble(final String arg) throws ArithmeticException {
try {
return arg.isEmpty()? Double.NaN : Double.parseDouble(arg);
} catch (final NumberFormatException e) {
final ArithmeticException arithmeticException = new CoercionException("Double coercion: ("+ arg +")");
arithmeticException.initCause(e);
throw arithmeticException;
}
}
/**
* Converts a string to an int.
* <p>This ensure the represented number is a natural (not a real).</p>
* @param arg the arg
* @return an int
* @throws ArithmeticException if the string can not be coerced into a long
*/
private int parseInteger(final String arg) throws ArithmeticException {
final long l = parseLong(arg);
final int i = (int) l;
if (i == l) {
return i;
}
throw new CoercionException("Int coercion: ("+ arg +")");
}
/**
* Converts a string to a long.
* <p>This ensure the represented number is a natural (not a real).</p>
* @param arg the arg
* @return a long
* @throws ArithmeticException if the string can not be coerced into a long
*/
private long parseLong(final String arg) throws ArithmeticException {
final double d = parseDouble(arg);
if (Double.isNaN(d)) {
return 0L;
}
final double f = floor(d);
if (d == f) {
return (long) d;
}
throw new CoercionException("Long coercion: ("+ arg +")");
}
/**
* Positivize value (unary plus for numbers).
* <p>C/C++/C#/Java perform integral promotion of the operand, ie
* cast to int if type can be represented as int without loss of precision.
* @see #isPositivizeStable()
* @param val the value to positivize
* @return the positive value
*/
public Object positivize(final Object val) {
if (val == null) {
return null;
}
if (val instanceof Short) {
return ((Short) val).intValue();
}
if (val instanceof Byte) {
return ((Byte) val).intValue();
}
if (val instanceof Number) {
return val;
}
if (val instanceof Character) {
return (int) (Character) val;
}
if (val instanceof Boolean) {
return val;
}
if (val instanceof AtomicBoolean) {
return ((AtomicBoolean) val).get();
}
throw new ArithmeticException("Object positivize:(" + val + ")");
}
/**
* 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;
}
/**
* Creates a set-builder.
* @param size the number of elements in the set
* @return a set-builder instance
* @deprecated since 3.3.1
*/
@Deprecated
public SetBuilder setBuilder(final int size) {
return setBuilder(size, false);
}
/**
* Called by the interpreter when evaluating a literal set.
*
* @param size the number of elements in the set
* @param extended whether the set is extended or not
* @return the array builder
*/
public SetBuilder setBuilder(final int size, final boolean extended) {
return new org.apache.commons.jexl3.internal.SetBuilder(size, extended);
}
/**
* Shifts a bit pattern to the right.
*
* @param left left argument
* @param right right argument
* @return left << right.
*/
public Object shiftLeft(final Object left, final Object right) {
final long l = toLong(left);
final int r = toInteger(right);
return l << r;
}
/**
* Shifts a bit pattern to the right.
*
* @param left left argument
* @param right right argument
* @return left >> right.
*/
public Object shiftRight(final Object left, final Object right) {
final long l = toLong(left);
final long r = toInteger(right);
return l >> r;
}
/**
* Shifts a bit pattern to the right unsigned.
*
* @param left left argument
* @param right right argument
* @return left >>> right.
*/
public Object shiftRightUnsigned(final Object left, final Object right) {
final long l = toLong(left);
final long r = toInteger(right);
return l >>> r;
}
/**
* Calculate the <code>size</code> of various types: Collection, Array, Map, String.
*
* @param object the object to get the size of
* @return the <i>size</i> of object, 0 if null, 1 if there is no <i>better</i> solution
*/
public Integer size(final Object object) {
return size(object, object == null ? 0 : 1);
}
/**
* Calculate the <code>size</code> of various types: Collection, Array, Map, String.
*
* @param object the object to get the size of
* @param def the default value if object size can not be determined
* @return the size of object or null if there is no arithmetic solution
*/
public Integer size(final Object object, final Integer def) {
if (object instanceof CharSequence) {
return ((CharSequence) object).length();
}
if (object.getClass().isArray()) {
return Array.getLength(object);
}
if (object instanceof Collection<?>) {
return ((Collection<?>) object).size();
}
if (object instanceof Map<?, ?>) {
return ((Map<?, ?>) object).size();
}
return def;
}
/**
* Test if left starts with right.
*
* @param left left argument
* @param right right argument
* @return left ^= right or null if there is no arithmetic solution
*/
public Boolean startsWith(final Object left, final Object right) {
if (left == null && right == null) {
//if both are null L == R
return true;
}
if (left == null || right == null) {
// we know both aren't null, therefore L != R
return false;
}
if (left instanceof CharSequence) {
return toString(left).startsWith(toString(right));
}
return null;
}
/**
* Test if left and right are strictly equal.
* <p>They must have the same class, comparable and the comparison returns 0.</p>
*
* @param left left argument
* @param right right argument
* @return the test result
*/
public boolean strictEquals(final Object left, final Object right) {
if (left == right) {
return true;
}
if (left == null || right == null) {
return false;
}
if (left.getClass().equals(right.getClass())) {
return left.equals(right);
}
return false;
}
/**
* Subtract the right value from the left.
*
* @param left left argument
* @param right right argument
* @return left - right.
*/
public Object subtract(final Object left, final Object right) {
if (left == null && right == null) {
return controlNullNullOperands(JexlOperator.SUBTRACT);
}
final boolean strictCast = isStrict(JexlOperator.SUBTRACT);
// if both (non-null) args fit as long
final Number ln = asLongNumber(strictCast, left);
final Number rn = asLongNumber(strictCast, right);
if (ln != null && rn != null) {
final long x = ln.longValue();
final long y = rn.longValue();
final long result = x - y;
// detect overflow, see java8 Math.subtractExact
if (((x ^ y) & (x ^ result)) < 0) {
return BigInteger.valueOf(x).subtract(BigInteger.valueOf(y));
}
return narrowLong(left, right, result);
}
// if either are BigDecimal, use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
final BigDecimal l = toBigDecimal(strictCast, left);
final BigDecimal r = toBigDecimal(strictCast, right);
return l.subtract(r, getMathContext());
}
// if either are floating point (double or float), use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
final double l = toDouble(strictCast, left);
final double r = toDouble(strictCast, right);
return l - r;
}
// otherwise treat as BigInteger
final BigInteger l = toBigInteger(strictCast, left);
final BigInteger r = toBigInteger(strictCast, right);
final BigInteger result = l.subtract(r);
return narrowBigInteger(left, right, result);
}
/**
* 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);
}
/**
* 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
* @since 3.3
*/
protected BigDecimal toBigDecimal(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, BigDecimal.ZERO) : toBigDecimal(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 instanceof Double) {
if (Double.isNaN((Double) val)) {
return BigDecimal.ZERO;
}
return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
}
if (val instanceof Number) {
return roundBigDecimal(parseBigDecimal(val.toString()));
}
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) {
return roundBigDecimal(parseBigDecimal((String) val));
}
if (val instanceof Character) {
return new BigDecimal((Character) val);
}
if (val == null) {
return controlNullOperand(strict, BigDecimal.ZERO);
}
throw new CoercionException("BigDecimal coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* 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 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
* @since 3.3
*/
protected BigInteger toBigInteger(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, BigInteger.ZERO) : toBigInteger(val);
}
/**
* 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 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);
}
if (val == null) {
return controlNullOperand(strict, BigInteger.ZERO);
}
throw new CoercionException("BigInteger coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* Coerce to a primitive boolean.
* <p>Double.NaN, null, "false" and empty string coerce to false.</p>
*
* @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 toBoolean(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, false) : toBoolean(val);
}
/**
* 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 instanceof Boolean) {
return (Boolean) val;
}
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);
}
if (val == null) {
return controlNullOperand(strict, false);
}
// non-null value is true
return true;
}
/**
* 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
* @since 3.3
*/
protected double toDouble(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, 0.d) : toDouble(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 instanceof Double) {
return (Double) val;
}
if (val instanceof Number) {
return ((Number) val).doubleValue();
}
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;
}
if (val == null) {
return controlNullOperand(strict, 0.d);
}
throw new CoercionException("Double coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* 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 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
* @since 3.3
*/
protected int toInteger(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, 0) : toInteger(val);
}
/**
* 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 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;
}
if (val == null) {
return controlNullOperand(strict, 0);
}
throw new CoercionException("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 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
* @since 3.3
*/
protected long toLong(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, 0L) : toLong(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 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;
}
if (val == null) {
return controlNullOperand(strict, 0L);
}
throw new CoercionException("Long 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
* @since 3.3
*/
protected String toString(final boolean strict, final Object val) {
return isNullOperand(val)? controlNullOperand(strict, "") : toString(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 instanceof Double) {
final Double dval = (Double) val;
if (Double.isNaN(dval)) {
return "";
}
return dval.toString();
}
return val == null ? controlNullOperand(strict, "") : val.toString();
}
/**
* Performs a bitwise xor.
*
* @param left the left operand
* @param right the right operator
* @return left ^ right
*/
public Object xor(final Object left, final Object right) {
final long l = toLong(left);
final long r = toLong(right);
return l ^ r;
}
}