/**
* Copyright 2016 Nabarun Mondal
* Licensed 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 com.noga.njexl.lang;
import com.noga.njexl.lang.extension.TypeUtility;
import com.noga.njexl.lang.extension.datastructures.ListSet;
import com.noga.njexl.lang.extension.SetOperations;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import com.noga.njexl.lang.extension.iterators.DateIterator;
import com.noga.njexl.lang.extension.iterators.YieldedIterator;
import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Arithmetic;
import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Logic;
import com.noga.njexl.lang.extension.oop.ScriptMethod;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Period;
/**
* Perform arithmetic.
*
* All arithmetic operators (+, - , *, /, %) follow the same rules regarding their arguments.
* <ol>
* <li>If both are null, result is 0</li>
* <li>If either is a BigDecimal, coerce both to BigDecimal and and perform operation</li>
* <li>If either is a floating point number, coerce both to Double and perform operation</li>
* <li>If both are BigInteger, treat as BigInteger and perform operation</li>
* <li>Else treat as BigInteger, perform operation and attempt to narrow result:
* <ol>
* <li>if both arguments can be narrowed to Integer, narrow result to Integer</li>
* <li>if both arguments can be narrowed to Long, narrow result to Long</li>
* <li>Else return result as BigInteger</li>
* </ol>
* </li>
* </ol>
*
* Note that the only exception throw by JexlArithmetic is ArithmeticException.
* @since 2.0
*/
public class JexlArithmetic {
/** Double.MAX_VALUE as BigDecimal. */
protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
/** Double.MIN_VALUE as BigDecimal. */
protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_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.
* @since 2.1
*/
protected static final int BIGD_SCALE = -1;
/** Whether this JexlArithmetic instance behaves in strict or lenient mode.
* May be made final in a later version.
*/
private volatile boolean strict;
/**
* The big decimal math context.
* @since 2.1
*/
protected final MathContext mathContext;
/**
* The big decimal scale.
* @since 2.1
*/
protected final int mathScale;
public static boolean isListOrArray(Object a){
if ( a != null ){
return a instanceof List || a.getClass().isArray() || a instanceof YieldedIterator;
}
return false;
}
public static boolean isListOrSetOrArray(Object a){
if ( a != null ){
return a instanceof List || a instanceof Set || a.getClass().isArray() || a instanceof YieldedIterator;
}
return false;
}
public static boolean isTimeLike(Object a){
if ( a != null ){
return a instanceof Date || a instanceof DateTime
|| a instanceof Instant ;
}
return false;
}
public static boolean isAtomicNum(Object a){
return ( a instanceof AtomicInteger || a instanceof AtomicLong );
}
public static boolean areTimeLike(Object l, Object r){
return isTimeLike(l) && isTimeLike(r);
}
public static boolean areListOrArray(Object l, Object r){
return isListOrArray(l) && isListOrArray(r);
}
public static boolean areListOrSetOrArray(Object l, Object r){
return isListOrSetOrArray(l) && isListOrSetOrArray(r);
}
/**
* Creates a JexlArithmetic.
* @param lenient whether this arithmetic is lenient or strict
*/
public JexlArithmetic(boolean lenient) {
this(lenient, MathContext.DECIMAL128, BIGD_SCALE);
}
/**
* Creates a JexlArithmetic.
* @param lenient 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.
* @since 2.1
*/
public JexlArithmetic(boolean lenient, MathContext bigdContext, int bigdScale) {
this.strict = !lenient;
this.mathContext = bigdContext;
this.mathScale = bigdScale;
}
/**
* Sets whether this JexlArithmetic instance triggers errors during evaluation when
* null is used as an operand.
* <p>This method is <em>not</em> thread safe; it may be called as an optional step by the JexlEngine
* in its initialization code before expression creation & evaluation.</p>
* @see JexlEngine#setLenient
* @see JexlEngine#setSilent
* @see JexlEngine#setDebug
* @param flag true means no JexlException will occur, false allows them
* @deprecated as of 2.1 - may be removed in a later release
*/
@Deprecated
void setLenient(boolean flag) {
this.strict = !flag;
}
/**
* Checks whether this JexlArithmetic instance triggers errors during evaluation
* when null is used as an operand.
* @return true if lenient, false if strict
*/
public boolean isLenient() {
return !this.strict;
}
/**
* The MathContext instance used for +,-,/,*,% operations on big decimals.
* @return the math context
* @since 2.1
*/
public MathContext getMathContext() {
return mathContext;
}
/**
* The BigDecimal scale used for comparison and coercion operations.
* @return the scale
* @since 2.1
*/
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
* @since 2.1
*/
public BigDecimal roundBigDecimal(final BigDecimal number) {
int mscale = getMathScale();
if (mscale >= 0) {
return number.setScale(mscale, getMathContext().getRoundingMode());
} else {
return number;
}
}
/**
* The result of +,/,-,*,% when both operands are null.
* @return Integer(0) if lenient
* @throws ArithmeticException if strict
*/
protected Object controlNullNullOperands() {
if (!isLenient()) {
throw new ArithmeticException(JexlException.NULL_OPERAND);
}
return Integer.valueOf(0);
}
/**
* Throw a NPE if arithmetic is strict.
* @throws ArithmeticException if strict
*/
protected void controlNullOperand() {
if (!isLenient()) {
throw new ArithmeticException(JexlException.NULL_OPERAND);
}
}
/**
* 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(Object val) {
if (val instanceof Float || val instanceof Double) {
return true;
}
if (val instanceof String) {
String string = (String) val;
return string.indexOf('.') != -1 || string.indexOf('e') != -1 || string.indexOf('E') != -1;
}
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 static 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 static boolean isNumberable(final Object o) {
return o instanceof Integer
|| o instanceof Long
|| o instanceof Byte
|| o instanceof Short
|| o instanceof Character;
}
/**
* Checks if it is in Z, natural numbers
* @param o the object
* @return true if o in Z, else false
*/
public static boolean isZ(Object o){
return isNumberable(o) || o instanceof BigInteger ;
}
/**
* Checks if it is in Q, rational numbers
* @param o the object
* @return true if o in Q, else false
*/
public static boolean isQ(Object o){
return isFloatingPoint(o) || o instanceof BigDecimal ;
}
/**
* 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 bigi the BigInteger to narrow
* @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
*/
protected Number narrowBigInteger(Object lhs, Object rhs, BigInteger bigi) {
//coerce to long if possible
if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
&& bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
&& bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
// coerce to int if possible
long l = bigi.longValue();
// coerce to int when possible (int being so often used in method parms)
if (!(lhs instanceof Long || rhs instanceof Long)
&& l <= Integer.MAX_VALUE
&& l >= Integer.MIN_VALUE) {
return Integer.valueOf((int) l);
}
return Long.valueOf(l);
}
return bigi;
}
/**
* Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits if
* one of the arguments is a 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 bigd the BigDecimal to narrow
* @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
* @since 2.1
*/
protected Number narrowBigDecimal(Object lhs, Object rhs, BigDecimal bigd) {
if (isNumberable(lhs) || isNumberable(rhs)) {
try {
long l = bigd.longValueExact();
// coerce to int when possible (int being so often used in method parms)
if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE) {
return Integer.valueOf((int) l);
} else {
return Long.valueOf(l);
}
} catch (ArithmeticException xa) {
// ignore, no exact value possible
}
}
return bigd;
}
/**
* Given an array of objects, attempt to type it more strictly.
* <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>
* @param untyped an untyped array
* @return the original array if the attempt to strictly type the array fails, a typed array otherwise
*/
protected Object narrowArrayType(Object[] untyped) {
final int size = untyped.length;
Class<?> commonClass = null;
if (size > 0) {
boolean isNumber = true;
// for all children after first...
for (int u = 0; u < size && !Object.class.equals(commonClass); ++u) {
if (untyped[u] != null) {
Class<?> eclass = untyped[u].getClass();
// base common class on first non-null entry
if (commonClass == null) {
commonClass = eclass;
isNumber &= Number.class.isAssignableFrom(commonClass);
} else if (!commonClass.equals(eclass)) {
// if both are numbers...
if (isNumber && Number.class.isAssignableFrom(eclass)) {
commonClass = Number.class;
} else {
// attempt to find valid superclass
do {
eclass = eclass.getSuperclass();
if (eclass == null) {
commonClass = Object.class;
break;
}
} while (!commonClass.isAssignableFrom(eclass));
}
}
} else {
isNumber = false;
}
}
// convert array to the common class if not Object.class
if (commonClass != null && !Object.class.equals(commonClass)) {
// if the commonClass has an equivalent primitive type, get it
if (isNumber) {
try {
final Field type = commonClass.getField("TYPE");
commonClass = (Class<?>) type.get(null);
} catch (Exception xany) {
// ignore
}
}
// allocate and fill up the typed array
Object typed = Array.newInstance(commonClass, size);
for (int i = 0; i < size; ++i) {
Array.set(typed, i, untyped[i]);
}
return typed;
}
}
return untyped;
}
/**
* 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 occured and args array has not been modified
*/
public boolean narrowArguments(Object[] args) {
boolean narrowed = false;
for (int a = 0; a < args.length; ++a) {
Object arg = args[a];
if (arg instanceof Number) {
Object narg = narrow((Number) arg);
if (narg != arg) {
narrowed = true;
}
args[a] = narg;
}
}
return narrowed;
}
/**
* 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 first value
* @param right second value
* @return left + right.
*/
public Object add(Object left, Object right) {
if (left == null && right == null) {
return controlNullNullOperands();
}
try {
if ( left instanceof String ){
return toString(left).concat(toString(right));
}
// if either are bigdecimal use that type -- these are priority
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
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)) {
double l = toDouble(left);
double r = toDouble(right);
return new Double(l + r);
}
if ( left instanceof Character ){
if ( !( right instanceof Number) ){
return toString(left).concat(toString(right));
}
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.add(r);
return narrowBigInteger(left, right, result);
} catch (Exception e) {
if ( !(right instanceof String) &&
left instanceof Arithmetic ){ // very important
return ((Arithmetic)left).add(right);
}
if ( left instanceof Set ){
ListSet r = new ListSet((Set) left);
if (right instanceof Collection) {
r.addAll((Collection) right);
}else{
r.add(right);
}
return r;
}
if ( isListOrArray(left) ){
ArrayList r = TypeUtility.from(left);
if (isListOrArray(right)) {
r.addAll(TypeUtility.from(right)) ;
}else{
r.add(right);
}
return r;
}
if ( left instanceof Map && right instanceof Map){
HashMap r = new HashMap((Map)left);
r.putAll((Map)right);
return r;
}
if ( left instanceof Collection ){
Collection l = (Collection)left;
if ( right instanceof Collection ){
Collection r = (Collection)right;
l.addAll(r);
return l;
}
l.add(right);
return l;
}
if ( left instanceof DateTime ){
if ( right instanceof Integer ){
return ((DateTime) left).plus((long)right);
}
if ( right instanceof Duration ){
return ((DateTime) left).plus((Duration)right);
}
if ( right instanceof String ){
return ((DateTime) left).plus( DateIterator.parseDuration((String)right));
}
}
// Well, use strings!
return toString(left).concat(toString(right));
}
}
public static Collection addToCollection(Collection left, Object array){
int length = Array.getLength(array);
for ( int i = 0 ; i < length; i++ ){
Object e = Array.get(array,i);
left.add(e);
}
return left;
}
public static Collection removeFromCollection(Collection left, Object array){
int length = Array.getLength(array);
for ( int i = 0 ; i < length; i++ ){
Object e = Array.get(array,i);
left.remove(e);
}
return left;
}
public static Map removeFromMap(Map left, Object array){
int length = Array.getLength(array);
for ( int i = 0 ; i < length; i++ ){
Object e = Array.get(array,i);
left.remove(e);
}
return left;
}
public static Map removeFromMap(Map left, Collection right){
for (Object k : right ){
if ( left.containsKey(k) ){
left.remove(k);
}
}
return left;
}
public static Map removeFromMap(Map left, Map right){
for (Object k : right.keySet() ){
if ( left.containsKey(k) ){
Object ol = left.get(k);
Object or = right.get(k);
// awesomely bad idea -- but... let it pass
if ( SetOperations.arithmatic.equals(ol,or) ){
left.remove(k);
}
}
}
return left;
}
/**
* Add two values together, and assigns to left += .
* <p>
* If any numeric add fails on coercion to the appropriate type,
* treat as Strings and do concatenation.
* </p>
* @param left first value
* @param right second value
* @return left + right.
*/
public synchronized Object addMutable(Object left, Object right) {
if (left == null && right == null) {
return controlNullNullOperands();
}
try {
// if either are bigdecimal use that type -- these are priority
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
BigDecimal result = l.add(r, getMathContext());
left = narrowBigDecimal(left, right, result);
return left ;
}
if ( left instanceof String ){
left = toString(left).concat(toString(right));
return left;
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
double l = toDouble(left);
double r = toDouble(right);
l+= r ;
return l;
}
if ( left instanceof Character ){
if ( !( right instanceof Number) ){
left = toString(left).concat(toString(right));
return left;
}
}
Object oldLeft = null ;
if ( isAtomicNum(left) ){
oldLeft = left ;
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.add(r);
left = narrowBigInteger(left, right, result);
if ( oldLeft == null ) return left ;
if (oldLeft instanceof AtomicInteger) {
((AtomicInteger) oldLeft).set(((Number)left).intValue());
} else {
((AtomicLong) oldLeft).set(((Number)left).longValue());
}
return oldLeft;
} catch (Exception e) {
if ( !(right instanceof String) &&
left instanceof Arithmetic ){ // very important
left = ((Arithmetic)left).add(right);
return left;
}
if ( left instanceof Collection ) {
if ( right instanceof Collection ) {
((Collection)left).addAll((Collection) right) ;
}else if ( right != null && right.getClass().isArray() ){
left = addToCollection((Collection)left,right);
}
else{
((Collection)left).add(right) ;
}
return left;
}
if ( left != null && left.getClass().isArray() ){
left = TypeUtility.array(left,right);
return left;
}
if ( left instanceof Map && right instanceof Map){
((Map)left).putAll((Map)right);
return left;
}
if ( left instanceof DateTime ){
if ( right instanceof Integer ){
left = ((DateTime) left).plus((long)right);
return left;
}
if ( right instanceof Duration ){
left = ((DateTime) left).plus((Duration)right);
return left;
}
if ( right instanceof String ){
left = ((DateTime) left).plus( DateIterator.parseDuration((String)right));
return left ;
}
}
// Well, use strings!
left = toString(left).concat(toString(right));
return left;
}
}
/**
* Divide the left value by the right.
* @param left first value
* @param right second value
* @return left / right
* @throws ArithmeticException if right == 0
*/
public Object divide(Object left, Object right) {
try {
if (left == null && right == null) {
return controlNullNullOperands();
}
// if either are bigdecimal use that type --> highest priority
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
BigDecimal result = l.divide(r, getMathContext());
return narrowBigDecimal(left, right, result);
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
double l = toDouble(left);
double r = toDouble(right);
if (r == 0.0) {
throw new ArithmeticException("/");
}
return new Double(l / r);
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("/");
}
BigInteger result = l.divide(r);
return narrowBigInteger(left, right, result);
}catch (Throwable e){
if ( left instanceof Arithmetic ){
return ((Arithmetic) left).div(right);
}
if ( left instanceof Map ){
return SetOperations.dict_divide((Map) left, right);
}
if ( areListOrSetOrArray( left,right ) ){
return SetOperations.division(left,right);
}
throw e;
}
}
/**
* left value mod right.
* @param left first value
* @param right second value
* @return left mod right
* @throws ArithmeticException if right == 0.0
*/
public Object mod(Object left, Object right) {
if (left == null && right == null) {
return controlNullNullOperands();
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
double l = toDouble(left);
double r = toDouble(right);
if (r == 0.0) {
throw new ArithmeticException("%");
}
return new Double(l % r);
}
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
if (BigDecimal.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
BigDecimal remainder = l.remainder(r, getMathContext());
return narrowBigDecimal(left, right, remainder);
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.mod(r);
if (BigInteger.ZERO.equals(r)) {
throw new ArithmeticException("%");
}
// must use right as the bound, because it is mod...
return narrowBigInteger((short)0, right, result);
}
/**
* Multiply the left value by the right.
* @param left first value
* @param right second value
* @return left * right.
*/
public Object multiply(Object left, Object right) {
try {
if (left == null && right == null) {
return controlNullNullOperands();
}
// if either are bigdecimal use that type --> highest priority
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
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)) {
double l = toDouble(left);
double r = toDouble(right);
return new Double(l * r);
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.multiply(r);
return narrowBigInteger(left, right, result);
}catch (Exception e){
// check if join is possible?
if ( areListOrArray(left,right) ){
return SetOperations.join(left,right);
}
if ( left instanceof ScriptMethod && right instanceof ScriptMethod ){
return ((ScriptMethod) left).compose((ScriptMethod)right );
}
if ( left instanceof Arithmetic){
return ((Arithmetic) left).mul(right);
}
throw e;
}
}
public BigDecimal pow(Object left, Object right){
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
MathContext mc = getMathContext();
try{
BigInteger power = r.toBigIntegerExact();
if ( power.equals( BigInteger.ZERO )){
return BigDecimal.ONE ;
}
if ( power.equals( BigInteger.ONE )){
return l ;
}
BigDecimal result;
if ( power.compareTo(BigInteger.ZERO) > 0 ) {
result = l ;
// +ve power
while (power.compareTo(BigInteger.ONE) > 0) {
result = result.multiply(l, mc);
power = power.subtract(BigInteger.ONE);
}
}else{
result = BigDecimal.ONE ;
// -ve power
while (power.compareTo(BigInteger.ZERO) < 0) {
result = result.divide(l, mc);
power = power.add(BigInteger.ONE);
}
}
return result;
}catch (ArithmeticException e){
/* now that is what we think is fractional
* Do a total hack * */
if ( r.compareTo(BigDecimal.ZERO) < 0 ){
l = BigDecimal.ONE.divide(l,mc);
r = r.negate();
}
BigDecimal intPart = new BigDecimal(r.toBigInteger());
BigDecimal fraction = r.subtract( intPart );
BigDecimal a = pow(l,intPart);
double ext = Math.pow( l.doubleValue(), fraction.doubleValue());
BigDecimal result = a.multiply( new BigDecimal(ext));
return result ;
}
}
public static List reverseList(List l){
List r = new ArrayList<>();
int size = l.size();
for ( int i = size - 1; i >= 0 ; i--){
r.add(l.get(i));
}
return r;
}
/**
* Power of the left value by the right.
* @param left first value
* @param right second value
* @return power(lef, right).
*/
public Object power(Object left, Object right) {
if (left == null && right == null) {
return controlNullNullOperands();
}
try {
BigDecimal pow = pow(left, right);
// if either are big decimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
return pow;
}
// for big integer types
if (right instanceof BigInteger) {
if (left instanceof BigInteger) {
return pow.toBigInteger();
}
if (isNumberable(left)) {
return pow.toBigInteger();
}
return pow;
}
// take care of left BigInteger
if (left instanceof BigInteger) {
if (isNumberable(right)) {
return pow.toBigInteger();
}
return pow;
}
// if any of these are float use Double
if (isFloatingPoint(left) || isFloatingPoint(right)) {
return narrowNumber(pow, Double.class);
}
if (isNumberable(left) && isNumberable(right)) {
Object o = narrowNumber(pow, Integer.class);
if (o instanceof BigDecimal) {
return narrowNumber(pow, Long.class);
}
return o;
}
if ( left instanceof String ) throw new Exception("");
}catch (Exception e) {
if (left instanceof String) {
if (right instanceof Integer) {
int r = (int) right;
if (r >= 0) {
StringBuffer buf = new StringBuffer();
if (r > 0) {
buf.append(left);
}
for (int i = 1; i < r; i++) {
buf.append(left);
}
return buf.toString();
} else {
StringBuilder sb = new StringBuilder(left.toString());
sb.reverse();
r = -r;
StringBuffer buf = new StringBuffer(sb);
for (int i = 1; i < r; i++) {
buf.append(sb);
}
return buf.toString();
}
}
}
if (isListOrArray(left) && right instanceof Integer) {
int n = (int) right;
if ( n == 0 ){
return Collections.EMPTY_LIST ;
}
List l = TypeUtility.from(left);
if ( n < 0 ){
l = reverseList(l);
n = -n;
}
while (--n > 0) {
l = SetOperations.join(l, left);
}
return l;
}
if ( left instanceof ScriptMethod ){
if ( isNumberable(right)){
long l = toLong(right);
return ((ScriptMethod) left).pow(l);
}
}
if (left instanceof Arithmetic) {
return ((Arithmetic) left).exp(right);
}
}
throw new ArithmeticException("Power can not be computed!");
}
/**
* Subtract the right value from the left.
* @param left first value
* @param right second value
* @return left - right.
*/
public Object subtract(Object left, Object right) {
try {
if (left == null && right == null) {
return controlNullNullOperands();
}
// if either are bigdecimal use that type --> highest precision
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
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)) {
double l = toDouble(left);
double r = toDouble(right);
return new Double(l - r);
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.subtract(r);
return narrowBigInteger(left, right, result);
}catch (Exception e){
// if set ?
if (left instanceof Set ) {
if ( right instanceof Collection ) {
return SetOperations.set_d((Set) left, (Collection) right );
}
ListSet l = new ListSet((Set)left);
l.remove(right);
return l;
}
// if list or array
if (isListOrArray(left)) {
if (isListOrArray(right)) {
return SetOperations.list_d(left, right);
} else {
ArrayList r = TypeUtility.from(left);
r.remove(right);
return r;
}
}
if ( left instanceof Map ){
return SetOperations.dict_subtract((Map)left,right);
}
if ( left instanceof Arithmetic){
return ((Arithmetic) left).sub(right);
}
if ( isTimeLike(left) ){
left = TypeUtility.castTime(left);
if ( isTimeLike( right ) ){
right = TypeUtility.castTime(right);
long diff = ((DateTime) left).getMillis() - ((DateTime) right).getMillis() ;
return diff;
}
if ( right instanceof Number ){
return ((DateTime) left).minus(((Number)right).longValue() );
}
if ( right instanceof Duration){
return ((DateTime) left).minus((Duration) right);
}
if ( right instanceof String ){
return ((DateTime) left).minus(DateIterator.parseDuration((String) right));
}
}
throw e;
}
}
/**
* Subtract the right value from the left, mutably: -= .
* @param left first value
* @param right second value
* @return left - right.
*/
public synchronized Object subtractMutable(Object left, Object right) {
try {
if (left == null && right == null) {
return controlNullNullOperands();
}
// if either are bigdecimal use that type --> highest precision
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
BigDecimal result = l.subtract(r, getMathContext());
left = narrowBigDecimal(left, right, result);
return left ;
}
// if either are floating point (double or float) use double
if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
double l = toDouble(left);
double r = toDouble(right);
left = l - r ;
return left;
}
Object oldLeft = null;
if ( isAtomicNum(left)){
oldLeft = left ;
}
// otherwise treat as integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.subtract(r);
left = narrowBigInteger(left, right, result);
if ( oldLeft == null) return left ;
if (oldLeft instanceof AtomicInteger) {
((AtomicInteger) oldLeft).set(((Number)left).intValue());
} else {
((AtomicLong) oldLeft).set(((Number)left).longValue());
}
return oldLeft ;
}catch (Exception e){
if ( left instanceof Collection ) {
if ( right instanceof Collection ) {
((Collection)left).removeAll((Collection)right);
} else if ( right != null && right.getClass().isArray()){
left = removeFromCollection((Collection)left, right );
}
else {
((Collection)left).remove(right);
}
return left;
}
if ( left instanceof Map ){
if ( right instanceof Map ){
left = removeFromMap((Map)left,(Map)right);
}else if ( right instanceof Collection ){
left = removeFromMap((Map)left,(Collection)right);
}else if ( right != null && right.getClass().isArray() ){
left = removeFromMap((Map)left, right);
}else{
((Map)left).remove(right);
}
return left;
}
if ( left != null && left.getClass().isArray() ){
if ( isListOrSetOrArray(right)){
left = SetOperations.list_d(left, right );
}else{
left = SetOperations.list_d(left, new Object[]{ right } );
}
left = TypeUtility.array(left);
return left;
}
if ( left instanceof Arithmetic){
left = ((Arithmetic) left).sub(right);
return left;
}
if ( isTimeLike(left) ){
left = TypeUtility.castTime(left);
if ( isTimeLike( right ) ){
right = TypeUtility.castTime(right);
left = ((DateTime) left).getMillis() - ((DateTime) right).getMillis() ;
return left ;
}
if ( right instanceof Number ){
left = ((DateTime) left).minus(((Number)right).longValue() );
return left;
}
if ( right instanceof Duration){
left = ((DateTime) left).minus((Duration) right);
return left;
}
if ( right instanceof String ){
left = ((DateTime) left).minus(DateIterator.parseDuration((String) right));
return left;
}
}
throw e;
}
}
/**
* Negates a value (unary minus for numbers).
* @param val the value to negate
* @return the negated value
* @since 2.1
*/
public Object negate(Object val) {
if (val instanceof Integer) {
int valueAsInt = ((Integer) val).intValue();
return Integer.valueOf(-valueAsInt);
} else if (val instanceof Double) {
double valueAsDouble = ((Double) val).doubleValue();
return new Double(-valueAsDouble);
} else if (val instanceof Long) {
long valueAsLong = -((Long) val).longValue();
return Long.valueOf(valueAsLong);
} else if (val instanceof BigDecimal) {
BigDecimal valueAsBigD = (BigDecimal) val;
return valueAsBigD.negate();
} else if (val instanceof BigInteger) {
BigInteger valueAsBigI = (BigInteger) val;
return valueAsBigI.negate();
} else if (val instanceof Float) {
float valueAsFloat = ((Float) val).floatValue();
return new Float(-valueAsFloat);
} else if (val instanceof Short) {
short valueAsShort = ((Short) val).shortValue();
return Short.valueOf((short) -valueAsShort);
} else if (val instanceof Byte) {
byte valueAsByte = ((Byte) val).byteValue();
return Byte.valueOf((byte) -valueAsByte);
} else if (val instanceof Boolean) {
return ((Boolean) val).booleanValue() ? Boolean.FALSE : Boolean.TRUE;
}
if ( val instanceof Arithmetic){
return ((Arithmetic) val).neg();
}
throw new ArithmeticException("Object negation:(" + val + ")");
}
/**
* Test if left regexp matches right.
*
* @param left first value
* @param right second value
* @return test result.
* @since 2.1
*/
public boolean matches(Object left, 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;
}
final String arg = left.toString();
if (right instanceof java.util.regex.Pattern) {
return ((java.util.regex.Pattern) right).matcher(arg).matches();
} else {
return arg.matches(right.toString());
}
}
/**
* Performs a bitwise and.
* @param left the left operand
* @param right the right operator
* @return left and right
* @since 2.1
*/
public Object bitwiseAnd(Object left, Object right) {
try {
long l = toLong(left);
long r = toLong(right);
return Long.valueOf(l & r);
}catch (Exception e){
if ( left instanceof Set && right instanceof Set){
return SetOperations.set_i((Set) left, (Set) right);
}
if ( left instanceof Map && right instanceof Map){
return SetOperations.dict_i((Map) left, (Map) right);
}
if ( areListOrArray(left,right)){
return SetOperations.list_i(left, right);
}
if ( left instanceof Logic ){
return ((Logic) left).and(right);
}
throw e;
}
}
/**
* Performs a bitwise or.
* @param left the left operand
* @param right the right operator
* @return left | right
* @since 2.1
*/
public Object bitwiseOr(Object left, Object right) {
try {
long l = toLong(left);
long r = toLong(right);
return Long.valueOf(l | r);
}catch (Exception e){
if ( left instanceof Set && right instanceof Set){
return SetOperations.set_u((Set) left, (Set) right);
}
if ( left instanceof Map && right instanceof Map){
return SetOperations.dict_u((Map) left, (Map) right);
}
if ( areListOrArray(left,right)){
return SetOperations.list_u(left, right);
}
if ( left instanceof Logic){
return ((Logic) left).or(right);
}
throw e;
}
}
/**
* Performs a bitwise xor.
* @param left the left operand
* @param right the right operator
* @return left right
* @since 2.1
*/
public Object bitwiseXor(Object left, Object right) {
try {
long l = toLong(left);
long r = toLong(right);
return Long.valueOf(l ^ r);
}catch (Exception e){
if ( left instanceof Set && right instanceof Set){
return SetOperations.set_sym_d((Set) left, (Set) right);
}
if ( left instanceof Map && right instanceof Map){
return SetOperations.dict_sym_d((Map) left, (Map) right);
}
if ( areListOrArray(left,right)){
return SetOperations.list_sym_d(left, right);
}
if ( left instanceof Logic){
return ((Logic) left).xor(right);
}
throw e;
}
}
/**
* Performs a bitwise complement.
* @param val the operand
* @return ~val
* @since 2.1
*/
public Object bitwiseComplement(Object val) {
try {
long l = toLong(val);
return Long.valueOf(~l);
}catch (Exception e){
if ( val instanceof Logic){
return ((Logic) val).complement();
}
throw e;
}
}
public static class NonComparableCollectionException extends ArithmeticException{
public NonComparableCollectionException(String message){
super(message);
}
}
/**
* 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
* @since 2.1
*/
public int compare(Object left, Object right, String operator) {
if (left != null && right != null) {
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
BigDecimal r = toBigDecimal(right);
return l.compareTo(r);
} else if (left instanceof BigInteger || right instanceof BigInteger) {
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
return l.compareTo(r);
} else if (isFloatingPoint(left) || isFloatingPoint(right)) {
double lhs = toDouble(left);
double rhs = toDouble(right);
if (Double.isNaN(lhs)) {
if (Double.isNaN(rhs)) {
return 0;
} else {
return -1;
}
} else if (Double.isNaN(rhs)) {
// lhs is not NaN
return +1;
} else if (lhs < rhs) {
return -1;
} else if (lhs > rhs) {
return +1;
} else {
return 0;
}
}else if ( left instanceof Character ){
return ((Character) left).compareTo( TypeUtility.castChar(right)) ;
}
else if (isNumberable(left) || isNumberable(right)) {
try {
long lhs = toLong(left);
long rhs = toLong(right);
if (lhs < rhs) {
return -1;
} else if (lhs > rhs) {
return +1;
} else {
return 0;
}
}catch (Exception e){
// what sort of stuff?
if ( left instanceof String || right instanceof String){
return toString(left).compareTo(toString(right));
}
}
} else if (left instanceof String || right instanceof String) {
return toString(left).compareTo(toString(right));
} else if ( left instanceof Set && right instanceof Set ){
Set l = TypeUtility.set(left);
Set r = TypeUtility.set(right);
SetOperations.SetRelation sr = SetOperations.set_relation(l,r);
switch (sr){
case SUBSET:
return -1;
case SUPERSET:
return 1 ;
case EQUAL:
return 0 ;
default:
throw new NonComparableCollectionException(l.toString() + "," +r.toString());
}
}else if ( left instanceof Map && right instanceof Map ){
Map l = (Map)left;
Map r = (Map)right;
SetOperations.SetRelation sr = SetOperations.dict_relation(l,r);
switch (sr){
case SUBSET:
return -1;
case SUPERSET:
return 1 ;
case EQUAL:
return 0 ;
default:
throw new NonComparableCollectionException(l.toString() + "," +r.toString());
}
}
else if ( areListOrSetOrArray(left,right) ){
HashMap l = SetOperations.multiset(left);
HashMap r = SetOperations.multiset(right);
SetOperations.SetRelation sr = SetOperations.mset_relation(l, r);
switch (sr){
case SUBSET:
return -1;
case SUPERSET:
return 1 ;
case EQUAL:
return 0 ;
default:
throw new NonComparableCollectionException(l.toString() + "," +r.toString());
}
}else if ( areTimeLike(left,right) ){
// both to dateTime and compare
DateTime l = TypeUtility.castTime(left);
DateTime r = TypeUtility.castTime(right);
return l.compareTo(r);
}
else if ("==".equals(operator)) {
return left.equals(right) ? 0 : -1;
}
else if (left instanceof Comparable<?>) {
@SuppressWarnings("unchecked") // OK because of instanceof check above
final Comparable<Object> comparable = (Comparable<Object>) left;
return comparable.compareTo(right);
} else if (right instanceof Comparable<?>) {
@SuppressWarnings("unchecked") // OK because of instanceof check above
final Comparable<Object> comparable = (Comparable<Object>) right;
return comparable.compareTo(left);
}
}
throw new ArithmeticException("Object comparison:(" + left + " " + operator + " " + right + ")");
}
/**
* Test if left and right are equal.
*
* @param left first value
* @param right second value
* @return test result.
*/
public boolean equals(Object left, Object right) {
if (left == right) {
return true;
} else if (left == null || right == null) {
return false;
} else if (left instanceof Boolean || right instanceof Boolean) {
return toBoolean(left) == toBoolean(right);
} else {
try{
return compare(left, right, "==") == 0;
}catch (NumberFormatException e){
/*
Here is problem -- throwing exception
while it should do equals operation on non nulls.
*/
return left.equals(right);
}catch (NonComparableCollectionException ncc){
/*
Important too, missed it completely earlier!
Checks if the inputs were collections
which can not be compared at all
* */
return false ;
}
}
}
/**
* Test if left < right.
*s
* @param left first value
* @param right second value
* @return test result.
*/
public boolean lessThan(Object left, Object right) {
if ((left == right) || (left == null) || (right == null)) {
return false;
} else {
try {
return compare(left, right, "<") < 0;
}catch (NonComparableCollectionException ne){
return false ;
}
}
}
/**
* Test if left > right.
*
* @param left first value
* @param right second value
* @return test result.
*/
public boolean greaterThan(Object left, Object right) {
if ((left == right) || left == null || right == null) {
return false;
} else {
try {
return compare(left, right, ">") > 0;
}catch (NonComparableCollectionException ne){
return false;
}
}
}
/**
* Test if left <= right.
*
* @param left first value
* @param right second value
* @return test result.
*/
public boolean lessThanOrEqual(Object left, Object right) {
if (left == right) {
return true;
} else if (left == null || right == null) {
return false;
} else {
try {
return compare(left, right, "<=") <= 0;
}catch (NonComparableCollectionException ne){
return false ;
}
}
}
/**
* Test if left >= right.
*
* @param left first value
* @param right second value
* @return test result.
*/
public boolean greaterThanOrEqual(Object left, Object right) {
if (left == right) {
return true;
} else if (left == null || right == null) {
return false;
} else {
try {
return compare(left, right, ">=") >= 0;
}catch (NonComparableCollectionException ne){
return false;
}
}
}
/**
* Coerce to a boolean (not a java.lang.Boolean).
*
* @param val Object to be coerced.
* @return The boolean coerced value, or false if none possible.
*/
public boolean toBoolean(Object val) {
if (val == null) {
controlNullOperand();
return false;
} else if (val instanceof Boolean) {
return ((Boolean) val).booleanValue();
} else if (val instanceof Number) {
double number = toDouble(val);
return !Double.isNaN(number) && number != 0.d;
} else if (val instanceof String) {
String strval = val.toString();
// This is bad idea, let's fix this:-
if ( "true".equalsIgnoreCase(strval) ){
return true;
}
if ( strval.isEmpty() || "false".equalsIgnoreCase(strval) ){
return false;
}
// TODO : what should we do? Warn?
throw new Error("The string '" + strval + "' is not a boolean!");
}
// TODO: is this a reasonable default?
return false;
}
/**
* Coerce to a long (not a java.lang.Long).
*
* @param val Object to be coerced.
* @return The long coerced value.
*/
public long toLong(Object val) {
if (val == null) {
controlNullOperand();
return 0L;
} else if (val instanceof Double) {
if (!Double.isNaN(((Double) val).doubleValue())) {
return 0;
} else {
return ((Double) val).longValue();
}
} else if (val instanceof Number) {
return ((Number) val).longValue();
} else if (val instanceof String) {
if ("".equals(val)) {
return 0;
} else {
// what if " <num> " comes?
String text = ((String)val).trim();
return Long.parseLong(text);
}
} else if (val instanceof Boolean) {
return ((Boolean) val).booleanValue() ? 1L : 0L;
} else if (val instanceof Character) {
return ((Character) val).charValue();
}
throw new ArithmeticException("Long coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* Get a BigInteger from the object passed.
* Null and empty string maps to zero.
* @param val the object to be coerced.
* @return a BigDecimal.
* @throws NullPointerException if val is null and mode is strict.
*/
public BigInteger toBigInteger(Object val) {
if (val == null) {
controlNullOperand();
return BigInteger.ZERO;
} else if (val instanceof BigInteger) {
return (BigInteger) val;
} else if (val instanceof Double) {
if (!Double.isNaN(((Double) val).doubleValue())) {
return new BigInteger(val.toString());
} else {
return BigInteger.ZERO;
}
} else if (val instanceof Number) {
try {
BigDecimal bd = new BigDecimal(val.toString());
// this is much better off
return bd.toBigIntegerExact();
}catch (ArithmeticException e){
throw new NumberFormatException(e.toString());
}
} else if (val instanceof String) {
String string = (String) val;
if ("".equals(string.trim())) {
return BigInteger.ZERO;
} else {
return new BigInteger(string);
}
} else if (val instanceof Character) {
int i = ((Character) val).charValue();
return BigInteger.valueOf(i);
}
throw new ArithmeticException("BigInteger coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* Get a BigDecimal from the object passed.
* Null and empty string maps to zero.
* @param val the object to be coerced.
* @return a BigDecimal.
* @throws NullPointerException if val is null and mode is strict.
*/
public BigDecimal toBigDecimal(Object val) {
if (val instanceof BigDecimal) {
return roundBigDecimal((BigDecimal) val);
} else if (val == null) {
controlNullOperand();
return BigDecimal.ZERO;
} else if (val instanceof String) {
String string = ((String) val).trim();
if ("".equals(string)) {
return BigDecimal.ZERO;
}
return roundBigDecimal(new BigDecimal(string, getMathContext()));
} else if (val instanceof Double) {
if (!Double.isNaN(((Double) val).doubleValue())) {
return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
} else {
return BigDecimal.ZERO;
}
} else if (val instanceof Number) {
return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
} else if (val instanceof Character) {
int i = ((Character) val).charValue();
return new BigDecimal(i);
}
throw new ArithmeticException("BigDecimal coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* Coerce to a double.
*
* @param val Object to be coerced.
* @return The double coerced value.
* @throws NullPointerException if val is null and mode is strict.
*/
public double toDouble(Object val) {
if (val == null) {
controlNullOperand();
return 0;
} else if (val instanceof Double) {
return ((Double) val).doubleValue();
} else 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));
} else if (val instanceof Boolean) {
return ((Boolean) val).booleanValue() ? 1. : 0.;
} else if (val instanceof String) {
String string = ((String) val).trim();
if ("".equals(string)) {
return Double.NaN;
} else {
// the spec seems to be iffy about this. Going to give it a wack anyway
return Double.parseDouble(string);
}
} else if (val instanceof Character) {
int i = ((Character) val).charValue();
return i;
}
throw new ArithmeticException("Double coercion: "
+ val.getClass().getName() + ":(" + val + ")");
}
/**
* Coerce to a string.
*
* @param val Object to be coerced.
* @return The String coerced value.
* @throws NullPointerException if val is null and mode is strict.
*/
public String toString(Object val) {
if (val == null) {
controlNullOperand();
return "";
} else if (val instanceof Double) {
Double dval = (Double) val;
if (Double.isNaN(dval.doubleValue())) {
return "";
} else {
return dval.toString();
}
} else {
return val.toString();
}
}
/**
* Given a Number, return back the value using the smallest type the result
* will fit into. 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.
*
* @param original the original number.
* @return a value of the smallest type the original number will fit into.
*/
public Number narrow(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 orginal source class
* @return true if attempt to narrow source to target is accepted
* @since 2.1
*/
protected boolean narrowAccept(Class<?> narrow, Class<?> source) {
return narrow == null || narrow.equals(source);
}
/**
* Given a Number, return back 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
* @since 2.1
*/
protected Number narrowNumber(Number original, Class<?> narrow) {
if (original == null) {
return original;
}
Number result = original;
if (original instanceof BigDecimal) {
BigDecimal bigd = (BigDecimal) original;
// if it's bigger than a double it can't be narrowed
if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0) {
return original;
} else {
try {
long l = bigd.longValueExact();
// coerce to int when possible (int being so often used in method parms)
if (narrowAccept(narrow, Integer.class)
&& l <= Integer.MAX_VALUE
&& l >= Integer.MIN_VALUE) {
return Integer.valueOf((int) l);
} else if (narrowAccept(narrow, Long.class)) {
return Long.valueOf(l);
}
} catch (ArithmeticException xa) {
// ignore, no exact value possible
}
}
}
if (original instanceof Double || original instanceof Float || original instanceof BigDecimal) {
double value = original.doubleValue();
if (narrowAccept(narrow, Float.class)
&& value <= Float.MAX_VALUE
&& value >= Float.MIN_VALUE) {
result = Float.valueOf(result.floatValue());
}
/* else it fits in a double only
Just check if it was a BigDecimal
Then return the double value, we narrowed it
*/
if ( original instanceof BigDecimal){
return value;
}
} else {
if (original instanceof BigInteger) {
BigInteger bigi = (BigInteger) original;
// if it's bigger than a Long it can't be narrowed
if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
|| bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
return original;
}
}
long value = original.longValue();
if (narrowAccept(narrow, Byte.class)
&& value <= Byte.MAX_VALUE
&& value >= Byte.MIN_VALUE) {
// it will fit in a byte
result = Byte.valueOf((byte) value);
} else if (narrowAccept(narrow, Short.class)
&& value <= Short.MAX_VALUE
&& value >= Short.MIN_VALUE) {
result = Short.valueOf((short) value);
} else if (narrowAccept(narrow, Integer.class)
&& value <= Integer.MAX_VALUE
&& value >= Integer.MIN_VALUE) {
result = Integer.valueOf((int) value);
}
// else it fits in a long
}
return result;
}
}