001    package org.apache.commons.ognl;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
023    
024    import java.lang.reflect.Array;
025    import java.math.BigDecimal;
026    import java.math.BigInteger;
027    import java.util.Enumeration;
028    
029    /**
030     * This is an abstract class with static methods that define the operations of OGNL.
031     * 
032     * @author Luke Blanshard (blanshlu@netscape.net)
033     * @author Drew Davidson (drew@ognl.org)
034     */
035    public abstract class OgnlOps
036        implements NumericTypes
037    {
038    
039        /**
040         * Compares two objects for equality, even if it has to convert one of them to the other type. If both objects are
041         * numeric they are converted to the widest type and compared. If one is non-numeric and one is numeric the
042         * non-numeric is converted to double and compared to the double numeric value. If both are non-numeric and
043         * Comparable and the types are compatible (i.e. v1 is of the same or superclass of v2's type) they are compared
044         * with Comparable.compareTo(). If both values are non-numeric and not Comparable or of incompatible classes this
045         * will throw and IllegalArgumentException.
046         * 
047         * @param v1 First value to compare
048         * @param v2 second value to compare
049         * @return integer describing the comparison between the two objects. A negative number indicates that v1 < v2.
050         *         Positive indicates that v1 > v2. Zero indicates v1 == v2.
051         * @throws IllegalArgumentException if the objects are both non-numeric yet of incompatible types or do not
052         *             implement Comparable.
053         */
054        public static int compareWithConversion( Object v1, Object v2 )
055        {
056            int result;
057    
058            if ( v1 == v2 )
059            {
060                result = 0;
061            }
062            else
063            {
064                int t1 = getNumericType( v1 ), t2 = getNumericType( v2 ), type = getNumericType( t1, t2, true );
065    
066                switch ( type )
067                {
068                    case BIGINT:
069                        result = bigIntValue( v1 ).compareTo( bigIntValue( v2 ) );
070                        break;
071    
072                    case BIGDEC:
073                        result = bigDecValue( v1 ).compareTo( bigDecValue( v2 ) );
074                        break;
075    
076                    case NONNUMERIC:
077                        if ( ( t1 == NONNUMERIC ) && ( t2 == NONNUMERIC ) )
078                        {
079                            if ( ( v1 instanceof Comparable ) && v1.getClass().isAssignableFrom( v2.getClass() ) )
080                            {
081                                result = ( (Comparable) v1 ).compareTo( v2 );
082                                break;
083                            }
084                            throw new IllegalArgumentException( "invalid comparison: " + v1.getClass().getName()
085                                + " and " + v2.getClass().getName() );
086                        }
087                        // else fall through
088                    case FLOAT:
089                    case DOUBLE:
090                        double dv1 = doubleValue( v1 ),
091                        dv2 = doubleValue( v2 );
092    
093                        return ( dv1 == dv2 ) ? 0 : ( ( dv1 < dv2 ) ? -1 : 1 );
094    
095                    default:
096                        long lv1 = longValue( v1 ),
097                        lv2 = longValue( v2 );
098    
099                        return ( lv1 == lv2 ) ? 0 : ( ( lv1 < lv2 ) ? -1 : 1 );
100                }
101            }
102            return result;
103        }
104    
105        /**
106         * Returns true if object1 is equal to object2 in either the sense that they are the same object or, if both are
107         * non-null if they are equal in the <CODE>equals()</CODE> sense.
108         * 
109         * @param object1 First object to compare
110         * @param object2 Second object to compare
111         * @return true if v1 == v2
112         */
113        public static boolean isEqual( Object object1, Object object2 )
114        {
115            boolean result = false;
116    
117            if ( object1 == object2 )
118            {
119                result = true;
120            }
121            else
122            {
123                if ( ( object1 != null ) && object1.getClass().isArray() )
124                {
125                    if ( ( object2 != null ) && object2.getClass().isArray() && ( object2.getClass()
126                        == object1.getClass() ) )
127                    {
128                        result = ( Array.getLength( object1 ) == Array.getLength( object2 ) );
129                        if ( result )
130                        {
131                            for ( int i = 0, icount = Array.getLength( object1 ); result && ( i < icount ); i++ )
132                            {
133                                result = isEqual( Array.get( object1, i ), Array.get( object2, i ) );
134                            }
135                        }
136                    }
137                }
138                else
139                {
140                    // Check for converted equivalence first, then equals() equivalence
141                    result =
142                        ( object1 != null ) && ( object2 != null )
143                            && ( object1.equals( object2 ) || ( compareWithConversion( object1, object2 ) == 0 ) );
144                }
145            }
146            return result;
147        }
148    
149        public static boolean booleanValue( boolean value )
150        {
151            return value;
152        }
153    
154        public static boolean booleanValue( int value )
155        {
156            return value > 0;
157        }
158    
159        public static boolean booleanValue( float value )
160        {
161            return value > 0;
162        }
163    
164        public static boolean booleanValue( long value )
165        {
166            return value > 0;
167        }
168    
169        public static boolean booleanValue( double value )
170        {
171            return value > 0;
172        }
173    
174        /**
175         * Evaluates the given object as a boolean: if it is a Boolean object, it's easy; if it's a Number or a Character,
176         * returns true for non-zero objects; and otherwise returns true for non-null objects.
177         * 
178         * @param value an object to interpret as a boolean
179         * @return the boolean value implied by the given object
180         */
181        public static boolean booleanValue( Object value )
182        {
183            if ( value == null )
184            {
185                return false;
186            }
187            Class<?> c = value.getClass();
188    
189            if ( c == Boolean.class )
190            {
191                return (Boolean) value;
192            }
193            // if ( c == String.class )
194            // return ((String)value).length() > 0;
195    
196            if ( c == Character.class )
197            {
198                return (Character) value != 0;
199            }
200            return !( value instanceof Number ) || ( (Number) value ).doubleValue() != 0;
201    
202        }
203    
204        /**
205         * Evaluates the given object as a long integer.
206         * 
207         * @param value an object to interpret as a long integer
208         * @return the long integer value implied by the given object
209         * @throws NumberFormatException if the given object can't be understood as a long integer
210         */
211        public static long longValue( Object value )
212        {
213            if ( value == null )
214            {
215                return 0L;
216            }
217            Class<?> c = value.getClass();
218            if ( c.getSuperclass() == Number.class )
219            {
220                return ( (Number) value ).longValue();
221            }
222            if ( c == Boolean.class )
223            {
224                return (Boolean) value ? 1 : 0;
225            }
226            if ( c == Character.class )
227            {
228                return (Character) value;
229            }
230            return Long.parseLong( stringValue( value, true ) );
231        }
232    
233        /**
234         * Evaluates the given object as a double-precision floating-point number.
235         * 
236         * @param value an object to interpret as a double
237         * @return the double value implied by the given object
238         * @throws NumberFormatException if the given object can't be understood as a double
239         */
240        public static double doubleValue( Object value )
241        {
242            if ( value == null )
243            {
244                return 0.0;
245            }
246            Class<?> c = value.getClass();
247            if ( c.getSuperclass() == Number.class )
248            {
249                return ( (Number) value ).doubleValue();
250            }
251            if ( c == Boolean.class )
252            {
253                return (Boolean) value ? 1 : 0;
254            }
255            if ( c == Character.class )
256            {
257                return (Character) value;
258            }
259            String s = stringValue( value, true );
260    
261            return ( s.length() == 0 ) ? 0.0 : Double.parseDouble( s );
262        }
263    
264        /**
265         * Evaluates the given object as a BigInteger.
266         * 
267         * @param value an object to interpret as a BigInteger
268         * @return the BigInteger value implied by the given object
269         * @throws NumberFormatException if the given object can't be understood as a BigInteger
270         */
271        public static BigInteger bigIntValue( Object value )
272        {
273            if ( value == null )
274            {
275                return BigInteger.valueOf( 0L );
276            }
277            Class<?> c = value.getClass();
278            if ( c == BigInteger.class )
279            {
280                return (BigInteger) value;
281            }
282            if ( c == BigDecimal.class )
283            {
284                return ( (BigDecimal) value ).toBigInteger();
285            }
286            if ( c.getSuperclass() == Number.class )
287            {
288                return BigInteger.valueOf( ( (Number) value ).longValue() );
289            }
290            if ( c == Boolean.class )
291            {
292                return BigInteger.valueOf( (Boolean) value ? 1 : 0 );
293            }
294            if ( c == Character.class )
295            {
296                return BigInteger.valueOf( (Character) value );
297            }
298            return new BigInteger( stringValue( value, true ) );
299        }
300    
301        /**
302         * Evaluates the given object as a BigDecimal.
303         * 
304         * @param value an object to interpret as a BigDecimal
305         * @return the BigDecimal value implied by the given object
306         * @throws NumberFormatException if the given object can't be understood as a BigDecimal
307         */
308        public static BigDecimal bigDecValue( Object value )
309        {
310            if ( value == null )
311            {
312                return BigDecimal.valueOf( 0L );
313            }
314            Class<?> c = value.getClass();
315            if ( c == BigDecimal.class )
316            {
317                return (BigDecimal) value;
318            }
319            if ( c == BigInteger.class )
320            {
321                return new BigDecimal( (BigInteger) value );
322            }
323            if ( c == Boolean.class )
324            {
325                return BigDecimal.valueOf( (Boolean) value ? 1 : 0 );
326            }
327            if ( c == Character.class )
328            {
329                return BigDecimal.valueOf( ( (Character) value ).charValue() );
330            }
331            return new BigDecimal( stringValue( value, true ) );
332        }
333    
334        /**
335         * Evaluates the given object as a String and trims it if the trim flag is true.
336         * 
337         * @param value an object to interpret as a String
338         * @param trim if true trims the result
339         * @return the String value implied by the given object as returned by the toString() method, or "null" if the
340         *         object is null.
341         */
342        public static String stringValue( Object value, boolean trim )
343        {
344            String result;
345    
346            if ( value == null )
347            {
348                result = OgnlRuntime.NULL_STRING;
349            }
350            else
351            {
352                result = value.toString();
353                if ( trim )
354                {
355                    result = result.trim();
356                }
357            }
358            return result;
359        }
360    
361        /**
362         * Evaluates the given object as a String.
363         * 
364         * @param value an object to interpret as a String
365         * @return the String value implied by the given object as returned by the toString() method, or "null" if the
366         *         object is null.
367         */
368        public static String stringValue( Object value )
369        {
370            return stringValue( value, false );
371        }
372    
373        /**
374         * Returns a constant from the NumericTypes interface that represents the numeric type of the given object.
375         * 
376         * @param value an object that needs to be interpreted as a number
377         * @return the appropriate constant from the NumericTypes interface
378         */
379        public static int getNumericType( Object value )
380        {
381            if ( value != null )
382            {
383                Class<?> c = value.getClass();
384                if ( c == Integer.class )
385                {
386                    return INT;
387                }
388                if ( c == Double.class )
389                {
390                    return DOUBLE;
391                }
392                if ( c == Boolean.class )
393                {
394                    return BOOL;
395                }
396                if ( c == Byte.class )
397                {
398                    return BYTE;
399                }
400                if ( c == Character.class )
401                {
402                    return CHAR;
403                }
404                if ( c == Short.class )
405                {
406                    return SHORT;
407                }
408                if ( c == Long.class )
409                {
410                    return LONG;
411                }
412                if ( c == Float.class )
413                {
414                    return FLOAT;
415                }
416                if ( c == BigInteger.class )
417                {
418                    return BIGINT;
419                }
420                if ( c == BigDecimal.class )
421                {
422                    return BIGDEC;
423                }
424            }
425            return NONNUMERIC;
426        }
427    
428        public static Object toArray( char value, Class<?> toType )
429            throws OgnlException
430        {
431            return toArray( Character.valueOf( value ), toType );
432        }
433    
434        public static Object toArray( byte value, Class<?> toType )
435            throws OgnlException
436        {
437            return toArray( Byte.valueOf( value ), toType );
438        }
439    
440        public static Object toArray( int value, Class<?> toType )
441            throws OgnlException
442        {
443            return toArray( Integer.valueOf( value ), toType );
444        }
445    
446        public static Object toArray( long value, Class<?> toType )
447            throws OgnlException
448        {
449            return toArray( Long.valueOf( value ), toType );
450        }
451    
452        public static Object toArray( float value, Class<?> toType )
453            throws OgnlException
454        {
455            return toArray( Float.valueOf( value ), toType );
456        }
457    
458        public static Object toArray( double value, Class<?> toType )
459            throws OgnlException
460        {
461            return toArray( Double.valueOf( value ), toType );
462        }
463    
464        public static Object toArray( boolean value, Class<?> toType )
465            throws OgnlException
466        {
467            return toArray( Boolean.valueOf( value ), toType );
468        }
469    
470        public static <T> Object convertValue( char value, Class<T> toType )
471            throws OgnlException
472        {
473            return convertValue( Character.valueOf(value), toType );
474        }
475    
476        public static <T> Object convertValue( byte value, Class<T> toType )
477            throws OgnlException
478        {
479            return convertValue( Byte.valueOf( value ), toType );
480        }
481    
482        public static <T> Object convertValue( int value, Class<T> toType )
483            throws OgnlException
484        {
485            return convertValue( Integer.valueOf( value ), toType );
486        }
487    
488        public static <T> Object convertValue( long value, Class<T> toType )
489            throws OgnlException
490        {
491            return convertValue( Long.valueOf( value ), toType );
492        }
493    
494        public static <T> Object convertValue( float value, Class<T> toType )
495            throws OgnlException
496        {
497            return convertValue( Float.valueOf( value ), toType );
498        }
499    
500        public static <T> Object convertValue( double value, Class<T> toType )
501            throws OgnlException
502        {
503            return convertValue( Double.valueOf( value ), toType );
504        }
505    
506        public static <T> Object convertValue( boolean value, Class<T> toType )
507            throws OgnlException
508        {
509            return convertValue( Boolean.valueOf( value ), toType );
510        }
511    
512        // //////////////////////////////////////////////////////////////
513    
514        public static <T> Object convertValue( char value, Class<T> toType, boolean preventNull )
515            throws OgnlException
516        {
517            return convertValue( Character.valueOf( value ), toType, preventNull );
518        }
519    
520        public static <T> Object convertValue( byte value, Class<T> toType, boolean preventNull )
521            throws OgnlException
522        {
523            return convertValue( Byte.valueOf( value ), toType, preventNull );
524        }
525    
526        public static <T> Object convertValue( int value, Class<T> toType, boolean preventNull )
527            throws OgnlException
528        {
529            return convertValue( Integer.valueOf( value ), toType, preventNull );
530        }
531    
532        public static <T> Object convertValue( long value, Class<T> toType, boolean preventNull )
533            throws OgnlException
534        {
535            return convertValue( Long.valueOf( value ), toType, preventNull );
536        }
537    
538        public static <T> Object convertValue( float value, Class<T> toType, boolean preventNull )
539            throws OgnlException
540        {
541            return convertValue( new Float( value ), toType, preventNull );
542        }
543    
544        public static <T> Object convertValue( double value, Class<T> toType, boolean preventNull )
545            throws OgnlException
546        {
547            return convertValue( new Double( value ), toType, preventNull );
548        }
549    
550        public static <T> Object convertValue( boolean value, Class<T> toType, boolean preventNull )
551            throws OgnlException
552        {
553            return convertValue( Boolean.valueOf( value ), toType, preventNull );
554        }
555    
556        // ///////////////////////////////////////////////////////////////
557    
558        public static Object toArray( char value, Class<?> toType, boolean preventNull )
559            throws OgnlException
560        {
561            return toArray( Character.valueOf( value ), toType, preventNull );
562        }
563    
564        public static Object toArray( byte value, Class<?> toType, boolean preventNull )
565            throws OgnlException
566        {
567            return toArray( Byte.valueOf( value ), toType, preventNull );
568        }
569    
570        public static Object toArray( int value, Class<?> toType, boolean preventNull )
571            throws OgnlException
572        {
573            return toArray( Integer.valueOf( value ), toType, preventNull );
574        }
575    
576        public static Object toArray( long value, Class<?> toType, boolean preventNull )
577            throws OgnlException
578        {
579            return toArray( Long.valueOf(value), toType, preventNull );
580        }
581    
582        public static Object toArray( float value, Class<?> toType, boolean preventNull )
583            throws OgnlException
584        {
585            return toArray( Float.valueOf( value ), toType, preventNull );
586        }
587    
588        public static Object toArray( double value, Class<?> toType, boolean preventNull )
589            throws OgnlException
590        {
591            return toArray( Double.valueOf(value), toType, preventNull );
592        }
593    
594        public static Object toArray( boolean value, Class<?> toType, boolean preventNull )
595            throws OgnlException
596        {
597            return toArray( Boolean.valueOf( value ), toType, preventNull );
598        }
599    
600        /**
601         * Returns the value converted numerically to the given class type This method also detects when arrays are being
602         * converted and converts the components of one array to the type of the other.
603         * 
604         * @param value an object to be converted to the given type
605         * @param toType class type to be converted to
606         * @return converted value of the type given, or value if the value cannot be converted to the given type.
607         */
608        public static Object convertValue( Object value, Class<?> toType )
609        {
610            return convertValue( value, toType, false );
611        }
612    
613        public static Object toArray( Object value, Class<?> toType )
614            throws OgnlException
615        {
616            return toArray( value, toType, false );
617        }
618    
619        public static Object toArray( Object value, Class<?> toType, boolean preventNulls )
620            throws OgnlException
621        {
622            if ( value == null )
623            {
624                return null;
625            }
626    
627            Object result;
628    
629            Class<?> aClass = value.getClass();
630            if ( aClass.isArray() && toType.isAssignableFrom( aClass.getComponentType() ) )
631            {
632                return value;
633            }
634    
635            if ( !aClass.isArray() )
636            {
637    
638                if ( toType == Character.TYPE )
639                {
640                    return stringValue( value ).toCharArray();
641                }
642    
643                Object arr = Array.newInstance( toType, 1 );
644                Array.set( arr, 0, convertValue( value, toType, preventNulls ) );
645    
646                return arr;
647            }
648    
649            result = Array.newInstance( toType, Array.getLength( value ) );
650            for ( int i = 0, icount = Array.getLength( value ); i < icount; i++ )
651            {
652                Array.set( result, i, convertValue( Array.get( value, i ), toType ) );
653            }
654    
655            if ( result == null && preventNulls )
656            {
657                return value;
658            }
659    
660            return result;
661        }
662    
663        public static <T> Object convertValue( Object value, Class<T> toType, boolean preventNulls )
664        {
665            Object result = null;
666    
667            if ( value != null && toType.isAssignableFrom( value.getClass() ) )
668            {
669                return value;
670            }
671    
672            if ( value != null )
673            {
674                /* If array -> array then convert components of array individually */
675                boolean classIsArray = value.getClass().isArray();
676                boolean toTypeIsArray = toType.isArray();
677                if ( classIsArray && toTypeIsArray)
678                {
679                    Class<?> componentType = toType.getComponentType();
680    
681                    result = Array.newInstance( componentType, Array.getLength( value ) );
682                    for ( int i = 0, icount = Array.getLength( value ); i < icount; i++ )
683                    {
684                        Array.set( result, i, convertValue( Array.get( value, i ), componentType ) );
685                    }
686                }
687                else if ( classIsArray && !toTypeIsArray)
688                {
689    
690                    return convertValue( Array.get( value, 0 ), toType );
691                }
692                else if ( toTypeIsArray )
693                {
694    
695                    if ( toType.getComponentType() == Character.TYPE )
696                    {
697    
698                        result = stringValue( value ).toCharArray();
699                    }
700                    else if ( toType.getComponentType() == Object.class )
701                    {
702                        return new Object[] { value };
703                    }
704                }
705                else
706                {
707                    if ( ( toType == Integer.class ) || ( toType == Integer.TYPE ) )
708                    {
709                        result = (int) longValue( value );
710                    }
711                    else if ( ( toType == Double.class ) || ( toType == Double.TYPE ) )
712                    {
713                        result = doubleValue( value );
714                    }
715                    else if ( ( toType == Boolean.class ) || ( toType == Boolean.TYPE ) )
716                    {
717                        result = booleanValue( value ) ? Boolean.TRUE : Boolean.FALSE;
718                    }
719                    else if ( ( toType == Byte.class ) || ( toType == Byte.TYPE ) )
720                    {
721                        result = (byte) longValue( value );
722                    }
723                    else if ( ( toType == Character.class ) || ( toType == Character.TYPE ) )
724                    {
725                        result = (char) longValue( value );
726                    }
727                    else if ( ( toType == Short.class ) || ( toType == Short.TYPE ) )
728                    {
729                        result = (short) longValue( value );
730                    }
731                    else if ( ( toType == Long.class ) || ( toType == Long.TYPE ) )
732                    {
733                        result = longValue( value );
734                    }
735                    else if ( ( toType == Float.class ) || ( toType == Float.TYPE ) )
736                    {
737                        result = new Float( doubleValue( value ) );
738                    }
739                    else if ( toType == BigInteger.class )
740                    {
741                        result = bigIntValue( value );
742                    }
743                    else if ( toType == BigDecimal.class )
744                    {
745                        result = bigDecValue( value );
746                    }
747                    else if ( toType == String.class )
748                    {
749                        result = stringValue( value );
750                    }
751                }
752            }
753            else
754            {
755                if ( toType.isPrimitive() )
756                {
757                    result = OgnlRuntime.getPrimitiveDefaultValue( toType );
758                }
759                else if ( preventNulls && toType == Boolean.class )
760                {
761                    result = Boolean.FALSE;
762                }
763                else if ( preventNulls && Number.class.isAssignableFrom( toType ) )
764                {
765                    result = OgnlRuntime.getNumericDefaultValue( toType );
766                }
767            }
768    
769            if ( result == null && preventNulls )
770            {
771                return value;
772            }
773    
774            if ( value != null && result == null )
775            {
776    
777                throw new IllegalArgumentException( "Unable to convert type " + value.getClass().getName() + " of " + value
778                    + " to type of " + toType.getName() );
779            }
780    
781            return result;
782        }
783    
784        /**
785         * Converts the specified value to a primitive integer value.
786         * <ul>
787         * <li>Null values will cause a -1 to be returned.</li>
788         * <li>{@link Number} instances have their intValue() methods invoked.</li>
789         * <li>All other types result in calling Integer.parseInt(value.toString());</li>
790         * </ul>
791         * 
792         * @param value The object to get the value of.
793         * @return A valid integer.
794         */
795        public static int getIntValue( Object value )
796        {
797            try
798            {
799                if ( value == null )
800                {
801                    return -1;
802                }
803    
804                if ( Number.class.isInstance( value ) )
805                {
806    
807                    return ( (Number) value ).intValue();
808                }
809    
810                String str = String.class.isInstance( value ) ? (String) value : value.toString();
811    
812                return Integer.parseInt( str );
813            }
814            catch ( Throwable t )
815            {
816                throw new RuntimeException( "Error converting " + value + " to integer:", t );
817            }
818        }
819    
820        /**
821         * Returns the constant from the NumericTypes interface that best expresses the type of a numeric operation on the
822         * two given objects.
823         * 
824         * @param v1 one argument to a numeric operator
825         * @param v2 the other argument
826         * @return the appropriate constant from the NumericTypes interface
827         */
828        public static int getNumericType( Object v1, Object v2 )
829        {
830            return getNumericType( v1, v2, false );
831        }
832    
833        /**
834         * Returns the constant from the NumericTypes interface that best expresses the type of an operation, which can be
835         * either numeric or not, on the two given types.
836         * 
837         * @param t1 type of one argument to an operator
838         * @param t2 type of the other argument
839         * @param canBeNonNumeric whether the operator can be interpreted as non-numeric
840         * @return the appropriate constant from the NumericTypes interface
841         */
842        public static int getNumericType( int t1, int t2, boolean canBeNonNumeric )
843        {
844            if ( t1 == t2 )
845            {
846                return t1;
847            }
848    
849            if ( canBeNonNumeric && ( t1 == NONNUMERIC || t2 == NONNUMERIC || t1 == CHAR || t2 == CHAR ) )
850            {
851                return NONNUMERIC;
852            }
853    
854            if ( t1 == NONNUMERIC )
855            {
856                t1 = DOUBLE; // Try to interpret strings as doubles...
857            }
858            if ( t2 == NONNUMERIC )
859            {
860                t2 = DOUBLE; // Try to interpret strings as doubles...
861            }
862    
863            if ( t1 >= MIN_REAL_TYPE )
864            {
865                if ( t2 >= MIN_REAL_TYPE )
866                {
867                    return Math.max( t1, t2 );
868                }
869                if ( t2 < INT )
870                {
871                    return t1;
872                }
873                if ( t2 == BIGINT )
874                {
875                    return BIGDEC;
876                }
877                return Math.max( DOUBLE, t1 );
878            }
879            else if ( t2 >= MIN_REAL_TYPE )
880            {
881                if ( t1 < INT )
882                {
883                    return t2;
884                }
885                if ( t1 == BIGINT )
886                {
887                    return BIGDEC;
888                }
889                return Math.max( DOUBLE, t2 );
890            }
891            else
892            {
893                return Math.max( t1, t2 );
894            }
895        }
896    
897        /**
898         * Returns the constant from the NumericTypes interface that best expresses the type of an operation, which can be
899         * either numeric or not, on the two given objects.
900         * 
901         * @param v1 one argument to an operator
902         * @param v2 the other argument
903         * @param canBeNonNumeric whether the operator can be interpreted as non-numeric
904         * @return the appropriate constant from the NumericTypes interface
905         */
906        public static int getNumericType( Object v1, Object v2, boolean canBeNonNumeric )
907        {
908            return getNumericType( getNumericType( v1 ), getNumericType( v2 ), canBeNonNumeric );
909        }
910    
911        /**
912         * Returns a new Number object of an appropriate type to hold the given integer value. The type of the returned
913         * object is consistent with the given type argument, which is a constant from the NumericTypes interface.
914         * 
915         * @param type the nominal numeric type of the result, a constant from the NumericTypes interface
916         * @param value the integer value to convert to a Number object
917         * @return a Number object with the given value, of type implied by the type argument
918         */
919        public static Number newInteger( int type, long value )
920        {
921            switch ( type )
922            {
923                case BOOL:
924                case CHAR:
925                case INT:
926                    return (int) value;
927    
928                case FLOAT:
929                    if ( (long) (float) value == value )
930                    {
931                        return (float) value;
932                    }
933                    // else fall through:
934                case DOUBLE:
935                    if ( (long) (double) value == value )
936                    {
937                        return (double) value;
938                    }
939                    // else fall through:
940                case LONG:
941                    return value;
942    
943                case BYTE:
944                    return (byte) value;
945    
946                case SHORT:
947                    return (short) value;
948    
949                default:
950                    return BigInteger.valueOf( value );
951            }
952        }
953    
954        /**
955         * Returns a new Number object of an appropriate type to hold the given real value. The type of the returned object
956         * is always either Float or Double, and is only Float if the given type tag (a constant from the NumericTypes
957         * interface) is FLOAT.
958         * 
959         * @param type the nominal numeric type of the result, a constant from the NumericTypes interface
960         * @param value the real value to convert to a Number object
961         * @return a Number object with the given value, of type implied by the type argument
962         */
963        public static Number newReal( int type, double value )
964        {
965            if ( type == FLOAT )
966            {
967                return (float) value;
968            }
969            return value;
970        }
971    
972        public static Object binaryOr( Object v1, Object v2 )
973        {
974            int type = getNumericType( v1, v2 );
975            if ( type == BIGINT || type == BIGDEC )
976            {
977                return bigIntValue( v1 ).or( bigIntValue( v2 ) );
978            }
979            return newInteger( type, longValue( v1 ) | longValue( v2 ) );
980        }
981    
982        public static Object binaryXor( Object v1, Object v2 )
983        {
984            int type = getNumericType( v1, v2 );
985            if ( type == BIGINT || type == BIGDEC )
986            {
987                return bigIntValue( v1 ).xor( bigIntValue( v2 ) );
988            }
989            return newInteger( type, longValue( v1 ) ^ longValue( v2 ) );
990        }
991    
992        public static Object binaryAnd( Object v1, Object v2 )
993        {
994            int type = getNumericType( v1, v2 );
995            if ( type == BIGINT || type == BIGDEC )
996            {
997                return bigIntValue( v1 ).and( bigIntValue( v2 ) );
998            }
999            return newInteger( type, longValue( v1 ) & longValue( v2 ) );
1000        }
1001    
1002        public static boolean equal( Object v1, Object v2 )
1003        {
1004            if ( v1 == null )
1005            {
1006                return v2 == null;
1007            }
1008            if ( v1 == v2 || isEqual( v1, v2 ) )
1009            {
1010                return true;
1011            }
1012            if ( v1 instanceof Number && v2 instanceof Number )
1013            {
1014                return ( (Number) v1 ).doubleValue() == ( (Number) v2 ).doubleValue();
1015            }
1016            return false;
1017        }
1018    
1019        public static boolean less( Object v1, Object v2 )
1020        {
1021            return compareWithConversion( v1, v2 ) < 0;
1022        }
1023    
1024        public static boolean greater( Object v1, Object v2 )
1025        {
1026            return compareWithConversion( v1, v2 ) > 0;
1027        }
1028    
1029        public static boolean in( Object v1, Object v2 )
1030            throws OgnlException
1031        {
1032            if ( v2 == null ) // A null collection is always treated as empty
1033            {
1034                return false;
1035            }
1036    
1037            ElementsAccessor elementsAccessor = OgnlRuntime.getElementsAccessor( OgnlRuntime.getTargetClass( v2 ) );
1038    
1039            // FIXME O(n) is there a better way?!
1040            for ( Enumeration<?> e = elementsAccessor.getElements( v2 ); e.hasMoreElements(); )
1041            {
1042                Object o = e.nextElement();
1043    
1044                if ( equal( v1, o ) )
1045                {
1046                    return true;
1047                }
1048            }
1049    
1050            return false;
1051        }
1052    
1053        public static Object shiftLeft( Object v1, Object v2 )
1054        {
1055            int type = getNumericType( v1 );
1056            if ( type == BIGINT || type == BIGDEC )
1057            {
1058                return bigIntValue( v1 ).shiftLeft( (int) longValue( v2 ) );
1059            }
1060            return newInteger( type, longValue( v1 ) << (int) longValue( v2 ) );
1061        }
1062    
1063        public static Object shiftRight( Object v1, Object v2 )
1064        {
1065            int type = getNumericType( v1 );
1066            if ( type == BIGINT || type == BIGDEC )
1067            {
1068                return bigIntValue( v1 ).shiftRight( (int) longValue( v2 ) );
1069            }
1070            return newInteger( type, longValue( v1 ) >> (int) longValue( v2 ) );
1071        }
1072    
1073        public static Object unsignedShiftRight( Object v1, Object v2 )
1074        {
1075            int type = getNumericType( v1 );
1076            if ( type == BIGINT || type == BIGDEC )
1077            {
1078                return bigIntValue( v1 ).shiftRight( (int) longValue( v2 ) );
1079            }
1080            if ( type <= INT )
1081            {
1082                return newInteger( INT, ( (int) longValue( v1 ) ) >>> (int) longValue( v2 ) );
1083            }
1084            return newInteger( type, longValue( v1 ) >>> (int) longValue( v2 ) );
1085        }
1086    
1087        public static Object add( Object v1, Object v2 )
1088        {
1089            int type = getNumericType( v1, v2, true );
1090            switch ( type )
1091            {
1092                case BIGINT:
1093                    return bigIntValue( v1 ).add( bigIntValue( v2 ) );
1094                case BIGDEC:
1095                    return bigDecValue( v1 ).add( bigDecValue( v2 ) );
1096                case FLOAT:
1097                case DOUBLE:
1098                    return newReal( type, doubleValue( v1 ) + doubleValue( v2 ) );
1099                case NONNUMERIC:
1100                    int t1 = getNumericType( v1 ),
1101                    t2 = getNumericType( v2 );
1102    
1103                    if ( ( ( t1 != NONNUMERIC ) && ( v2 == null ) ) || ( ( t2 != NONNUMERIC ) && ( v1 == null ) ) )
1104                    {
1105                        throw new NullPointerException( "Can't add values " + v1 + " , " + v2 );
1106                    }
1107    
1108                    return stringValue( v1 ) + stringValue( v2 );
1109                default:
1110                    return newInteger( type, longValue( v1 ) + longValue( v2 ) );
1111            }
1112        }
1113    
1114        public static Object subtract( Object v1, Object v2 )
1115        {
1116            int type = getNumericType( v1, v2 );
1117            switch ( type )
1118            {
1119                case BIGINT:
1120                    return bigIntValue( v1 ).subtract( bigIntValue( v2 ) );
1121                case BIGDEC:
1122                    return bigDecValue( v1 ).subtract( bigDecValue( v2 ) );
1123                case FLOAT:
1124                case DOUBLE:
1125                    return newReal( type, doubleValue( v1 ) - doubleValue( v2 ) );
1126                default:
1127                    return newInteger( type, longValue( v1 ) - longValue( v2 ) );
1128            }
1129        }
1130    
1131        public static Object multiply( Object v1, Object v2 )
1132        {
1133            int type = getNumericType( v1, v2 );
1134            switch ( type )
1135            {
1136                case BIGINT:
1137                    return bigIntValue( v1 ).multiply( bigIntValue( v2 ) );
1138                case BIGDEC:
1139                    return bigDecValue( v1 ).multiply( bigDecValue( v2 ) );
1140                case FLOAT:
1141                case DOUBLE:
1142                    return newReal( type, doubleValue( v1 ) * doubleValue( v2 ) );
1143                default:
1144                    return newInteger( type, longValue( v1 ) * longValue( v2 ) );
1145            }
1146        }
1147    
1148        public static Object divide( Object v1, Object v2 )
1149        {
1150            int type = getNumericType( v1, v2 );
1151            switch ( type )
1152            {
1153                case BIGINT:
1154                    return bigIntValue( v1 ).divide( bigIntValue( v2 ) );
1155                case BIGDEC:
1156                    return bigDecValue( v1 ).divide( bigDecValue( v2 ), BigDecimal.ROUND_HALF_EVEN );
1157                case FLOAT:
1158                case DOUBLE:
1159                    return newReal( type, doubleValue( v1 ) / doubleValue( v2 ) );
1160                default:
1161                    return newInteger( type, longValue( v1 ) / longValue( v2 ) );
1162            }
1163        }
1164    
1165        public static Object remainder( Object v1, Object v2 )
1166        {
1167            int type = getNumericType( v1, v2 );
1168            switch ( type )
1169            {
1170                case BIGDEC:
1171                case BIGINT:
1172                    return bigIntValue( v1 ).remainder( bigIntValue( v2 ) );
1173                default:
1174                    return newInteger( type, longValue( v1 ) % longValue( v2 ) );
1175            }
1176        }
1177    
1178        public static Object negate( Object value )
1179        {
1180            int type = getNumericType( value );
1181            switch ( type )
1182            {
1183                case BIGINT:
1184                    return bigIntValue( value ).negate();
1185                case BIGDEC:
1186                    return bigDecValue( value ).negate();
1187                case FLOAT:
1188                case DOUBLE:
1189                    return newReal( type, -doubleValue( value ) );
1190                default:
1191                    return newInteger( type, -longValue( value ) );
1192            }
1193        }
1194    
1195        public static Object bitNegate( Object value )
1196        {
1197            int type = getNumericType( value );
1198            switch ( type )
1199            {
1200                case BIGDEC:
1201                case BIGINT:
1202                    return bigIntValue( value ).not();
1203                default:
1204                    return newInteger( type, ~longValue( value ) );
1205            }
1206        }
1207    
1208        public static String getEscapeString( String value )
1209        {
1210            StringBuilder result = new StringBuilder();
1211    
1212            int length = value.length();
1213            for ( int i = 0; i < length; i++ )
1214            {
1215                result.append( getEscapedChar( value.charAt( i ) ) );
1216            }
1217            return result.toString();
1218        }
1219    
1220        public static String getEscapedChar( char ch )
1221        {
1222            String result;
1223    
1224            switch ( ch )
1225            {
1226                case '\b':
1227                    result = "\b";
1228                    break;
1229                case '\t':
1230                    result = "\\t";
1231                    break;
1232                case '\n':
1233                    result = "\\n";
1234                    break;
1235                case '\f':
1236                    result = "\\f";
1237                    break;
1238                case '\r':
1239                    result = "\\r";
1240                    break;
1241                case '\"':
1242                    result = "\\\"";
1243                    break;
1244                case '\'':
1245                    result = "\\\'";
1246                    break;
1247                case '\\':
1248                    result = "\\\\";
1249                    break;
1250                default:
1251                    if ( Character.isISOControl( ch ) )
1252                    {
1253    
1254                        String hc = Integer.toString( (int) ch, 16 );
1255                        int hcl = hc.length();
1256    
1257                        result = "\\u";
1258                        if ( hcl < 4 )
1259                        {
1260                            if ( hcl == 3 )
1261                            {
1262                                result = result + "0";
1263                            }
1264                            else
1265                            {
1266                                if ( hcl == 2 )
1267                                {
1268                                    result = result + "00";
1269                                }
1270                                else
1271                                {
1272                                    result = result + "000";
1273                                }
1274                            }
1275                        }
1276    
1277                        result = result + hc;
1278                    }
1279                    else
1280                    {
1281                        result = ch + "";
1282                    }
1283                    break;
1284            }
1285            return result;
1286        }
1287    
1288        public static Object returnValue( Object ignore, Object returnValue )
1289        {
1290            return returnValue;
1291        }
1292    
1293        /**
1294         * Utility method that converts incoming exceptions to {@link RuntimeException} instances - or casts them if they
1295         * already are.
1296         * 
1297         * @param t The exception to cast.
1298         * @return The exception cast to a {@link RuntimeException}.
1299         */
1300        public static RuntimeException castToRuntime( Throwable t )
1301        {
1302            if ( RuntimeException.class.isInstance( t ) )
1303            {
1304                return (RuntimeException) t;
1305            }
1306    
1307            if ( OgnlException.class.isInstance( t ) )
1308            {
1309                throw new UnsupportedCompilationException( "Error evluating expression: " + t.getMessage(), t );
1310            }
1311    
1312            return new RuntimeException( t );
1313        }
1314    }