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    
024    /**
025     * Perform arithmetic.
026     * <p>
027     * All arithmetic operators (+, - , *, /, %) follow the same rules regarding their arguments.
028     * <ol>
029     * <li>If both are null, result is 0</li>
030     * <li>If either is a floating point number, coerce both to Double and perform operation</li>
031     * <li>If both are BigInteger, treat as BigInteger and perform operation</li>
032     * <li>If either is a BigDecimal, coerce both to BigDecimal and and perform operation</li>
033     * <li>Else treat as BigInteger, perform operation and attempt to narrow result:
034     * <ol>
035     * <li>if both arguments can be narrowed to Integer, narrow result to Integer</li>
036     * <li>if both arguments can be narrowed to Long, narrow result to Long</li>
037     * <li>Else return result as BigInteger</li>
038     * </ol>
039     * </li>
040     * </ol>
041     * </p>
042     * @since 2.0
043     */
044    public class JexlArithmetic {
045        /** Double.MAX_VALUE as BigDecimal. */
046        protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
047        /** Double.MIN_VALUE as BigDecimal. */
048        protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE);
049        /** Long.MAX_VALUE as BigInteger. */
050        protected static final BigInteger BIGI_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
051        /** Long.MIN_VALUE as BigInteger. */
052        protected static final BigInteger BIGI_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
053        /** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
054        private boolean strict;
055    
056        /**
057         * Creates a JexlArithmetic.
058         * @param lenient whether this arithmetic is lenient or strict
059         */
060        public JexlArithmetic(boolean lenient) {
061            this.strict = !lenient;
062        }
063    
064        /**
065         * Sets whether this JexlArithmetic instance triggers errors during evaluation when
066         * null is used as an operand.
067         * <p>This method is <em>not</em> thread safe; it may be called as an optional step by the JexlEngine
068         * in its initialization code before expression creation &amp; evaluation.</p>
069         * @see JexlEngine#setSilent
070         * @see JexlEngine#setDebug
071         * @param lenient true means no JexlException will occur, false allows them
072         */
073        void setLenient(boolean lenient) {
074            this.strict = !lenient;
075        }
076    
077        /**
078         * Checks whether this JexlArithmetic instance triggers errors during evaluation
079         * when null is used as an operand.
080         * @return true if lenient, false if strict
081         */
082        public boolean isLenient() {
083            return !this.strict;
084        }
085    
086        /**
087         * The result of +,/,-,*,% when both operands are null.
088         * @return Integer(0) if lenient
089         * @throws NullPointerException if strict
090         */
091        protected Object controlNullNullOperands() {
092            if (strict) {
093                throw new NullPointerException(JexlException.NULL_OPERAND);
094            }
095            return Integer.valueOf(0);
096        }
097    
098        /**
099         * Throw a NPE if arithmetic is strict.
100         * @throws NullPointerException if strict
101         */
102        protected void controlNullOperand() {
103            if (strict) {
104                throw new NullPointerException(JexlException.NULL_OPERAND);
105            }
106        }
107    
108        /**
109         * Test if either left or right are either a Float or Double.
110         * @param left one object to test
111         * @param right the other
112         * @return the result of the test.
113         */
114        protected boolean isFloatingPointType(Object left, Object right) {
115            return left instanceof Float || left instanceof Double || right instanceof Float || right instanceof Double;
116        }
117    
118        /**
119         * Test if the passed value is a floating point number, i.e. a float, double
120         * or string with ( "." | "E" | "e").
121         *
122         * @param val the object to be tested
123         * @return true if it is, false otherwise.
124         */
125        protected boolean isFloatingPointNumber(Object val) {
126            if (val instanceof Float || val instanceof Double) {
127                return true;
128            }
129            if (val instanceof String) {
130                String string = (String) val;
131                return string.indexOf('.') != -1 || string.indexOf('e') != -1 || string.indexOf('E') != -1;
132            }
133            return false;
134        }
135    
136        /**
137         * Is Object a floating point number.
138         *
139         * @param o Object to be analyzed.
140         * @return true if it is a Float or a Double.
141         */
142        protected boolean isFloatingPoint(final Object o) {
143            return o instanceof Float || o instanceof Double;
144        }
145    
146        /**
147         * Is Object a whole number.
148         *
149         * @param o Object to be analyzed.
150         * @return true if Integer, Long, Byte, Short or Character.
151         */
152        protected boolean isNumberable(final Object o) {
153            return o instanceof Integer
154                || o instanceof Long
155                || o instanceof Byte
156                || o instanceof Short
157                || o instanceof Character;
158        }
159    
160        /**
161         * Given a BigInteger, narrow it to an Integer or Long if it fits and the arguments
162         * class allow it.
163         * <p>
164         * The rules are:
165         * if either arguments is a BigInteger, no narrowing will occur
166         * if either arguments is a Long, no narrowing to Integer will occur
167         * </p>
168         * @param lhs the left hand side operand that lead to the bigi result
169         * @param rhs the right hand side operand that lead to the bigi result
170         * @param bigi the BigInteger to narrow
171         * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
172         */
173        protected Number narrowBigInteger(Object lhs, Object rhs, BigInteger bigi) {
174            //coerce to long if possible
175            if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
176                && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
177                && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
178                // coerce to int if possible
179                long l = bigi.longValue();
180                // coerce to int when possible (int being so often used in method parms)
181                if (!(lhs instanceof Long || rhs instanceof Long)
182                    && l <= Integer.MAX_VALUE
183                    && l >= Integer.MIN_VALUE) {
184                    return Integer.valueOf((int) l);
185                }
186                return Long.valueOf(l);
187            }
188            return bigi;
189        }
190    
191        /**
192         * Given an array of objects, attempt to type it more strictly.
193         * <ul>
194         * <li>If all objects are of the same type, the array returned will be an array of that same type</li>
195         * <li>If all objects are Numbers, the array returned will be an array of Numbers</li>
196         * <li>If all objects are convertible to a primitive type, the array returned will be an array
197         * of the primitive type</li>
198         * </ul>
199         * @param untyped an untyped array
200         * @return the original array if the attempt to strictly type the array fails, a typed array otherwise
201         */
202        protected Object narrowArrayType(Object[] untyped) {
203            final int size = untyped.length;
204            Class<?> commonClass = null;
205            if (size > 0) {
206                // base common class on first entry
207                commonClass = untyped[0].getClass();
208                final boolean isNumber = Number.class.isAssignableFrom(commonClass);
209                // for all children after first...
210                for (int i = 1; i < size; i++) {
211                    Class<?> eclass = untyped[i].getClass();
212                    // detect same type for all elements in array
213                    if (!Object.class.equals(commonClass) && !commonClass.equals(eclass)) {
214                        // if both are numbers...
215                        if (isNumber && Number.class.isAssignableFrom(eclass)) {
216                            commonClass = Number.class;
217                        } else {
218                            commonClass = Object.class;
219                        }
220                    }
221                }
222                // convert array to the common class if not Object.class
223                if (!Object.class.equals(commonClass)) {
224                    // if the commonClass has an equivalent primitive type, get it
225                    if (isNumber) {
226                        try {
227                            Field TYPE = commonClass.getField("TYPE");
228                            commonClass = (Class<?>) TYPE.get(null);
229                        } catch (Exception xany) {
230                            // ignore
231                        }
232                    }
233                    // allocate and fill up the typed array
234                    Object typed = Array.newInstance(commonClass, size);
235                    for(int i = 0; i < size; ++i) {
236                        Array.set(typed, i, untyped[i]);
237                    }
238                    return typed;
239                }
240            }
241            return untyped;
242        }
243    
244        /**
245         * Replace all numbers in an arguments array with the smallest type that will fit.
246         * @param args the argument array
247         * @return true if some arguments were narrowed and args array is modified,
248         *         false if no narrowing occured and args array has not been modified
249         */
250        protected boolean narrowArguments(Object[] args) {
251            boolean narrowed = false;
252            for (int a = 0; a < args.length; ++a) {
253                Object arg = args[a];
254                if (arg instanceof Number) {
255                    Object narg = narrow((Number) arg);
256                    if (narg != arg) {
257                        narrowed = true;
258                    }
259                    args[a] = narg;
260                }
261            }
262            return narrowed;
263        }
264    
265        /**
266         * Add two values together.
267         * <p>
268         * If any numeric add fails on coercion to the appropriate type,
269         * treat as Strings and do concatenation.
270         * </p>
271         * @param left first value
272         * @param right second value
273         * @return left + right.
274         */
275        public Object add(Object left, Object right) {
276            if (left == null && right == null) {
277                return controlNullNullOperands();
278            }
279            
280            try {
281                // if either are floating point (double or float) use double
282                if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
283                    double l = toDouble(left);
284                    double r = toDouble(right);
285                    return new Double(l + r);
286                }
287            
288                // if both are bigintegers use that type
289                if (left instanceof BigInteger && right instanceof BigInteger) {
290                    BigInteger l = toBigInteger(left);
291                    BigInteger r = toBigInteger(right);
292                    return l.add(r);
293                }
294                
295                // if either are bigdecimal use that type 
296                if (left instanceof BigDecimal || right instanceof BigDecimal) {
297                    BigDecimal l = toBigDecimal(left);
298                    BigDecimal r = toBigDecimal(right);
299                    return l.add(r);
300                }
301                
302                // otherwise treat as integers
303                BigInteger l = toBigInteger(left);
304                BigInteger r = toBigInteger(right);
305                BigInteger result = l.add(r);
306                return narrowBigInteger(left, right, result);
307            } catch (java.lang.NumberFormatException nfe) {
308                // Well, use strings!
309                return toString(left).concat(toString(right));
310            }
311        }
312    
313        /**
314         * Divide the left value by the right.
315         * @param left first value
316         * @param right second value
317         * @return left / right
318         * @throws ArithmeticException if right == 0
319         */
320        public Object divide(Object left, Object right) {
321            if (left == null && right == null) {
322                return controlNullNullOperands();
323            }
324    
325            // if either are floating point (double or float) use double
326            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
327                double l = toDouble(left);
328                double r = toDouble(right);
329                if (r == 0.0) {
330                    throw new ArithmeticException("/");
331                }
332                return new Double(l / r);
333            }
334    
335            // if both are bigintegers use that type
336            if (left instanceof BigInteger && right instanceof BigInteger) {
337                BigInteger l = toBigInteger(left);
338                BigInteger r = toBigInteger(right);
339                return l.divide(r);
340            }
341    
342            // if either are bigdecimal use that type
343            if (left instanceof BigDecimal || right instanceof BigDecimal) {
344                BigDecimal l = toBigDecimal(left);
345                BigDecimal r = toBigDecimal(right);
346                BigDecimal d = l.divide(r);
347                return d;
348            }
349    
350            // otherwise treat as integers
351            BigInteger l = toBigInteger(left);
352            BigInteger r = toBigInteger(right);
353            BigInteger result = l.divide(r);
354            return narrowBigInteger(left, right, result);
355        }
356        
357        /**
358         * left value mod right.
359         * @param left first value
360         * @param right second value
361         * @return left mod right
362         * @throws ArithmeticException if right == 0.0
363         */
364        public Object mod(Object left, Object right) {
365            if (left == null && right == null) {
366                return controlNullNullOperands();
367            }
368    
369            // if either are floating point (double or float) use double
370            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
371                double l = toDouble(left);
372                double r = toDouble(right);
373                if (r == 0.0) {
374                    throw new ArithmeticException("%");
375                }
376                return new Double(l % r);
377            }
378    
379            // if both are bigintegers use that type
380            if (left instanceof BigInteger && right instanceof BigInteger) {
381                BigInteger l = toBigInteger(left);
382                BigInteger r = toBigInteger(right);
383                return l.mod(r);
384            }
385    
386            // if either are bigdecimal use that type 
387            if (left instanceof BigDecimal || right instanceof BigDecimal) {
388                BigDecimal l = toBigDecimal(left);
389                BigDecimal r = toBigDecimal(right);
390                BigDecimal remainder = l.remainder(r);
391                return remainder;
392            }
393    
394            // otherwise treat as integers
395            BigInteger l = toBigInteger(left);
396            BigInteger r = toBigInteger(right);
397            BigInteger result = l.mod(r);
398            return narrowBigInteger(left, right, result);
399        }
400        
401        /**
402         * Multiply the left value by the right.
403         * @param left first value
404         * @param right second value
405         * @return left * right.
406         */
407        public Object multiply(Object left, Object right) {
408            if (left == null && right == null) {
409                return controlNullNullOperands();
410            }
411    
412            // if either are floating point (double or float) use double
413            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
414                double l = toDouble(left);
415                double r = toDouble(right);
416                return new Double(l * r);
417            }
418            
419            // if both are bigintegers use that type
420            if (left instanceof BigInteger && right instanceof BigInteger) {
421                BigInteger l = toBigInteger(left);
422                BigInteger r = toBigInteger(right);
423                return l.multiply(r);
424            }
425            
426            // if either are bigdecimal use that type 
427            if (left instanceof BigDecimal || right instanceof BigDecimal) {
428                BigDecimal l = toBigDecimal(left);
429                BigDecimal r = toBigDecimal(right);
430                return l.multiply(r);
431            }
432    
433            // otherwise treat as integers
434            BigInteger l = toBigInteger(left);
435            BigInteger r = toBigInteger(right);
436            BigInteger result = l.multiply(r);
437            return narrowBigInteger(left, right, result);
438        }
439        
440        /**
441         * Subtract the right value from the left.
442         * @param left first value
443         * @param right second value
444         * @return left - right.
445         */
446        public Object subtract(Object left, Object right) {
447            if (left == null && right == null) {
448                return controlNullNullOperands();
449            }
450    
451            // if either are floating point (double or float) use double
452            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
453                double l = toDouble(left);
454                double r = toDouble(right);
455                return new Double(l - r);
456            }
457            
458            // if both are bigintegers use that type
459            if (left instanceof BigInteger && right instanceof BigInteger) {
460                BigInteger l = toBigInteger(left);
461                BigInteger r = toBigInteger(right);
462                return l.subtract(r);
463            }
464            
465            // if either are bigdecimal use that type 
466            if (left instanceof BigDecimal || right instanceof BigDecimal) {
467                BigDecimal l = toBigDecimal(left);
468                BigDecimal r = toBigDecimal(right);
469                return l.subtract(r);
470            }
471    
472            // otherwise treat as integers
473            BigInteger l = toBigInteger(left);
474            BigInteger r = toBigInteger(right);
475            BigInteger result = l.subtract(r);
476            return narrowBigInteger(left, right, result);
477        }
478    
479        /**
480         * Test if left regexp matches right.
481         *
482         * @param left first value
483         * @param right second value
484         * @return test result.
485         */
486        public boolean matches(Object left, Object right) {
487            if (left == null && right == null) {
488                //if both are null L == R
489                return true;
490            }
491            if (left == null || right == null) {
492                // we know both aren't null, therefore L != R
493                return false;
494            }
495            final String arg = left.toString();
496            if (right instanceof java.util.regex.Pattern) {
497                return ((java.util.regex.Pattern) right).matcher(arg).matches();
498            } else {
499                return arg.matches(right.toString());
500            }
501        }
502    
503        /**
504         * Test if left and right are equal.
505         *
506         * @param left first value
507         * @param right second value
508         * @return test result.
509         */
510        public boolean equals(Object left, Object right) {
511            if (left == null && right == null) {
512                /*
513                 * if both are null L == R
514                 */
515                return true;
516            } else if (left == null || right == null) {
517                /*
518                 * we know both aren't null, therefore L != R
519                 */
520                return false;
521            } else if (left.getClass().equals(right.getClass())) {
522                return left.equals(right);
523            } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
524                return toBigDecimal(left).compareTo(toBigDecimal(right)) == 0;
525            } else if (isFloatingPointType(left, right)) {
526                return toDouble(left) == toDouble(right);
527            } else if (left instanceof Number || right instanceof Number || left instanceof Character
528                || right instanceof Character) {
529                return toLong(left) == toLong(right);
530            } else if (left instanceof Boolean || right instanceof Boolean) {
531                return toBoolean(left) == toBoolean(right);
532            } else if (left instanceof java.lang.String || right instanceof String) {
533                return left.toString().equals(right.toString());
534            }
535    
536            return left.equals(right);
537        }
538    
539    
540        /**
541         * Test if left < right.
542         *
543         * @param left first value
544         * @param right second value
545         * @return test result.
546         */
547        public boolean lessThan(Object left, Object right) {
548            if ((left == right) || (left == null) || (right == null)) {
549                return false;
550            } else if (isFloatingPoint(left) || isFloatingPoint(right)) {
551                double leftDouble = toDouble(left);
552                double rightDouble = toDouble(right);
553                return leftDouble < rightDouble;
554            } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
555                BigDecimal l  = toBigDecimal(left);
556                BigDecimal r  = toBigDecimal(right);
557                return l.compareTo(r) < 0;
558            } else if (isNumberable(left) || isNumberable(right)) {
559                long leftLong = toLong(left);
560                long rightLong = toLong(right);
561                return leftLong < rightLong;
562            } else if (left instanceof String || right instanceof String) {
563                String leftString = left.toString();
564                String rightString = right.toString();
565                return leftString.compareTo(rightString) < 0;
566            } else if (left instanceof Comparable<?>) {
567                @SuppressWarnings("unchecked") // OK because of instanceof check above
568                final Comparable<Object> comparable = (Comparable<Object>) left;
569                return comparable.compareTo(right) < 0;
570            } else if (right instanceof Comparable<?>) {
571                @SuppressWarnings("unchecked") // OK because of instanceof check above
572                final Comparable<Object> comparable = (Comparable<Object>) right;
573                return comparable.compareTo(left) > 0;
574            }
575    
576            throw new IllegalArgumentException("Invalid comparison : comparing cardinality for left: " + left
577                + " and right: " + right);
578    
579        }
580    
581        /**
582         * Test if left > right.
583         *
584         * @param left first value
585         * @param right second value
586         * @return test result.
587         */
588        public boolean greaterThan(Object left, Object right) {
589            if (left == null || right == null) {
590                return false;
591            }
592            return !equals(left, right) && !lessThan(left, right);
593        }
594    
595        /**
596         * Test if left <= right.
597         *
598         * @param left first value
599         * @param right second value
600         * @return test result.
601         */
602        public boolean lessThanOrEqual(Object left, Object right) {
603            return equals(left, right) || lessThan(left, right);
604        }
605    
606        /**
607         * Test if left >= right.
608         *
609         * @param left first value
610         * @param right second value
611         * @return test result.
612         */
613        public boolean greaterThanOrEqual(Object left, Object right) {
614            return equals(left, right) || greaterThan(left, right);
615        }
616        
617        /**
618         * Coerce to a boolean (not a java.lang.Boolean).
619         *
620         * @param val Object to be coerced.
621         * @return The boolean coerced value, or false if none possible.
622         */
623        public boolean toBoolean(Object val) {
624            if (val == null) {
625                controlNullOperand();
626                return false;
627            } else if (val instanceof Boolean) {
628                return ((Boolean) val).booleanValue();
629            } else if (val instanceof String) {
630                return Boolean.valueOf((String) val).booleanValue();
631            }
632            // TODO: is this a reasonable default?
633            return false;
634        }
635    
636        /**
637         * Coerce to a int.
638         *
639         * @param val Object to be coerced.
640         * @return The int coerced value.
641         */
642        public int toInteger(Object val) {
643            if (val == null) {
644                controlNullOperand();
645                return 0;
646            } else if (val instanceof String) {
647                if ("".equals(val)) {
648                    return 0;
649                }
650                return Integer.parseInt((String) val);
651            } else if (val instanceof Character) {
652                return ((Character) val).charValue();
653            } else if (val instanceof Boolean) {
654                throw new IllegalArgumentException("Boolean->Integer coercion exception");
655            } else if (val instanceof Number) {
656                return ((Number) val).intValue();
657            }
658    
659            throw new IllegalArgumentException("Integer coercion exception. Can't coerce type: "
660                    + val.getClass().getName());
661        }
662    
663        
664        /**
665         * Coerce to a long (not a java.lang.Long).
666         *
667         * @param val Object to be coerced.
668         * @return The long coerced value.
669         */
670        public long toLong(Object val) {
671            if (val == null) {
672                controlNullOperand();
673                return 0L;
674            } else if (val instanceof String) {
675                if ("".equals(val)) {
676                    return 0;
677                }
678                return Long.parseLong((String) val);
679            } else if (val instanceof Character) {
680                return ((Character) val).charValue();
681            } else if (val instanceof Boolean) {
682                throw new NumberFormatException("Boolean->Long coercion exception");
683            } else if (val instanceof Number) {
684                return ((Number) val).longValue();
685            }
686    
687            throw new NumberFormatException("Long coercion exception. Can't coerce type: " + val.getClass().getName());
688        }
689    
690        /**
691         * Get a BigInteger from the object passed.
692         * Null and empty string maps to zero.
693         * @param val the object to be coerced.
694         * @return a BigDecimal.
695         * @throws NullPointerException if val is null and mode is strict.
696         */
697        public BigInteger toBigInteger(Object val) {
698            if (val instanceof BigInteger) {
699                return (BigInteger) val;
700            } else if (val == null) {
701                controlNullOperand();
702                return BigInteger.valueOf(0);
703            } else if (val instanceof String) {
704                String string = (String) val;
705                if ("".equals(string.trim())) {
706                    return BigInteger.ZERO;
707                }
708                return new BigInteger(string);
709            } else if (val instanceof Number) {
710                return new BigInteger(val.toString());
711            } else if (val instanceof Character) {
712                int i = ((Character) val).charValue();
713                return BigInteger.valueOf(i);
714            }
715            
716            throw new IllegalArgumentException("BigInteger coercion exception. Can't coerce type: "
717                    + val.getClass().getName());
718        }
719        
720        /**
721         * Get a BigDecimal from the object passed.
722         * Null and empty string maps to zero.
723         * @param val the object to be coerced.
724         * @return a BigDecimal.
725         * @throws NullPointerException if val is null and mode is strict.
726         */
727        public BigDecimal toBigDecimal(Object val) {
728            if (val instanceof BigDecimal) {
729                return (BigDecimal) val;
730            } else if (val == null) {
731                controlNullOperand();
732                return BigDecimal.ZERO;
733            } else if (val instanceof String) {
734                String string = (String) val;
735                if ("".equals(string.trim())) {
736                    return BigDecimal.valueOf(0);
737                }
738                return new BigDecimal(string);
739            } else if (val instanceof Number) {
740                return new BigDecimal(val.toString());
741            } else if (val instanceof Character) {
742                int i = ((Character) val).charValue();
743                return new BigDecimal(i);
744            }
745            
746            throw new IllegalArgumentException("BigDecimal coercion exception. Can't coerce type: "
747                    + val.getClass().getName());
748        }
749        
750        /**
751         * Coerce to a double.
752         *
753         * @param val Object to be coerced.
754         * @return The double coerced value.
755         * @throws NullPointerException if val is null and mode is strict.
756         */
757        public double toDouble(Object val) {
758            if (val == null) {
759                controlNullOperand();
760                return 0;
761            } else if (val instanceof String) {
762                String string = (String) val;
763                if ("".equals(string.trim())) {
764                    return 0;
765                }
766                // the spec seems to be iffy about this.  Going to give it a wack anyway
767                return Double.parseDouble(string);
768            } else if (val instanceof Character) {
769                int i = ((Character) val).charValue();
770    
771                return i;
772            } else if (val instanceof Double) {
773                return ((Double) val).doubleValue();
774            } else if (val instanceof Number) {
775                //The below construct is used rather than ((Number)val).doubleValue() to ensure
776                //equality between comparing new Double( 6.4 / 3 ) and the jexl expression of 6.4 / 3
777                return Double.parseDouble(String.valueOf(val));
778            } else if (val instanceof Boolean) {
779                throw new IllegalArgumentException("Boolean->Double coercion exception");
780            }
781    
782            throw new IllegalArgumentException("Double coercion exception. Can't coerce type: "
783                    + val.getClass().getName());
784        }
785    
786    
787        /**
788         * Coerce to a string.
789         *
790         * @param val Object to be coerced.
791         * @return The String coerced value.
792         * @throws NullPointerException if val is null and mode is strict.
793         */
794        public String toString(Object val) {
795            if (val == null) {
796                controlNullOperand();
797                val = "";
798            }
799            return val.toString();
800        }
801    
802        /**
803         * Given a Number, return back the value using the smallest type the result
804         * will fit into. This works hand in hand with parameter 'widening' in java
805         * method calls, e.g. a call to substring(int,int) with an int and a long
806         * will fail, but a call to substring(int,int) with an int and a short will
807         * succeed.
808         *
809         * @param original the original number.
810         * @return a value of the smallest type the original number will fit into.
811         */
812        public Number narrow(Number original) {
813            if (original == null) {
814                return original;
815            }
816            Number result = original;
817            if (original instanceof BigDecimal) {
818                BigDecimal bigd = (BigDecimal) original;
819                // if it's bigger than a double it can't be narrowed
820                if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0) {
821                    return original;
822                }
823            }
824            if (original instanceof Double || original instanceof Float || original instanceof BigDecimal) {
825                double value = original.doubleValue();
826                if (value <= Float.MAX_VALUE && value >= Float.MIN_VALUE) {
827                    result = Float.valueOf(result.floatValue());
828                }
829                // else it fits in a double only
830            } else {
831                if (original instanceof BigInteger) {
832                    BigInteger bigi = (BigInteger) original;
833                    // if it's bigger than a Long it can't be narrowed
834                    if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
835                        || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
836                        return original;
837                    }
838                }
839                long value = original.longValue();
840                if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
841                    // it will fit in a byte
842                    result = Byte.valueOf((byte) value);
843                } else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
844                    result = Short.valueOf((short) value);
845                } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
846                    result = Integer.valueOf((int) value);
847                }
848                // else it fits in a long
849            }
850            return result;
851        }
852    
853    }