    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.jexl2;
    018
    019    import java.lang.reflect.Array;
    020    import java.lang.reflect.Field;
    021    import java.math.BigDecimal;
    022    import java.math.BigInteger;
    023    import java.math.MathContext;
    024
    025    /**
    026     * Perform arithmetic.
    027     * <p>
    028     * All arithmetic operators (+, - , *, /, %) follow the same rules regarding their arguments.
    029     * <ol>
    030     * <li>If both are null, result is 0</li>
    031     * <li>If either is a BigDecimal, coerce both to BigDecimal and and perform operation</li>
    032     * <li>If either is a floating point number, coerce both to Double and perform operation</li>
    033     * <li>If both are BigInteger, treat as BigInteger and perform operation</li>
    034     * <li>Else treat as BigInteger, perform operation and attempt to narrow result:
    035     * <ol>
    036     * <li>if both arguments can be narrowed to Integer, narrow result to Integer</li>
    037     * <li>if both arguments can be narrowed to Long, narrow result to Long</li>
    038     * <li>Else return result as BigInteger</li>
    039     * </ol>
    040     * </li>
    041     * </ol>
    042     * </p>
    043     * Note that the only exception throw by JexlArithmetic is ArithmeticException.
    044     * @since 2.0
    045     */
    046    public class JexlArithmetic {
    047        /** Double.MAX_VALUE as BigDecimal. */
    048        protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
    049        /** Double.MIN_VALUE as BigDecimal. */
    050        protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE);
    051        /** Long.MAX_VALUE as BigInteger. */
    052        protected static final BigInteger BIGI_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
    053        /** Long.MIN_VALUE as BigInteger. */
    054        protected static final BigInteger BIGI_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
    055        /**
    056         * Default BigDecimal scale.
    057         * @since 2.1
    058         */
    059        protected static final int BIGD_SCALE = -1;
    060        /** Whether this JexlArithmetic instance behaves in strict or lenient mode.
    061         * May be made final in a later version.
    062         */
    063        private volatile boolean strict;
    064        /**
    065         * The big decimal math context.
    066         * @since 2.1
    067         */
    068        protected final MathContext mathContext;
    069        /**
    070         * The big decimal scale.
    071         * @since 2.1
    072         */
    073        protected final int mathScale;
    074
    075        /**
    076         * Creates a JexlArithmetic.
    077         * @param lenient whether this arithmetic is lenient or strict
    078         */
    079        public JexlArithmetic(boolean lenient) {
    080            this(lenient, MathContext.DECIMAL128, BIGD_SCALE);
    081        }
    082
    083        /**
    084         * Creates a JexlArithmetic.
    085         * @param lenient whether this arithmetic is lenient or strict
    086         * @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
    087         * @param bigdScale the scale used for big decimals.
    088         * @since 2.1
    089         */
    090        public JexlArithmetic(boolean lenient, MathContext bigdContext, int bigdScale) {
    091            this.strict = !lenient;
    092            this.mathContext = bigdContext;
    093            this.mathScale = bigdScale;
    094        }
    095
    096
    097        /**
    098         * Sets whether this JexlArithmetic instance triggers errors during evaluation when
    099         * null is used as an operand.
    100         * <p>This method is <em>not</em> thread safe; it may be called as an optional step by the JexlEngine
    101         * in its initialization code before expression creation &amp; evaluation.</p>
    102         * @see JexlEngine#setLenient
    103         * @see JexlEngine#setSilent
    104         * @see JexlEngine#setDebug
    105         * @param flag true means no JexlException will occur, false allows them
    106         * @deprecated as of 2.1 - may be removed in a later release
    107         */
    108        @Deprecated
    109        void setLenient(boolean flag) {
    110            this.strict = !flag;
    111        }
    112
    113        /**
    114         * Checks whether this JexlArithmetic instance triggers errors during evaluation
    115         * when null is used as an operand.
    116         * @return true if lenient, false if strict
    117         */
    118        public boolean isLenient() {
    119            return !this.strict;
    120        }
    121
    122        /**
    123         * The MathContext instance used for +,-,/,*,% operations on big decimals.
    124         * @return the math context
    125         * @since 2.1
    126         */
    127        public MathContext getMathContext() {
    128            return mathContext;
    129        }
    130
    131        /**
    132         * The BigDecimal scale used for comparison and coercion operations.
    133         * @return the scale
    134         * @since 2.1
    135         */
    136        public int getMathScale() {
    137            return mathScale;
    138        }
    139
    140        /**
    141         * Ensure a big decimal is rounded by this arithmetic scale and rounding mode.
    142         * @param number the big decimal to round
    143         * @return the rounded big decimal
    144         * @since 2.1
    145         */
    146        public BigDecimal roundBigDecimal(final BigDecimal number) {
    147            int mscale = getMathScale();
    148            if (mscale >= 0) {
    149                return number.setScale(mscale, getMathContext().getRoundingMode());
    150            } else {
    151                return number;
    152            }
    153        }
    154
    155        /**
    156         * The result of +,/,-,*,% when both operands are null.
    157         * @return Integer(0) if lenient
    158         * @throws ArithmeticException if strict
    159         */
    160        protected Object controlNullNullOperands() {
    161            if (!isLenient()) {
    162                throw new ArithmeticException(JexlException.NULL_OPERAND);
    163            }
    164            return Integer.valueOf(0);
    165        }
    166
    167        /**
    168         * Throw a NPE if arithmetic is strict.
    169         * @throws ArithmeticException if strict
    170         */
    171        protected void controlNullOperand() {
    172            if (!isLenient()) {
    173                throw new ArithmeticException(JexlException.NULL_OPERAND);
    174            }
    175        }
    176
    177        /**
    178         * Test if either left or right are either a Float or Double.
    179         * @param left one object to test
    180         * @param right the other
    181         * @return the result of the test.
    182         */
    183        protected boolean isFloatingPointType(Object left, Object right) {
    184            return left instanceof Float || left instanceof Double || right instanceof Float || right instanceof Double;
    185        }
    186
    187        /**
    188         * Test if the passed value is a floating point number, i.e. a float, double
    189         * or string with ( "." | "E" | "e").
    190         *
    191         * @param val the object to be tested
    192         * @return true if it is, false otherwise.
    193         */
    194        protected boolean isFloatingPointNumber(Object val) {
    195            if (val instanceof Float || val instanceof Double) {
    196                return true;
    197            }
    198            if (val instanceof String) {
    199                String string = (String) val;
    200                return string.indexOf('.') != -1 || string.indexOf('e') != -1 || string.indexOf('E') != -1;
    201            }
    202            return false;
    203        }
    204
    205        /**
    206         * Is Object a floating point number.
    207         *
    208         * @param o Object to be analyzed.
    209         * @return true if it is a Float or a Double.
    210         */
    211        protected boolean isFloatingPoint(final Object o) {
    212            return o instanceof Float || o instanceof Double;
    213        }
    214
    215        /**
    216         * Is Object a whole number.
    217         *
    218         * @param o Object to be analyzed.
    219         * @return true if Integer, Long, Byte, Short or Character.
    220         */
    221        protected boolean isNumberable(final Object o) {
    222            return o instanceof Integer
    223                    || o instanceof Long
    224                    || o instanceof Byte
    225                    || o instanceof Short
    226                    || o instanceof Character;
    227        }
    228
    229        /**
    230         * Given a BigInteger, narrow it to an Integer or Long if it fits and the arguments
    231         * class allow it.
    232         * <p>
    233         * The rules are:
    234         * if either arguments is a BigInteger, no narrowing will occur
    235         * if either arguments is a Long, no narrowing to Integer will occur
    236         * </p>
    237         * @param lhs the left hand side operand that lead to the bigi result
    238         * @param rhs the right hand side operand that lead to the bigi result
    239         * @param bigi the BigInteger to narrow
    240         * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
    241         */
    242        protected Number narrowBigInteger(Object lhs, Object rhs, BigInteger bigi) {
    243            //coerce to long if possible
    244            if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
    245                    && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
    246                    && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
    247                // coerce to int if possible
    248                long l = bigi.longValue();
    249                // coerce to int when possible (int being so often used in method parms)
    250                if (!(lhs instanceof Long || rhs instanceof Long)
    251                        && l <= Integer.MAX_VALUE
    252                        && l >= Integer.MIN_VALUE) {
    253                    return Integer.valueOf((int) l);
    254                }
    255                return Long.valueOf(l);
    256            }
    257            return bigi;
    258        }
    259
    260        /**
    261         * Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits if
    262         * one of the arguments is a numberable.
    263         *
    264         * @param lhs the left hand side operand that lead to the bigd result
    265         * @param rhs the right hand side operand that lead to the bigd result
    266         * @param bigd the BigDecimal to narrow
    267         * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
    268         * @since 2.1
    269         */
    270        protected Number narrowBigDecimal(Object lhs, Object rhs, BigDecimal bigd) {
    271            if (isNumberable(lhs) || isNumberable(rhs)) {
    272                try {
    273                    long l = bigd.longValueExact();
    274                    // coerce to int when possible (int being so often used in method parms)
    275                    if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE) {
    276                        return Integer.valueOf((int) l);
    277                    } else {
    278                        return Long.valueOf(l);
    279                    }
    280                } catch (ArithmeticException xa) {
    281                    // ignore, no exact value possible
    282                }
    283            }
    284            return bigd;
    285        }
    286
    287        /**
    288         * Given an array of objects, attempt to type it more strictly.
    289         * <ul>
    290         * <li>If all objects are of the same type, the array returned will be an array of that same type</li>
    291         * <li>If all objects are Numbers, the array returned will be an array of Numbers</li>
    292         * <li>If all objects are convertible to a primitive type, the array returned will be an array
    293         * of the primitive type</li>
    294         * </ul>
    295         * @param untyped an untyped array
    296         * @return the original array if the attempt to strictly type the array fails, a typed array otherwise
    297         */
    298        protected Object narrowArrayType(Object[] untyped) {
    299            final int size = untyped.length;
    300            Class<?> commonClass = null;
    301            if (size > 0) {
    302                boolean isNumber = true;
    303                // for all children after first...
    304                for (int u = 0; u < size && !Object.class.equals(commonClass); ++u) {
    305                    if (untyped[u] != null) {
    306                        Class<?> eclass = untyped[u].getClass();
    307                        // base common class on first non-null entry
    308                        if (commonClass == null) {
    309                            commonClass = eclass;
    310                            isNumber &= Number.class.isAssignableFrom(commonClass);
    311                        } else if (!commonClass.equals(eclass)) {
    312                            // if both are numbers...
    313                            if (isNumber && Number.class.isAssignableFrom(eclass)) {
    314                                commonClass = Number.class;
    315                            } else {
    316                                // attempt to find valid superclass
    317                                do {
    318                                    eclass = eclass.getSuperclass();
    319                                    if (eclass == null) {
    320                                        commonClass = Object.class;
    321                                        break;
    322                                    }
    323                                } while (!commonClass.isAssignableFrom(eclass));
    324                            }
    325                        }
    326                    } else {
    327                        isNumber = false;
    328                    }
    329                }
    330                // convert array to the common class if not Object.class
    331                if (commonClass != null && !Object.class.equals(commonClass)) {
    332                    // if the commonClass has an equivalent primitive type, get it
    333                    if (isNumber) {
    334                        try {
    335                            final Field type = commonClass.getField("TYPE");
    336                            commonClass = (Class<?>) type.get(null);
    337                        } catch (Exception xany) {
    338                            // ignore
    339                        }
    340                    }
    341                    // allocate and fill up the typed array
    342                    Object typed = Array.newInstance(commonClass, size);
    343                    for (int i = 0; i < size; ++i) {
    344                        Array.set(typed, i, untyped[i]);
    345                    }
    346                    return typed;
    347                }
    348            }
    349            return untyped;
    350        }
    351
    352        /**
    353         * Replace all numbers in an arguments array with the smallest type that will fit.
    354         * @param args the argument array
    355         * @return true if some arguments were narrowed and args array is modified,
    356         *         false if no narrowing occured and args array has not been modified
    357         */
    358        protected boolean narrowArguments(Object[] args) {
    359            boolean narrowed = false;
    360            for (int a = 0; a < args.length; ++a) {
    361                Object arg = args[a];
    362                if (arg instanceof Number) {
    363                    Object narg = narrow((Number) arg);
    364                    if (narg != arg) {
    365                        narrowed = true;
    366                    }
    367                    args[a] = narg;
    368                }
    369            }
    370            return narrowed;
    371        }
    372
    373        /**
    374         * Add two values together.
    375         * <p>
    376         * If any numeric add fails on coercion to the appropriate type,
    377         * treat as Strings and do concatenation.
    378         * </p>
    379         * @param left first value
    380         * @param right second value
    381         * @return left + right.
    382         */
    383        public Object add(Object left, Object right) {
    384            if (left == null && right == null) {
    385                return controlNullNullOperands();
    386            }
    387
    388            try {
    389                // if either are floating point (double or float) use double
    390                if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
    391                    double l = toDouble(left);
    392                    double r = toDouble(right);
    393                    return new Double(l + r);
    394                }
    395
    396                // if either are bigdecimal use that type
    397                if (left instanceof BigDecimal || right instanceof BigDecimal) {
    398                    BigDecimal l = toBigDecimal(left);
    399                    BigDecimal r = toBigDecimal(right);
    400                    BigDecimal result = l.add(r, getMathContext());
    401                    return narrowBigDecimal(left, right, result);
    402                }
    403
    404                // otherwise treat as integers
    405                BigInteger l = toBigInteger(left);
    406                BigInteger r = toBigInteger(right);
    407                BigInteger result = l.add(r);
    408                return narrowBigInteger(left, right, result);
    409            } catch (java.lang.NumberFormatException nfe) {
    410                // Well, use strings!
    411                return toString(left).concat(toString(right));
    412            }
    413        }
    414
    415        /**
    416         * Divide the left value by the right.
    417         * @param left first value
    418         * @param right second value
    419         * @return left / right
    420         * @throws ArithmeticException if right == 0
    421         */
    422        public Object divide(Object left, Object right) {
    423            if (left == null && right == null) {
    424                return controlNullNullOperands();
    425            }
    426
    427            // if either are floating point (double or float) use double
    428            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
    429                double l = toDouble(left);
    430                double r = toDouble(right);
    431                if (r == 0.0) {
    432                    throw new ArithmeticException("/");
    433                }
    434                return new Double(l / r);
    435            }
    436
    437            // if either are bigdecimal use that type
    438            if (left instanceof BigDecimal || right instanceof BigDecimal) {
    439                BigDecimal l = toBigDecimal(left);
    440                BigDecimal r = toBigDecimal(right);
    441                if (BigDecimal.ZERO.equals(r)) {
    442                    throw new ArithmeticException("/");
    443                }
    444                BigDecimal result = l.divide(r, getMathContext());
    445                return narrowBigDecimal(left, right, result);
    446            }
    447
    448            // otherwise treat as integers
    449            BigInteger l = toBigInteger(left);
    450            BigInteger r = toBigInteger(right);
    451            if (BigInteger.ZERO.equals(r)) {
    452                throw new ArithmeticException("/");
    453            }
    454            BigInteger result = l.divide(r);
    455            return narrowBigInteger(left, right, result);
    456        }
    457
    458        /**
    459         * left value mod right.
    460         * @param left first value
    461         * @param right second value
    462         * @return left mod right
    463         * @throws ArithmeticException if right == 0.0
    464         */
    465        public Object mod(Object left, Object right) {
    466            if (left == null && right == null) {
    467                return controlNullNullOperands();
    468            }
    469
    470            // if either are floating point (double or float) use double
    471            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
    472                double l = toDouble(left);
    473                double r = toDouble(right);
    474                if (r == 0.0) {
    475                    throw new ArithmeticException("%");
    476                }
    477                return new Double(l % r);
    478            }
    479
    480            // if either are bigdecimal use that type
    481            if (left instanceof BigDecimal || right instanceof BigDecimal) {
    482                BigDecimal l = toBigDecimal(left);
    483                BigDecimal r = toBigDecimal(right);
    484                if (BigDecimal.ZERO.equals(r)) {
    485                    throw new ArithmeticException("%");
    486                }
    487                BigDecimal remainder = l.remainder(r, getMathContext());
    488                return narrowBigDecimal(left, right, remainder);
    489            }
    490
    491            // otherwise treat as integers
    492            BigInteger l = toBigInteger(left);
    493            BigInteger r = toBigInteger(right);
    494            BigInteger result = l.mod(r);
    495            if (BigInteger.ZERO.equals(r)) {
    496                throw new ArithmeticException("%");
    497            }
    498            return narrowBigInteger(left, right, result);
    499        }
    500
    501        /**
    502         * Multiply the left value by the right.
    503         * @param left first value
    504         * @param right second value
    505         * @return left * right.
    506         */
    507        public Object multiply(Object left, Object right) {
    508            if (left == null && right == null) {
    509                return controlNullNullOperands();
    510            }
    511
    512            // if either are floating point (double or float) use double
    513            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
    514                double l = toDouble(left);
    515                double r = toDouble(right);
    516                return new Double(l * r);
    517            }
    518
    519            // if either are bigdecimal use that type
    520            if (left instanceof BigDecimal || right instanceof BigDecimal) {
    521                BigDecimal l = toBigDecimal(left);
    522                BigDecimal r = toBigDecimal(right);
    523                BigDecimal result = l.multiply(r, getMathContext());
    524                return narrowBigDecimal(left, right, result);
    525            }
    526
    527            // otherwise treat as integers
    528            BigInteger l = toBigInteger(left);
    529            BigInteger r = toBigInteger(right);
    530            BigInteger result = l.multiply(r);
    531            return narrowBigInteger(left, right, result);
    532        }
    533
    534        /**
    535         * Subtract the right value from the left.
    536         * @param left first value
    537         * @param right second value
    538         * @return left - right.
    539         */
    540        public Object subtract(Object left, Object right) {
    541            if (left == null && right == null) {
    542                return controlNullNullOperands();
    543            }
    544
    545            // if either are floating point (double or float) use double
    546            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
    547                double l = toDouble(left);
    548                double r = toDouble(right);
    549                return new Double(l - r);
    550            }
    551
    552            // if either are bigdecimal use that type
    553            if (left instanceof BigDecimal || right instanceof BigDecimal) {
    554                BigDecimal l = toBigDecimal(left);
    555                BigDecimal r = toBigDecimal(right);
    556                BigDecimal result = l.subtract(r, getMathContext());
    557                return narrowBigDecimal(left, right, result);
    558            }
    559
    560            // otherwise treat as integers
    561            BigInteger l = toBigInteger(left);
    562            BigInteger r = toBigInteger(right);
    563            BigInteger result = l.subtract(r);
    564            return narrowBigInteger(left, right, result);
    565        }
    566
    567        /**
    568         * Negates a value (unary minus for numbers).
    569         * @param val the value to negate
    570         * @return the negated value
    571         * @since 2.1
    572         */
    573        public Object negate(Object val) {
    574            if (val instanceof Integer) {
    575                int valueAsInt = ((Integer) val).intValue();
    576                return Integer.valueOf(-valueAsInt);
    577            } else if (val instanceof Double) {
    578                double valueAsDouble = ((Double) val).doubleValue();
    579                return new Double(-valueAsDouble);
    580            } else if (val instanceof Long) {
    581                long valueAsLong = -((Long) val).longValue();
    582                return Long.valueOf(valueAsLong);
    583            } else if (val instanceof BigDecimal) {
    584                BigDecimal valueAsBigD = (BigDecimal) val;
    585                return valueAsBigD.negate();
    586            } else if (val instanceof BigInteger) {
    587                BigInteger valueAsBigI = (BigInteger) val;
    588                return valueAsBigI.negate();
    589            } else if (val instanceof Float) {
    590                float valueAsFloat = ((Float) val).floatValue();
    591                return new Float(-valueAsFloat);
    592            } else if (val instanceof Short) {
    593                short valueAsShort = ((Short) val).shortValue();
    594                return Short.valueOf((short) -valueAsShort);
    595            } else if (val instanceof Byte) {
    596                byte valueAsByte = ((Byte) val).byteValue();
    597                return Byte.valueOf((byte) -valueAsByte);
    598            } else if (val instanceof Boolean) {
    599                return ((Boolean) val).booleanValue() ? Boolean.FALSE : Boolean.TRUE;
    600            }
    601            throw new ArithmeticException("Object negation:(" + val + ")");
    602        }
    603
    604        /**
    605         * Test if left regexp matches right.
    606         *
    607         * @param left first value
    608         * @param right second value
    609         * @return test result.
    610         * @since 2.1
    611         */
    612        public boolean matches(Object left, Object right) {
    613            if (left == null && right == null) {
    614                //if both are null L == R
    615                return true;
    616            }
    617            if (left == null || right == null) {
    618                // we know both aren't null, therefore L != R
    619                return false;
    620            }
    621            final String arg = left.toString();
    622            if (right instanceof java.util.regex.Pattern) {
    623                return ((java.util.regex.Pattern) right).matcher(arg).matches();
    624            } else {
    625                return arg.matches(right.toString());
    626            }
    627        }
    628
    629        /**
    630         * Performs a bitwise and.
    631         * @param left the left operand
    632         * @param right the right operator
    633         * @return left & right
    634         * @since 2.1
    635         */
    636        public Object bitwiseAnd(Object left, Object right) {
    637            long l = toLong(left);
    638            long r = toLong(right);
    639            return Long.valueOf(l & r);
    640        }
    641
    642        /**
    643         * Performs a bitwise or.
    644         * @param left the left operand
    645         * @param right the right operator
    646         * @return left | right
    647         * @since 2.1
    648         */
    649        public Object bitwiseOr(Object left, Object right) {
    650            long l = toLong(left);
    651            long r = toLong(right);
    652            return Long.valueOf(l | r);
    653        }
    654
    655        /**
    656         * Performs a bitwise xor.
    657         * @param left the left operand
    658         * @param right the right operator
    659         * @return left  right
    660         * @since 2.1
    661         */
    662        public Object bitwiseXor(Object left, Object right) {
    663            long l = toLong(left);
    664            long r = toLong(right);
    665            return Long.valueOf(l ^ r);
    666        }
    667
    668        /**
    669         * Performs a bitwise complement.
    670         * @param val the operand
    671         * @return ~val
    672         * @since 2.1
    673         */
    674        public Object bitwiseComplement(Object val) {
    675            long l = toLong(val);
    676            return Long.valueOf(~l);
    677        }
    678
    679        /**
    680         * Performs a comparison.
    681         * @param left the left operand
    682         * @param right the right operator
    683         * @param operator the operator
    684         * @return -1 if left  &lt; right; +1 if left &gt > right; 0 if left == right
    685         * @throws ArithmeticException if either left or right is null
    686         * @since 2.1
    687         */
    688        protected int compare(Object left, Object right, String operator) {
    689            if (left != null && right != null) {
    690                if (left instanceof BigDecimal || right instanceof BigDecimal) {
    691                    BigDecimal l = toBigDecimal(left);
    692                    BigDecimal r = toBigDecimal(right);
    693                    return l.compareTo(r);
    694                } else if (left instanceof BigInteger || right instanceof BigInteger) {
    695                    BigInteger l = toBigInteger(left);
    696                    BigInteger r = toBigInteger(right);
    697                    return l.compareTo(r);
    698                } else if (isFloatingPoint(left) || isFloatingPoint(right)) {
    699                    double lhs = toDouble(left);
    700                    double rhs = toDouble(right);
    701                    if (Double.isNaN(lhs)) {
    702                        if (Double.isNaN(rhs)) {
    703                            return 0;
    704                        } else {
    705                            return -1;
    706                        }
    707                    } else if (Double.isNaN(rhs)) {
    708                        // lhs is not NaN
    709                        return +1;
    710                    } else if (lhs < rhs) {
    711                        return -1;
    712                    } else if (lhs > rhs) {
    713                        return +1;
    714                    } else {
    715                        return 0;
    716                    }
    717                } else if (isNumberable(left) || isNumberable(right)) {
    718                    long lhs = toLong(left);
    719                    long rhs = toLong(right);
    720                    if (lhs < rhs) {
    721                        return -1;
    722                    } else if (lhs > rhs) {
    723                        return +1;
    724                    } else {
    725                        return 0;
    726                    }
    727                } else if (left instanceof String || right instanceof String) {
    728                    return toString(left).compareTo(toString(right));
    729                } else if ("==".equals(operator)) {
    730                    return left.equals(right) ? 0 : -1;
    731                } else if (left instanceof Comparable<?>) {
    732                    @SuppressWarnings("unchecked") // OK because of instanceof check above
    733                    final Comparable<Object> comparable = (Comparable<Object>) left;
    734                    return comparable.compareTo(right);
    735                } else if (right instanceof Comparable<?>) {
    736                    @SuppressWarnings("unchecked") // OK because of instanceof check above
    737                    final Comparable<Object> comparable = (Comparable<Object>) right;
    738                    return comparable.compareTo(left);
    739                }
    740            }
    741            throw new ArithmeticException("Object comparison:(" + left + " " + operator + " " + right + ")");
    742        }
    743
    744        /**
    745         * Test if left and right are equal.
    746         *
    747         * @param left first value
    748         * @param right second value
    749         * @return test result.
    750         */
    751        public boolean equals(Object left, Object right) {
    752            if (left == right) {
    753                return true;
    754            } else if (left == null || right == null) {
    755                return false;
    756            } else if (left instanceof Boolean || right instanceof Boolean) {
    757                return toBoolean(left) == toBoolean(right);
    758            } else {
    759                return compare(left, right, "==") == 0;
    760            }
    761        }
    762
    763        /**
    764         * Test if left < right.
    765         *
    766         * @param left first value
    767         * @param right second value
    768         * @return test result.
    769         */
    770        public boolean lessThan(Object left, Object right) {
    771            if ((left == right) || (left == null) || (right == null)) {
    772                return false;
    773            } else {
    774                return compare(left, right, "<") < 0;
    775            }
    776
    777        }
    778
    779        /**
    780         * Test if left > right.
    781         *
    782         * @param left first value
    783         * @param right second value
    784         * @return test result.
    785         */
    786        public boolean greaterThan(Object left, Object right) {
    787            if ((left == right) || left == null || right == null) {
    788                return false;
    789            } else {
    790                return compare(left, right, ">") > 0;
    791            }
    792        }
    793
    794        /**
    795         * Test if left <= right.
    796         *
    797         * @param left first value
    798         * @param right second value
    799         * @return test result.
    800         */
    801        public boolean lessThanOrEqual(Object left, Object right) {
    802            if (left == right) {
    803                return true;
    804            } else if (left == null || right == null) {
    805                return false;
    806            } else {
    807                return compare(left, right, "<=") <= 0;
    808            }
    809        }
    810
    811        /**
    812         * Test if left >= right.
    813         *
    814         * @param left first value
    815         * @param right second value
    816         * @return test result.
    817         */
    818        public boolean greaterThanOrEqual(Object left, Object right) {
    819            if (left == right) {
    820                return true;
    821            } else if (left == null || right == null) {
    822                return false;
    823            } else {
    824                return compare(left, right, ">=") >= 0;
    825            }
    826        }
    827
    828        /**
    829         * Coerce to a boolean (not a java.lang.Boolean).
    830         *
    831         * @param val Object to be coerced.
    832         * @return The boolean coerced value, or false if none possible.
    833         */
    834        public boolean toBoolean(Object val) {
    835            if (val == null) {
    836                controlNullOperand();
    837                return false;
    838            } else if (val instanceof Boolean) {
    839                return ((Boolean) val).booleanValue();
    840            } else if (val instanceof Number) {
    841                double number = toDouble(val);
    842                return !Double.isNaN(number) && number != 0.d;
    843            } else if (val instanceof String) {
    844                String strval = val.toString();
    845                return strval.length() > 0 && !"false".equals(strval);
    846            }
    847            // TODO: is this a reasonable default?
    848            return false;
    849        }
    850
    851        /**
    852         * Coerce to a int.
    853         *
    854         * @param val Object to be coerced.
    855         * @return The int coerced value.
    856         */
    857        public int toInteger(Object val) {
    858            if (val == null) {
    859                controlNullOperand();
    860                return 0;
    861            } else if (val instanceof Double) {
    862                if (!Double.isNaN(((Double) val).doubleValue())) {
    863                    return 0;
    864                } else {
    865                    return ((Double) val).intValue();
    866                }
    867            } else if (val instanceof Number) {
    868                return ((Number) val).intValue();
    869            } else if (val instanceof String) {
    870                if ("".equals(val)) {
    871                    return 0;
    872                }
    873                return Integer.parseInt((String) val);
    874            } else if (val instanceof Boolean) {
    875                return ((Boolean) val).booleanValue() ? 1 : 0;
    876            } else if (val instanceof Character) {
    877                return ((Character) val).charValue();
    878            }
    879
    880            throw new ArithmeticException("Integer coercion: "
    881                    + val.getClass().getName() + ":(" + val + ")");
    882        }
    883
    884        /**
    885         * Coerce to a long (not a java.lang.Long).
    886         *
    887         * @param val Object to be coerced.
    888         * @return The long coerced value.
    889         */
    890        public long toLong(Object val) {
    891            if (val == null) {
    892                controlNullOperand();
    893                return 0L;
    894            } else if (val instanceof Double) {
    895                if (!Double.isNaN(((Double) val).doubleValue())) {
    896                    return 0;
    897                } else {
    898                    return ((Double) val).longValue();
    899                }
    900            } else if (val instanceof Number) {
    901                return ((Number) val).longValue();
    902            } else if (val instanceof String) {
    903                if ("".equals(val)) {
    904                    return 0;
    905                } else {
    906                    return Long.parseLong((String) val);
    907                }
    908            } else if (val instanceof Boolean) {
    909                return ((Boolean) val).booleanValue() ? 1L : 0L;
    910            } else if (val instanceof Character) {
    911                return ((Character) val).charValue();
    912            }
    913
    914            throw new ArithmeticException("Long coercion: "
    915                    + val.getClass().getName() + ":(" + val + ")");
    916        }
    917
    918        /**
    919         * Get a BigInteger from the object passed.
    920         * Null and empty string maps to zero.
    921         * @param val the object to be coerced.
    922         * @return a BigDecimal.
    923         * @throws NullPointerException if val is null and mode is strict.
    924         */
    925        public BigInteger toBigInteger(Object val) {
    926            if (val == null) {
    927                controlNullOperand();
    928                return BigInteger.ZERO;
    929            } else if (val instanceof BigInteger) {
    930                return (BigInteger) val;
    931            } else if (val instanceof Double) {
    932                if (!Double.isNaN(((Double) val).doubleValue())) {
    933                    return new BigInteger(val.toString());
    934                } else {
    935                    return BigInteger.ZERO;
    936                }
    937            } else if (val instanceof Number) {
    938                return new BigInteger(val.toString());
    939            } else if (val instanceof String) {
    940                String string = (String) val;
    941                if ("".equals(string.trim())) {
    942                    return BigInteger.ZERO;
    943                } else {
    944                    return new BigInteger(string);
    945                }
    946            } else if (val instanceof Character) {
    947                int i = ((Character) val).charValue();
    948                return BigInteger.valueOf(i);
    949            }
    950
    951            throw new ArithmeticException("BigInteger coercion: "
    952                    + val.getClass().getName() + ":(" + val + ")");
    953        }
    954
    955        /**
    956         * Get a BigDecimal from the object passed.
    957         * Null and empty string maps to zero.
    958         * @param val the object to be coerced.
    959         * @return a BigDecimal.
    960         * @throws NullPointerException if val is null and mode is strict.
    961         */
    962        public BigDecimal toBigDecimal(Object val) {
    963            if (val instanceof BigDecimal) {
    964                return roundBigDecimal((BigDecimal) val);
    965            } else if (val == null) {
    966                controlNullOperand();
    967                return BigDecimal.ZERO;
    968            } else if (val instanceof String) {
    969                String string = ((String) val).trim();
    970                if ("".equals(string)) {
    971                    return BigDecimal.ZERO;
    972                }
    973                return roundBigDecimal(new BigDecimal(string, getMathContext()));
    974            } else if (val instanceof Double) {
    975                if (!Double.isNaN(((Double) val).doubleValue())) {
    976                    return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
    977                } else {
    978                    return BigDecimal.ZERO;
    979                }
    980            } else if (val instanceof Number) {
    981                return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
    982            } else if (val instanceof Character) {
    983                int i = ((Character) val).charValue();
    984                return new BigDecimal(i);
    985            }
    986
    987            throw new ArithmeticException("BigDecimal coercion: "
    988                    + val.getClass().getName() + ":(" + val + ")");
    989        }
    990
    991        /**
    992         * Coerce to a double.
    993         *
    994         * @param val Object to be coerced.
    995         * @return The double coerced value.
    996         * @throws NullPointerException if val is null and mode is strict.
    997         */
    998        public double toDouble(Object val) {
    999            if (val == null) {
    1000                controlNullOperand();
    1001                return 0;
    1002            } else if (val instanceof Double) {
    1003                return ((Double) val).doubleValue();
    1004            } else if (val instanceof Number) {
    1005                //The below construct is used rather than ((Number)val).doubleValue() to ensure
    1006                //equality between comparing new Double( 6.4 / 3 ) and the jexl expression of 6.4 / 3
    1007                return Double.parseDouble(String.valueOf(val));
    1008            } else if (val instanceof Boolean) {
    1009                return ((Boolean) val).booleanValue() ? 1. : 0.;
    1010            } else if (val instanceof String) {
    1011                String string = ((String) val).trim();
    1012                if ("".equals(string)) {
    1013                    return Double.NaN;
    1014                } else {
    1015                    // the spec seems to be iffy about this.  Going to give it a wack anyway
    1016                    return Double.parseDouble(string);
    1017                }
    1018            } else if (val instanceof Character) {
    1019                int i = ((Character) val).charValue();
    1020                return i;
    1021            }
    1022
    1023            throw new ArithmeticException("Double coercion: "
    1024                    + val.getClass().getName() + ":(" + val + ")");
    1025        }
    1026
    1027        /**
    1028         * Coerce to a string.
    1029         *
    1030         * @param val Object to be coerced.
    1031         * @return The String coerced value.
    1032         * @throws NullPointerException if val is null and mode is strict.
    1033         */
    1034        public String toString(Object val) {
    1035            if (val == null) {
    1036                controlNullOperand();
    1037                return "";
    1038            } else if (val instanceof Double) {
    1039                Double dval = (Double) val;
    1040                if (Double.isNaN(dval.doubleValue())) {
    1041                    return "";
    1042                } else {
    1043                    return dval.toString();
    1044                }
    1045            } else {
    1046                return val.toString();
    1047            }
    1048        }
    1049
    1050        /**
    1051         * Given a Number, return back the value using the smallest type the result
    1052         * will fit into. This works hand in hand with parameter 'widening' in java
    1053         * method calls, e.g. a call to substring(int,int) with an int and a long
    1054         * will fail, but a call to substring(int,int) with an int and a short will
    1055         * succeed.
    1056         *
    1057         * @param original the original number.
    1058         * @return a value of the smallest type the original number will fit into.
    1059         */
    1060        public Number narrow(Number original) {
    1061            return narrowNumber(original, null);
    1062        }
    1063
    1064        /**
    1065         * Whether we consider the narrow class as a potential candidate for narrowing the source.
    1066         * @param narrow the target narrow class
    1067         * @param source the orginal source class
    1068         * @return true if attempt to narrow source to target is accepted
    1069         * @since 2.1
    1070         */
    1071        protected boolean narrowAccept(Class<?> narrow, Class<?> source) {
    1072            return narrow == null || narrow.equals(source);
    1073        }
    1074
    1075        /**
    1076         * Given a Number, return back the value attempting to narrow it to a target class.
    1077         * @param original the original number
    1078         * @param narrow the attempted target class
    1079         * @return  the narrowed number or the source if no narrowing was possible
    1080         * @since 2.1
    1081         */
    1082        protected Number narrowNumber(Number original, Class<?> narrow) {
    1083            if (original == null) {
    1084                return original;
    1085            }
    1086            Number result = original;
    1087            if (original instanceof BigDecimal) {
    1088                BigDecimal bigd = (BigDecimal) original;
    1089                // if it's bigger than a double it can't be narrowed
    1090                if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0) {
    1091                    return original;
    1092                } else {
    1093                    try {
    1094                        long l = bigd.longValueExact();
    1095                        // coerce to int when possible (int being so often used in method parms)
    1096                        if (narrowAccept(narrow, Integer.class)
    1097                                && l <= Integer.MAX_VALUE
    1098                                && l >= Integer.MIN_VALUE) {
    1099                            return Integer.valueOf((int) l);
    1100                        } else if (narrowAccept(narrow, Long.class)) {
    1101                            return Long.valueOf(l);
    1102                        }
    1103                    } catch (ArithmeticException xa) {
    1104                        // ignore, no exact value possible
    1105                    }
    1106                }
    1107            }
    1108            if (original instanceof Double || original instanceof Float || original instanceof BigDecimal) {
    1109                double value = original.doubleValue();
    1110                if (narrowAccept(narrow, Float.class)
    1111                        && value <= Float.MAX_VALUE
    1112                        && value >= Float.MIN_VALUE) {
    1113                    result = Float.valueOf(result.floatValue());
    1114                }
    1115                // else it fits in a double only
    1116            } else {
    1117                if (original instanceof BigInteger) {
    1118                    BigInteger bigi = (BigInteger) original;
    1119                    // if it's bigger than a Long it can't be narrowed
    1120                    if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
    1121                            || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
    1122                        return original;
    1123                    }
    1124                }
    1125                long value = original.longValue();
    1126                if (narrowAccept(narrow, Byte.class)
    1127                        && value <= Byte.MAX_VALUE
    1128                        && value >= Byte.MIN_VALUE) {
    1129                    // it will fit in a byte
    1130                    result = Byte.valueOf((byte) value);
    1131                } else if (narrowAccept(narrow, Short.class)
    1132                        && value <= Short.MAX_VALUE
    1133                        && value >= Short.MIN_VALUE) {
    1134                    result = Short.valueOf((short) value);
    1135                } else if (narrowAccept(narrow, Integer.class)
    1136                        && value <= Integer.MAX_VALUE
    1137                        && value >= Integer.MIN_VALUE) {
    1138                    result = Integer.valueOf((int) value);
    1139                }
    1140                // else it fits in a long
    1141            }
    1142            return result;
    1143        }
    1144    }