/** * 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; } }