View Javadoc

1   package org.apache.commons.ognl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
23  
24  import java.lang.reflect.Array;
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.util.Enumeration;
28  
29  /**
30   * This is an abstract class with static methods that define the operations of OGNL.
31   * 
32   * @author Luke Blanshard (blanshlu@netscape.net)
33   * @author Drew Davidson (drew@ognl.org)
34   */
35  public abstract class OgnlOps
36      implements NumericTypes
37  {
38  
39      /**
40       * Compares two objects for equality, even if it has to convert one of them to the other type. If both objects are
41       * numeric they are converted to the widest type and compared. If one is non-numeric and one is numeric the
42       * non-numeric is converted to double and compared to the double numeric value. If both are non-numeric and
43       * Comparable and the types are compatible (i.e. v1 is of the same or superclass of v2's type) they are compared
44       * with Comparable.compareTo(). If both values are non-numeric and not Comparable or of incompatible classes this
45       * will throw and IllegalArgumentException.
46       * 
47       * @param v1 First value to compare
48       * @param v2 second value to compare
49       * @return integer describing the comparison between the two objects. A negative number indicates that v1 < v2.
50       *         Positive indicates that v1 > v2. Zero indicates v1 == v2.
51       * @throws IllegalArgumentException if the objects are both non-numeric yet of incompatible types or do not
52       *             implement Comparable.
53       */
54      public static int compareWithConversion( Object v1, Object v2 )
55      {
56          int result;
57  
58          if ( v1 == v2 )
59          {
60              result = 0;
61          }
62          else
63          {
64              int t1 = getNumericType( v1 ), t2 = getNumericType( v2 ), type = getNumericType( t1, t2, true );
65  
66              switch ( type )
67              {
68                  case BIGINT:
69                      result = bigIntValue( v1 ).compareTo( bigIntValue( v2 ) );
70                      break;
71  
72                  case BIGDEC:
73                      result = bigDecValue( v1 ).compareTo( bigDecValue( v2 ) );
74                      break;
75  
76                  case NONNUMERIC:
77                      if ( ( t1 == NONNUMERIC ) && ( t2 == NONNUMERIC ) )
78                      {
79                          if ( ( v1 instanceof Comparable ) && v1.getClass().isAssignableFrom( v2.getClass() ) )
80                          {
81                              result = ( (Comparable) v1 ).compareTo( v2 );
82                              break;
83                          }
84                          throw new IllegalArgumentException( "invalid comparison: " + v1.getClass().getName()
85                              + " and " + v2.getClass().getName() );
86                      }
87                      // else fall through
88                  case FLOAT:
89                  case DOUBLE:
90                      double dv1 = doubleValue( v1 ),
91                      dv2 = doubleValue( v2 );
92  
93                      return ( dv1 == dv2 ) ? 0 : ( ( dv1 < dv2 ) ? -1 : 1 );
94  
95                  default:
96                      long lv1 = longValue( v1 ),
97                      lv2 = longValue( v2 );
98  
99                      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 }