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.ExpressionCompiler;
23  import org.apache.commons.ognl.enhance.OgnlExpressionCompiler;
24  import org.apache.commons.ognl.internal.CacheException;
25  import org.apache.commons.ognl.internal.entry.DeclaredMethodCacheEntry;
26  import org.apache.commons.ognl.internal.entry.GenericMethodParameterTypeCacheEntry;
27  import org.apache.commons.ognl.internal.entry.MethodAccessEntryValue;
28  import org.apache.commons.ognl.internal.entry.PermissionCacheEntry;
29  
30  import java.beans.BeanInfo;
31  import java.beans.IndexedPropertyDescriptor;
32  import java.beans.IntrospectionException;
33  import java.beans.Introspector;
34  import java.beans.MethodDescriptor;
35  import java.beans.PropertyDescriptor;
36  import java.lang.reflect.Constructor;
37  import java.lang.reflect.Field;
38  import java.lang.reflect.InvocationTargetException;
39  import java.lang.reflect.Member;
40  import java.lang.reflect.Method;
41  import java.lang.reflect.Modifier;
42  import java.lang.reflect.ParameterizedType;
43  import java.lang.reflect.Proxy;
44  import java.security.Permission;
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Collections;
48  import java.util.HashMap;
49  import java.util.List;
50  import java.util.Map;
51  
52  /**
53   * Utility class used by internal OGNL API to do various things like:
54   * <ul>
55   * <li>Handles majority of reflection logic / caching.</li>
56   * <li>Utility methods for casting strings / various numeric types used by {@link OgnlExpressionCompiler}.</li.
57   * <li>Core runtime configuration point for setting/using global {@link TypeConverter} / {@link OgnlExpressionCompiler}
58   * / {@link NullHandler} instances / etc..</li>
59   * </ul>
60   *
61   * $Id: OgnlRuntime.java 1239219 2012-02-01 17:27:54Z mcucchiara $
62   *
63   * @author Luke Blanshard (blanshlu@netscape.net)
64   * @author Drew Davidson (drew@ognl.org)
65   */
66  public class OgnlRuntime
67  {
68      /**
69       * Constant expression used to indicate that a given method / property couldn't be found during reflection
70       * operations.
71       */
72      public static final Object NotFound = new Object();
73  
74      public static final Object[] NoArguments = new Object[]{ };
75  
76      /**
77       * Token returned by TypeConverter for no conversion possible
78       */
79      public static final Object NoConversionPossible = "ognl.NoConversionPossible";
80  
81      /**
82       * Not an indexed property
83       */
84      public static final int INDEXED_PROPERTY_NONE = 0;
85  
86      /**
87       * JavaBeans IndexedProperty
88       */
89      public static final int INDEXED_PROPERTY_INT = 1;
90  
91      /**
92       * OGNL ObjectIndexedProperty
93       */
94      public static final int INDEXED_PROPERTY_OBJECT = 2;
95  
96      /**
97       * Constant string representation of null string.
98       */
99      public static final String NULL_STRING = "" + null;
100 
101     /**
102      * Java beans standard set method prefix.
103      */
104     public static final String SET_PREFIX = "set";
105 
106     /**
107      * Java beans standard get method prefix.
108      */
109     public static final String GET_PREFIX = "get";
110 
111     /**
112      * Java beans standard is<Foo> boolean getter prefix.
113      */
114     public static final String IS_PREFIX = "is";
115 
116     /**
117      * Prefix padding for hexadecimal numbers to HEX_LENGTH.
118      */
119     private static final Map<Integer, String> HEX_PADDING = new HashMap<Integer, String>();
120 
121     private static final int HEX_LENGTH = 8;
122 
123     /**
124      * Returned by <CODE>getUniqueDescriptor()</CODE> when the object is <CODE>null</CODE>.
125      */
126     private static final String NULL_OBJECT_STRING = "<null>";
127 
128     static OgnlCache cache = new OgnlCache();
129 
130     private static final PrimitiveTypes primitiveTypes = new PrimitiveTypes();
131 
132     private static final PrimitiveDefaults primitiveDefaults = new PrimitiveDefaults();
133 
134     private static SecurityManager securityManager = System.getSecurityManager();
135 
136     private static final EvaluationPool evaluationPool = new EvaluationPool();
137 
138     private static final ObjectArrayPool objectArrayPool = new ObjectArrayPool();
139 
140     /**
141      * Expression compiler used by {@link Ognl#compileExpression(OgnlContext, Object, String)} calls.
142      */
143     private static OgnlExpressionCompiler compiler;
144 
145     /**
146      * Used to provide primitive type equivalent conversions into and out of native / object types.
147      */
148     private static final PrimitiveWrapperClasses primitiveWrapperClasses = new PrimitiveWrapperClasses();
149 
150     /**
151      * Constant strings for casting different primitive types.
152      */
153     private static final NumericCasts numericCasts = new NumericCasts();
154 
155     /**
156      * Constant strings for getting the primitive value of different native types on the generic {@link Number} object
157      * interface. (or the less generic BigDecimal/BigInteger types)
158      */
159     private static final NumericValues numericValues = new NumericValues();
160 
161     /**
162      * Numeric primitive literal string expressions.
163      */
164     private static final NumericLiterals numericLiterals = new NumericLiterals();
165 
166     private static final NumericDefaults numericDefaults = new NumericDefaults();
167 
168     /**
169      * Clears all of the cached reflection information normally used to improve the speed of expressions that operate on
170      * the same classes or are executed multiple times.
171      * <p>
172      * <strong>Warning:</strong> Calling this too often can be a huge performance drain on your expressions - use with
173      * care.
174      * </p>
175      */
176     public static void clearCache()
177     {
178         cache.clear();
179     }
180 
181     /**
182      * @deprecated This always returns true now since OGNL requires Java 1.5.
183      * @return Always returns true.
184      */
185     public static boolean isJdk15()
186     {
187         return true;
188     }
189 
190     public static String getNumericValueGetter( Class<?> type )
191     {
192         return numericValues.get( type );
193     }
194 
195     public static Class<?> getPrimitiveWrapperClass( Class<?> primitiveClass )
196     {
197         return primitiveWrapperClasses.get( primitiveClass );
198     }
199 
200     public static String getNumericCast( Class<? extends Number> type )
201     {
202         return numericCasts.get( type );
203     }
204 
205     public static String getNumericLiteral( Class<? extends Number> type )
206     {
207         return numericLiterals.get( type );
208     }
209 
210     public static void setCompiler( OgnlExpressionCompiler compiler )
211     {
212         OgnlRuntime.compiler = compiler;
213     }
214 
215     /**
216      * @deprecated use getCompiler(OgnlContext) instead
217      */
218     public static OgnlExpressionCompiler getCompiler()
219     {
220         return getCompiler( null );
221     }
222 
223     public static OgnlExpressionCompiler getCompiler( OgnlContext ognlContext )
224     {
225         if ( compiler == null )
226         {
227             try
228             {
229                 OgnlRuntime.classForName( ognlContext, "javassist.ClassPool" );
230                 compiler = new ExpressionCompiler();
231             }
232             catch ( ClassNotFoundException e )
233             {
234                 throw new IllegalArgumentException(
235                     "Javassist library is missing in classpath! Please add missed dependency!", e );
236             }
237         }
238         return compiler;
239     }
240 
241     public static void compileExpression( OgnlContext context, Node expression, Object root )
242         throws Exception
243     {
244         getCompiler( context ).compileExpression( context, expression, root );
245     }
246 
247     /**
248      * Gets the "target" class of an object for looking up accessors that are registered on the target. If the object is
249      * a Class object this will return the Class itself, else it will return object's getClass() result.
250      */
251     public static Class<?> getTargetClass( Object o )
252     {
253         return ( o == null ) ? null : ( ( o instanceof Class ) ? (Class<?>) o : o.getClass() );
254     }
255 
256     /**
257      * Returns the base name (the class name without the package name prepended) of the object given.
258      */
259     public static String getBaseName( Object o )
260     {
261         return ( o == null ) ? null : getClassBaseName( o.getClass() );
262     }
263 
264     /**
265      * Returns the base name (the class name without the package name prepended) of the class given.
266      */
267     public static String getClassBaseName( Class<?> clazz )
268     {
269         String className = clazz.getName();
270         return className.substring( className.lastIndexOf( '.' ) + 1 );
271     }
272 
273     public static String getClassName( Object object, boolean fullyQualified )
274     {
275         if ( !( object instanceof Class ) )
276         {
277             object = object.getClass();
278         }
279 
280         return getClassName( (Class<?>) object, fullyQualified );
281     }
282 
283     public static String getClassName( Class<?> clazz, boolean fullyQualified )
284     {
285         return fullyQualified ? clazz.getName() : getClassBaseName( clazz );
286     }
287 
288     /**
289      * Returns the package name of the object's class.
290      */
291     public static String getPackageName( Object object )
292     {
293         return ( object == null ) ? null : getClassPackageName( object.getClass() );
294     }
295 
296     /**
297      * Returns the package name of the class given.
298      */
299     public static String getClassPackageName( Class<?> clazz )
300     {
301         String className = clazz.getName();
302         int index = className.lastIndexOf( '.' );
303 
304         return ( index < 0 ) ? null : className.substring( 0, index );
305     }
306 
307     /**
308      * Returns a "pointer" string in the usual format for these things - 0x<hex digits>.
309      */
310     public static String getPointerString( int num )
311     {
312         String hex = Integer.toHexString( num ), pad;
313         Integer l = hex.length();
314 
315         // stringBuilder.append(HEX_PREFIX);
316         if ( ( pad = HEX_PADDING.get( l ) ) == null )
317         {
318             StringBuilder paddingStringBuilder = new StringBuilder();
319 
320             for ( int i = hex.length(); i < HEX_LENGTH; i++ )
321             {
322                 paddingStringBuilder.append( '0' );
323             }
324             pad = paddingStringBuilder.toString();
325             HEX_PADDING.put( l, pad );
326         }
327         return new StringBuilder().append( pad ).append( hex ).toString();
328     }
329 
330     /**
331      * Returns a "pointer" string in the usual format for these things - 0x<hex digits> for the object given. This will
332      * always return a unique value for each object.
333      */
334     public static String getPointerString( Object object )
335     {
336         return getPointerString( ( object == null ) ? 0 : System.identityHashCode( object ) );
337     }
338 
339     /**
340      * Returns a unique descriptor string that includes the object's class and a unique integer identifier. If
341      * fullyQualified is true then the class name will be fully qualified to include the package name, else it will be
342      * just the class' base name.
343      */
344     public static String getUniqueDescriptor( Object object, boolean fullyQualified )
345     {
346         StringBuilder stringBuilder = new StringBuilder();
347 
348         if ( object != null )
349         {
350             if ( object instanceof Proxy )
351             {
352                 Class<?> interfaceClass = object.getClass().getInterfaces()[0];
353 
354                 String className = getClassName( interfaceClass, fullyQualified );
355                 stringBuilder.append( className ).append( '^' );
356                 object = Proxy.getInvocationHandler( object );
357             }
358             String className = getClassName( object, fullyQualified );
359             String pointerString = getPointerString( object );
360             stringBuilder.append( className ).append( '@' ).append( pointerString );
361         }
362         else
363         {
364             stringBuilder.append( NULL_OBJECT_STRING );
365         }
366         return stringBuilder.toString();
367     }
368 
369     /**
370      * Returns a unique descriptor string that includes the object's class' base name and a unique integer identifier.
371      */
372     public static String getUniqueDescriptor( Object object )
373     {
374         return getUniqueDescriptor( object, false );
375     }
376 
377     /**
378      * Utility to convert a List into an Object[] array. If the list is zero elements this will return a constant array;
379      * toArray() on List always returns a new object and this is wasteful for our purposes.
380      */
381     public static <T> Object[] toArray( List<T> list )
382     {
383         Object[] array;
384         int size = list.size();
385 
386         if ( size == 0 )
387         {
388             array = NoArguments;
389         }
390         else
391         {
392             array = getObjectArrayPool().create( size );
393             for ( int i = 0; i < size; i++ )
394             {
395                 array[i] = list.get( i );
396             }
397         }
398         return array;
399     }
400 
401     /**
402      * Returns the parameter types of the given method.
403      */
404     public static Class<?>[] getParameterTypes( Method method )
405         throws CacheException
406     {
407         return cache.getMethodParameterTypes( method );
408     }
409 
410     /**
411      * Finds the appropriate parameter types for the given {@link Method} and {@link Class} instance of the type the
412      * method is associated with. Correctly finds generic types if running in >= 1.5 jre as well.
413      *
414      * @param type The class type the method is being executed against.
415      * @param method    The method to find types for.
416      * @return Array of parameter types for the given method.
417      * @throws org.apache.commons.ognl.internal.CacheException
418      */
419     public static Class<?>[] findParameterTypes( Class<?> type, Method method )
420         throws CacheException
421     {
422         if ( type == null || type.getGenericSuperclass() == null || !ParameterizedType.class.isInstance(
423             type.getGenericSuperclass() ) || method.getDeclaringClass().getTypeParameters() == null )
424         {
425             return getParameterTypes( method );
426         }
427 
428         GenericMethodParameterTypeCacheEntry key = new GenericMethodParameterTypeCacheEntry( method, type );
429         return cache.getGenericMethodParameterTypes( key );
430     }
431 
432     /**
433      * Returns the parameter types of the given method.
434      * @param constructor
435      * @return
436      * @throws org.apache.commons.ognl.internal.CacheException
437      */
438     public static Class<?>[] getParameterTypes( Constructor<?> constructor )
439         throws CacheException
440     {
441         return cache.getParameterTypes( constructor );
442     }
443 
444     /**
445      * Gets the SecurityManager that OGNL uses to determine permissions for invoking methods.
446      *
447      * @return SecurityManager for OGNL
448      */
449     public static SecurityManager getSecurityManager()
450     {
451         return securityManager;
452     }
453 
454     /**
455      * Sets the SecurityManager that OGNL uses to determine permissions for invoking methods.
456      *
457      * @param securityManager SecurityManager to set
458      */
459     public static void setSecurityManager( SecurityManager securityManager )
460     {
461         OgnlRuntime.securityManager = securityManager;
462         cache.setSecurityManager(securityManager);
463     }
464 
465     /**
466      * Permission will be named "invoke.<declaring-class>.<method-name>".
467      * @param method
468      * @return
469      * @throws org.apache.commons.ognl.internal.CacheException
470      */
471     public static Permission getPermission( Method method )
472         throws CacheException
473     {
474         PermissionCacheEntry key = new PermissionCacheEntry( method );
475         return cache.getInvokePermission( key );
476     }
477 
478     public static Object invokeMethod( Object target, Method method, Object[] argsArray )
479         throws InvocationTargetException, IllegalAccessException, CacheException
480     {
481         Object result;
482 
483         if ( securityManager != null )
484         {
485             if ( !cache.getMethodPerm( method ) )
486             {
487                 throw new IllegalAccessException( "Method [" + method + "] cannot be accessed." );
488             }
489         }
490 
491         MethodAccessEntryValue entry = cache.getMethodAccess( method );
492         if ( !entry.isAccessible() )
493         {
494             // only synchronize method invocation if it actually requires it
495             synchronized ( method )
496             {
497 
498                 if ( entry.isNotPublic() && !entry.isAccessible() )
499                 {
500                     method.setAccessible( true );
501                 }
502 
503                 result = method.invoke( target, argsArray );
504 
505                 if ( !entry.isAccessible() )
506                 {
507                     method.setAccessible( false );
508                 }
509             }
510         }
511         else
512         {
513             result = method.invoke( target, argsArray );
514         }
515 
516         return result;
517     }
518 
519     /**
520      * Gets the class for a method argument that is appropriate for looking up methods by reflection, by looking for the
521      * standard primitive wrapper classes and exchanging for them their underlying primitive class objects. Other
522      * classes are passed through unchanged.
523      *
524      * @param arg an object that is being passed to a method
525      * @return the class to use to look up the method
526      */
527     public static Class<?> getArgClass( Object arg )
528     {
529         if ( arg == null )
530         {
531             return null;
532         }
533         Class<?> clazz = arg.getClass();
534         if ( clazz == Boolean.class )
535         {
536             return Boolean.TYPE;
537         }
538         else if ( clazz.getSuperclass() == Number.class )
539         {
540             if ( clazz == Integer.class )
541             {
542                 return Integer.TYPE;
543             }
544             if ( clazz == Double.class )
545             {
546                 return Double.TYPE;
547             }
548             if ( clazz == Byte.class )
549             {
550                 return Byte.TYPE;
551             }
552             if ( clazz == Long.class )
553             {
554                 return Long.TYPE;
555             }
556             if ( clazz == Float.class )
557             {
558                 return Float.TYPE;
559             }
560             if ( clazz == Short.class )
561             {
562                 return Short.TYPE;
563             }
564         }
565         else if ( clazz == Character.class )
566         {
567             return Character.TYPE;
568         }
569         return clazz;
570     }
571 
572     /**
573      * Tells whether the given object is compatible with the given class ---that is, whether the given object can be
574      * passed as an argument to a method or constructor whose parameter type is the given class. If object is null this
575      * will return true because null is compatible with any type.
576      */
577     public static boolean isTypeCompatible( Object object, Class<?> clazz )
578     {
579         boolean result = true;
580 
581         if ( object != null )
582         {
583             if ( clazz.isPrimitive() )
584             {
585                 if ( getArgClass( object ) != clazz )
586                 {
587                     result = false;
588                 }
589             }
590             else if ( !clazz.isInstance( object ) )
591             {
592                 result = false;
593             }
594         }
595         return result;
596     }
597 
598     /**
599      * Tells whether the given array of objects is compatible with the given array of classes---that is, whether the
600      * given array of objects can be passed as arguments to a method or constructor whose parameter types are the given
601      * array of classes.
602      */
603     public static boolean areArgsCompatible( Object[] args, Class<?>[] classes )
604     {
605         return areArgsCompatible( args, classes, null );
606     }
607 
608     public static boolean areArgsCompatible( Object[] args, Class<?>[] classes, Method method )
609     {
610         boolean result = true;
611         boolean varArgs = method != null && method.isVarArgs();
612 
613         if ( args.length != classes.length && !varArgs )
614         {
615             result = false;
616         }
617         else if ( varArgs )
618         {
619             for ( int index = 0; result && ( index < args.length ); ++index )
620             {
621                 if ( index >= classes.length )
622                 {
623                     break;
624                 }
625 
626                 result = isTypeCompatible( args[index], classes[index] );
627 
628                 if ( !result && classes[index].isArray() )
629                 {
630                     result = isTypeCompatible( args[index], classes[index].getComponentType() );
631                 }
632             }
633         }
634         else
635         {
636             for ( int index = 0; result && ( index < args.length ); ++index )
637             {
638                 result = isTypeCompatible( args[index], classes[index] );
639             }
640         }
641         return result;
642     }
643 
644     /**
645      * Tells whether the first array of classes is more specific than the second. Assumes that the two arrays are of the
646      * same length.
647      */
648     public static boolean isMoreSpecific( Class<?>[] classes1, Class<?>[] classes2 )
649     {
650         for ( int index = 0; index < classes1.length; ++index )
651         {
652             Class<?> class1 = classes1[index], class2 = classes2[index];
653             if ( class1 != class2 )
654             {
655                 if ( class1.isPrimitive() )
656                 {
657                     return true;
658                 }
659                 else if ( class1.isAssignableFrom( class2 ) )
660                 {
661                     return false;
662                 }
663                 else if ( class2.isAssignableFrom( class1 ) )
664                 {
665                     return true;
666                 }
667             }
668         }
669 
670         // They are the same! So the first is not more specific than the second.
671         return false;
672     }
673 
674     /**
675      * @deprecated This method is no longer used.
676      * @param modifiers
677      * @return
678      */
679     public static String getModifierString( int modifiers )
680     {
681         String modifierString;
682 
683         if ( Modifier.isPublic( modifiers ) )
684         {
685             modifierString = "public";
686         }
687         else if ( Modifier.isProtected( modifiers ) )
688         {
689             modifierString = "protected";
690         }
691         else if ( Modifier.isPrivate( modifiers ) )
692         {
693             modifierString = "private";
694         }
695         else
696         {
697             modifierString = "";
698         }
699         if ( Modifier.isStatic( modifiers ) )
700         {
701             modifierString = "static " + modifierString;
702         }
703         if ( Modifier.isFinal( modifiers ) )
704         {
705             modifierString = "final " + modifierString;
706         }
707         if ( Modifier.isNative( modifiers ) )
708         {
709             modifierString = "native " + modifierString;
710         }
711         if ( Modifier.isSynchronized( modifiers ) )
712         {
713             modifierString = "synchronized " + modifierString;
714         }
715         if ( Modifier.isTransient( modifiers ) )
716         {
717             modifierString = "transient " + modifierString;
718         }
719         return modifierString;
720     }
721 
722     public static Class<?> classForName( OgnlContext context, String className )
723         throws ClassNotFoundException
724     {
725         Class<?> result = primitiveTypes.get( className );
726 
727         if ( result == null )
728         {
729             ClassResolver resolver;
730 
731             if ( ( context == null ) || ( ( resolver = context.getClassResolver() ) == null ) )
732             {
733                 resolver = OgnlContext.DEFAULT_CLASS_RESOLVER;
734             }
735             result = resolver.classForName( className, context );
736         }
737 
738         if ( result == null )
739         {
740             throw new ClassNotFoundException( "Unable to resolve class: " + className );
741         }
742 
743         return result;
744     }
745 
746     public static boolean isInstance( OgnlContext context, Object value, String className )
747         throws OgnlException
748     {
749         try
750         {
751             Class<?> clazz = classForName( context, className );
752             return clazz.isInstance( value );
753         }
754         catch ( ClassNotFoundException e )
755         {
756             throw new OgnlException( "No such class: " + className, e );
757         }
758     }
759 
760     public static Object getPrimitiveDefaultValue( Class<?> forClass )
761     {
762         return primitiveDefaults.get( forClass );
763     }
764 
765     public static Object getNumericDefaultValue( Class<?> forClass )
766     {
767         return numericDefaults.get( forClass );
768     }
769 
770     public static Object getConvertedType( OgnlContext context, Object target, Member member, String propertyName,
771                                            Object value, Class<?> type )
772     {
773         return context.getTypeConverter().convertValue( context, target, member, propertyName, value, type );
774     }
775 
776     public static boolean getConvertedTypes( OgnlContext context, Object target, Member member, String propertyName,
777                                              Class<?>[] parameterTypes, Object[] args, Object[] newArgs )
778         
779     {
780         boolean result = false;
781 
782         if ( parameterTypes.length == args.length )
783         {
784             result = true;
785             for ( int i = 0; result && ( i <= parameterTypes.length - 1 ); i++ )
786             {
787                 Object arg = args[i];
788                 Class<?> type = parameterTypes[i];
789 
790                 if ( isTypeCompatible( arg, type ) )
791                 {
792                     newArgs[i] = arg;
793                 }
794                 else
795                 {
796                     Object convertedType = getConvertedType( context, target, member, propertyName, arg, type );
797 
798                     if ( convertedType == OgnlRuntime.NoConversionPossible )
799                     {
800                         result = false;
801                     }
802                     else
803                     {
804                         newArgs[i] = convertedType;
805                     }
806                 }
807             }
808         }
809         return result;
810     }
811 
812     public static Method getConvertedMethodAndArgs( OgnlContext context, Object target, String propertyName,
813                                                     List<Method> methods, Object[] args, Object[] newArgs )
814         
815     {
816         Method convertedMethod = null;
817         TypeConverter typeConverter = context.getTypeConverter();
818 
819         if ( ( typeConverter != null ) && ( methods != null ) )
820         {
821             int methodsSize = methods.size();
822             for ( int i = 0; ( convertedMethod == null ) && ( i < methodsSize ); i++ )
823             {
824                 Method method = methods.get( i );
825                 Class<?>[] parameterTypes =
826                     findParameterTypes( target != null ? target.getClass() : null, method );// getParameterTypes(method);
827 
828                 if ( getConvertedTypes( context, target, method, propertyName, parameterTypes, args, newArgs ) )
829                 {
830                     convertedMethod = method;
831                 }
832             }
833         }
834         return convertedMethod;
835     }
836 
837     public static Constructor<?> getConvertedConstructorAndArgs( OgnlContext context, Object target,
838                                                                  List<Constructor<?>> constructors, Object[] args,
839                                                                  Object[] newArgs )
840         
841     {
842         Constructor<?> constructor = null;
843         TypeConverter typeConverter = context.getTypeConverter();
844 
845         if ( ( typeConverter != null ) && ( constructors != null ) )
846         {
847             for ( int i = 0; ( constructor == null ) && ( i < constructors.size() ); i++ )
848             {
849                 Constructor<?> ctor = constructors.get( i );
850                 Class<?>[] parameterTypes = getParameterTypes( ctor );
851 
852                 if ( getConvertedTypes( context, target, ctor, null, parameterTypes, args, newArgs ) )
853                 {
854                     constructor = ctor;
855                 }
856             }
857         }
858         return constructor;
859     }
860 
861     /**
862      * Gets the appropriate method to be called for the given target, method name and arguments. If successful this
863      * method will return the Method within the target that can be called and the converted arguments in actualArgs. If
864      * unsuccessful this method will return null and the actualArgs will be empty.
865      *
866      * @param context      The current execution context.
867      * @param source       Target object to run against or method name.
868      * @param target       Instance of object to be run against.
869      * @param propertyName Name of property to get method of.
870      * @param methods      List of current known methods.
871      * @param args         Arguments originally passed in.
872      * @param actualArgs   Converted arguments.
873      * @return Best method match or null if none could be found.
874      */
875     public static Method getAppropriateMethod( OgnlContext context, Object source, Object target, String propertyName,
876                                                List<Method> methods, Object[] args, Object[] actualArgs )
877         
878     {
879         Method appropriateMethod = null;
880         Class<?>[] resultParameterTypes = null;
881 
882         if ( methods != null )
883         {
884             for ( Method method : methods )
885             {
886                 Class<?> typeClass = target != null ? target.getClass() : null;
887                 if ( typeClass == null && source != null && Class.class.isInstance( source ) )
888                 {
889                     typeClass = (Class<?>) source;
890                 }
891 
892                 Class<?>[] mParameterTypes = findParameterTypes( typeClass, method );
893 
894                 if ( areArgsCompatible( args, mParameterTypes, method ) &&
895                     ( ( appropriateMethod == null ) || isMoreSpecific( mParameterTypes, resultParameterTypes ) ) )
896                 {
897                     appropriateMethod = method;
898                     resultParameterTypes = mParameterTypes;
899                     System.arraycopy( args, 0, actualArgs, 0, args.length );
900 
901                     for ( int i = 0; i < mParameterTypes.length; i++ )
902                     {
903                         Class<?> type = mParameterTypes[i];
904 
905                         if ( type.isPrimitive() && ( actualArgs[i] == null ) )
906                         {
907                             actualArgs[i] = getConvertedType( context, source, appropriateMethod, propertyName, null, type );
908                         }
909                     }
910                 }
911             }
912         }
913 
914         if ( appropriateMethod == null )
915         {
916             appropriateMethod = getConvertedMethodAndArgs( context, target, propertyName, methods, args, actualArgs );
917         }
918 
919         return appropriateMethod;
920     }
921 
922     public static Object callAppropriateMethod( OgnlContext context, Object source, Object target, String methodName,
923                                                 String propertyName, List<Method> methods, Object[] args )
924         throws MethodFailedException
925     {
926         Throwable cause = null;
927         Object[] actualArgs = objectArrayPool.create( args.length );
928 
929         try
930         {
931             Method method = getAppropriateMethod( context, source, target, propertyName, methods, args, actualArgs );
932 
933             if ( ( method == null ) || !isMethodAccessible( context, source, method, propertyName ) )
934             {
935                 StringBuilder buffer = new StringBuilder();
936                 String className = "";
937 
938                 if ( target != null )
939                 {
940                     className = target.getClass().getName() + ".";
941                 }
942 
943                 for ( int i = 0, ilast = args.length - 1; i <= ilast; i++ )
944                 {
945                     Object arg = args[i];
946 
947                     buffer.append( ( arg == null ) ? NULL_STRING : arg.getClass().getName() );
948                     if ( i < ilast )
949                     {
950                         buffer.append( ", " );
951                     }
952                 }
953 
954                 throw new NoSuchMethodException( className + methodName + "(" + buffer + ")" );
955             }
956 
957             Object[] convertedArgs = actualArgs;
958 
959             if ( method.isVarArgs() )
960             {
961                 Class<?>[] parmTypes = method.getParameterTypes();
962 
963                 // split arguments in to two dimensional array for varargs reflection invocation
964                 // where it is expected that the parameter passed in to invoke the method
965                 // will look like "new Object[] { arrayOfNonVarArgsArguments, arrayOfVarArgsArguments }"
966 
967                 for ( int i = 0; i < parmTypes.length; i++ )
968                 {
969                     if ( parmTypes[i].isArray() )
970                     {
971                         convertedArgs = new Object[i + 1];
972                         System.arraycopy( actualArgs, 0, convertedArgs, 0, convertedArgs.length );
973 
974                         Object[] varArgs;
975 
976                         // if they passed in varargs arguments grab them and dump in to new varargs array
977 
978                         if ( actualArgs.length > i )
979                         {
980                             List<Object> varArgsList = new ArrayList<Object>();
981                             for ( int j = i; j < actualArgs.length; j++ )
982                             {
983                                 if ( actualArgs[j] != null )
984                                 {
985                                     varArgsList.add( actualArgs[j] );
986                                 }
987                             }
988 
989                             varArgs = varArgsList.toArray();
990                         }
991                         else
992                         {
993                             varArgs = new Object[0];
994                         }
995 
996                         convertedArgs[i] = varArgs;
997                         break;
998                     }
999                 }
1000             }
1001 
1002             return invokeMethod( target, method, convertedArgs );
1003 
1004         }
1005         catch ( NoSuchMethodException e )
1006         {
1007             cause = e;
1008         }
1009         catch ( IllegalAccessException e )
1010         {
1011             cause = e;
1012         }
1013         catch ( InvocationTargetException e )
1014         {
1015             cause = e.getTargetException();
1016         }
1017         finally
1018         {
1019             objectArrayPool.recycle( actualArgs );
1020         }
1021 
1022         throw new MethodFailedException( source, methodName, cause );
1023     }
1024 
1025     public static Object callStaticMethod( OgnlContext context, String className, String methodName, Object[] args )
1026         throws OgnlException
1027     {
1028         try
1029         {
1030             Class<?> targetClass = classForName( context, className );
1031             if ( targetClass == null )
1032             {
1033                 throw new ClassNotFoundException( "Unable to resolve class with name " + className );
1034             }
1035 
1036             MethodAccessor methodAccessor = getMethodAccessor( targetClass );
1037 
1038             return methodAccessor.callStaticMethod( context, targetClass, methodName, args );
1039         }
1040         catch ( ClassNotFoundException ex )
1041         {
1042             throw new MethodFailedException( className, methodName, ex );
1043         }
1044     }
1045 
1046     /**
1047      * Invokes the specified method against the target object.
1048      *
1049      * @param context    The current execution context.
1050      * @param target     The object to invoke the method on.
1051      * @param methodName Name of the method - as in "getValue" or "add", etc..
1052      * @param args       Optional arguments needed for method.
1053      * @return Result of invoking method.
1054      * @throws OgnlException For lots of different reasons.
1055      */
1056     public static Object callMethod( OgnlContext context, Object target, String methodName, Object[] args )
1057         throws OgnlException
1058     {
1059         if ( target == null )
1060         {
1061             throw new NullPointerException( "target is null for method " + methodName );
1062         }
1063 
1064         return getMethodAccessor( target.getClass() ).callMethod( context, target, methodName, args );
1065     }
1066 
1067     public static Object callConstructor( OgnlContext context, String className, Object[] args )
1068         throws OgnlException
1069     {
1070         Throwable cause = null;
1071         Object[] actualArgs = args;
1072 
1073         try
1074         {
1075             Constructor<?> ctor = null;
1076             Class<?>[] ctorParameterTypes = null;
1077             Class<?> target = classForName( context, className );
1078             List<Constructor<?>> constructors = getConstructors( target );
1079 
1080             for ( Constructor<?> constructor : constructors )
1081             {
1082                 Class<?>[] cParameterTypes = getParameterTypes( constructor );
1083 
1084                 if ( areArgsCompatible( args, cParameterTypes ) && ( ctor == null || isMoreSpecific( cParameterTypes,
1085                                                                                                      ctorParameterTypes ) ) )
1086                 {
1087                     ctor = constructor;
1088                     ctorParameterTypes = cParameterTypes;
1089                 }
1090             }
1091             if ( ctor == null )
1092             {
1093                 actualArgs = objectArrayPool.create( args.length );
1094                 if ( ( ctor = getConvertedConstructorAndArgs( context, target, constructors, args, actualArgs ) )
1095                     == null )
1096                 {
1097                     throw new NoSuchMethodException();
1098                 }
1099             }
1100             if ( !context.getMemberAccess().isAccessible( context, target, ctor, null ) )
1101             {
1102                 throw new IllegalAccessException( "access denied to " + target.getName() + "()" );
1103             }
1104             return ctor.newInstance( actualArgs );
1105         }
1106         catch ( ClassNotFoundException e )
1107         {
1108             cause = e;
1109         }
1110         catch ( NoSuchMethodException e )
1111         {
1112             cause = e;
1113         }
1114         catch ( IllegalAccessException e )
1115         {
1116             cause = e;
1117         }
1118         catch ( InvocationTargetException e )
1119         {
1120             cause = e.getTargetException();
1121         }
1122         catch ( InstantiationException e )
1123         {
1124             cause = e;
1125         }
1126         finally
1127         {
1128             if ( actualArgs != args )
1129             {
1130                 objectArrayPool.recycle( actualArgs );
1131             }
1132         }
1133 
1134         throw new MethodFailedException( className, "new", cause );
1135     }
1136 
1137     public static Object getMethodValue( OgnlContext context, Object target, String propertyName )
1138         throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
1139     {
1140         return getMethodValue( context, target, propertyName, false );
1141     }
1142 
1143     /**
1144      * If the checkAccessAndExistence flag is true this method will check to see if the method exists and if it is
1145      * accessible according to the context's MemberAccess. If neither test passes this will return NotFound.
1146      */
1147     public static Object getMethodValue( OgnlContext context, Object target, String propertyName,
1148                                          boolean checkAccessAndExistence )
1149         throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
1150     {
1151         Object methodValue = null;
1152         Class<?> targetClass = target == null ? null : target.getClass();
1153         Method method = getGetMethod( context, targetClass, propertyName );
1154         if ( method == null )
1155         {
1156             method = getReadMethod( targetClass, propertyName, 0 );
1157         }
1158 
1159         if ( checkAccessAndExistence )
1160         {
1161             if ( ( method == null ) || !context.getMemberAccess().isAccessible( context, target, method, propertyName ) )
1162             {
1163                 methodValue = NotFound;
1164             }
1165         }
1166         if ( methodValue == null )
1167         {
1168             if ( method != null )
1169             {
1170                 try
1171                 {
1172                     methodValue = invokeMethod( target, method, NoArguments );
1173                 }
1174                 catch ( InvocationTargetException ex )
1175                 {
1176                     throw new OgnlException( propertyName, ex.getTargetException() );
1177                 }
1178             }
1179             else
1180             {
1181                 throw new NoSuchMethodException( propertyName );
1182             }
1183         }
1184         return methodValue;
1185     }
1186 
1187     public static boolean setMethodValue( OgnlContext context, Object target, String propertyName, Object value )
1188         throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
1189     {
1190         return setMethodValue( context, target, propertyName, value, false );
1191     }
1192 
1193     public static boolean setMethodValue( OgnlContext context, Object target, String propertyName, Object value,
1194                                           boolean checkAccessAndExistence )
1195         throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
1196     {
1197         boolean result = true;
1198         Method method = getSetMethod( context, ( target == null ) ? null : target.getClass(), propertyName );
1199 
1200         if ( checkAccessAndExistence )
1201         {
1202             if ( ( method == null ) || !context.getMemberAccess().isAccessible( context, target, method, propertyName ) )
1203             {
1204                 result = false;
1205             }
1206         }
1207 
1208         if ( result )
1209         {
1210             if ( method != null )
1211             {
1212                 Object[] args = objectArrayPool.create( value );
1213 
1214                 try
1215                 {
1216                     callAppropriateMethod( context, target, target, method.getName(), propertyName,
1217                                            Collections.nCopies( 1, method ), args );
1218                 }
1219                 finally
1220                 {
1221                     objectArrayPool.recycle( args );
1222                 }
1223             }
1224             else
1225             {
1226                 result = false;
1227             }
1228         }
1229 
1230         return result;
1231     }
1232 
1233     public static List<Constructor<?>> getConstructors( Class<?> targetClass )
1234     {
1235         return cache.getConstructor( targetClass );
1236     }
1237 
1238     /**
1239      * @param targetClass
1240      * @param staticMethods if true (false) returns only the (non-)static methods
1241      * @return Returns the map of methods for a given class
1242      */
1243     public static Map<String, List<Method>> getMethods( Class<?> targetClass, boolean staticMethods )
1244     {
1245         DeclaredMethodCacheEntry.MethodType type = staticMethods ?
1246             DeclaredMethodCacheEntry.MethodType.STATIC :
1247             DeclaredMethodCacheEntry.MethodType.NON_STATIC;
1248         DeclaredMethodCacheEntry key = new DeclaredMethodCacheEntry( targetClass, type );
1249         return cache.getMethod( key );
1250     }
1251 
1252     public static List<Method> getMethods( Class<?> targetClass, String name, boolean staticMethods )
1253     {
1254         return getMethods( targetClass, staticMethods ).get( name );
1255     }
1256 
1257     public static Map<String, Field> getFields( Class<?> targetClass )
1258     {
1259         return cache.getField( targetClass );
1260     }
1261 
1262     public static Field getField( Class<?> inClass, String name )
1263     {
1264         Field field = getFields( inClass ).get( name );
1265 
1266         if ( field == null )
1267         {
1268             // if field is null, it should search along the superclasses
1269             Class<?> superClass = inClass.getSuperclass();
1270             while ( superClass != null )
1271             {
1272                 field = getFields( superClass ).get( name );
1273                 if ( field != null )
1274                 {
1275                     return field;
1276                 }
1277                 superClass = superClass.getSuperclass();
1278             }
1279         }
1280         return field;
1281     }
1282 
1283     public static Object getFieldValue( OgnlContext context, Object target, String propertyName )
1284         throws NoSuchFieldException
1285     {
1286         return getFieldValue( context, target, propertyName, false );
1287     }
1288 
1289     public static Object getFieldValue( OgnlContext context, Object target, String propertyName,
1290                                         boolean checkAccessAndExistence )
1291         throws NoSuchFieldException
1292     {
1293         Object result = null;
1294         Class<?> targetClass = target == null ? null : target.getClass();
1295         Field field = getField( targetClass, propertyName );
1296 
1297         if ( checkAccessAndExistence )
1298         {
1299             if ( ( field == null ) || !context.getMemberAccess().isAccessible( context, target, field, propertyName ) )
1300             {
1301                 result = NotFound;
1302             }
1303         }
1304         if ( result == null )
1305         {
1306             if ( field == null )
1307             {
1308                 throw new NoSuchFieldException( propertyName );
1309             }
1310             try
1311             {
1312                 Object state;
1313 
1314                 if ( !Modifier.isStatic( field.getModifiers() ) )
1315                 {
1316                     state = context.getMemberAccess().setup( context, target, field, propertyName );
1317                     result = field.get( target );
1318                     context.getMemberAccess().restore( context, target, field, propertyName, state );
1319                 }
1320                 else
1321                 {
1322                     throw new NoSuchFieldException( propertyName );
1323                 }
1324 
1325             }
1326             catch ( IllegalAccessException ex )
1327             {
1328                 throw new NoSuchFieldException( propertyName );
1329             }
1330         }
1331         return result;
1332     }
1333 
1334     public static boolean setFieldValue( OgnlContext context, Object target, String propertyName, Object value )
1335         throws OgnlException
1336     {
1337         boolean result = false;
1338 
1339         try
1340         {
1341             Class<?> targetClass = target == null ? null : target.getClass();
1342             Field field = getField( targetClass, propertyName );
1343             Object state;
1344 
1345             if ( ( field != null ) && !Modifier.isStatic( field.getModifiers() ) )
1346             {
1347                 state = context.getMemberAccess().setup( context, target, field, propertyName );
1348                 try
1349                 {
1350                     if ( isTypeCompatible( value, field.getType() ) || (
1351                         ( value = getConvertedType( context, target, field, propertyName, value, field.getType() ) ) != null ) )
1352                     {
1353                         field.set( target, value );
1354                         result = true;
1355                     }
1356                 }
1357                 finally
1358                 {
1359                     context.getMemberAccess().restore( context, target, field, propertyName, state );
1360                 }
1361             }
1362         }
1363         catch ( IllegalAccessException ex )
1364         {
1365             throw new NoSuchPropertyException( target, propertyName, ex );
1366         }
1367         return result;
1368     }
1369 
1370     public static boolean isFieldAccessible( OgnlContext context, Object target, Class<?> inClass, String propertyName )
1371     {
1372         return isFieldAccessible( context, target, getField( inClass, propertyName ), propertyName );
1373     }
1374 
1375     public static boolean isFieldAccessible( OgnlContext context, Object target, Field field, String propertyName )
1376     {
1377         return context.getMemberAccess().isAccessible( context, target, field, propertyName );
1378     }
1379 
1380     public static boolean hasField( OgnlContext context, Object target, Class<?> inClass, String propertyName )
1381     {
1382         Field field = getField( inClass, propertyName );
1383 
1384         return ( field != null ) && isFieldAccessible( context, target, field, propertyName );
1385     }
1386 
1387     public static Object getStaticField( OgnlContext context, String className, String fieldName )
1388         throws OgnlException
1389     {
1390         Exception cause;
1391         try
1392         {
1393             Class<?> clazz = classForName( context, className );
1394 
1395             if ( clazz == null )
1396             {
1397                 throw new OgnlException(
1398                     "Unable to find class " + className + " when resolving field name of " + fieldName );
1399             }
1400 
1401             /*
1402              * Check for virtual static field "class"; this cannot interfere with normal static fields because it is a
1403              * reserved word.
1404              */
1405             if ( "class".equals( fieldName ) )
1406             {
1407                 return clazz;
1408             }
1409             else if ( clazz.isEnum() )
1410             {
1411                 return Enum.valueOf( (Class<? extends Enum>) clazz, fieldName );
1412             }
1413             else
1414             {
1415                 Field field = clazz.getField( fieldName );
1416                 if ( !Modifier.isStatic(field.getModifiers()) )
1417                 {
1418                     throw new OgnlException( "Field " + fieldName + " of class " + className + " is not static" );
1419                 }
1420 
1421                 return field.get( null );
1422             }
1423         }
1424         catch ( ClassNotFoundException e )
1425         {
1426             cause = e;
1427         }
1428         catch ( NoSuchFieldException e )
1429         {
1430             cause = e;
1431         }
1432         catch ( SecurityException e )
1433         {
1434             cause = e;
1435         }
1436         catch ( IllegalAccessException e )
1437         {
1438             cause = e;
1439         }
1440 
1441         throw new OgnlException( "Could not get static field " + fieldName + " from class " + className, cause );
1442     }
1443 
1444     /**
1445      * @param targetClass
1446      * @param propertyName
1447      * @param findSets
1448      * @return Returns the list of (g)setter of a class for a given property name
1449      * @
1450      */
1451     public static List<Method> getDeclaredMethods( Class<?> targetClass, String propertyName, boolean findSets )
1452     {
1453         String baseName = Character.toUpperCase( propertyName.charAt( 0 ) ) + propertyName.substring( 1 );
1454         List<Method> methods = new ArrayList<Method>();
1455         List<String> methodNames = new ArrayList<String>( 2 );
1456         if ( findSets )
1457         {
1458             methodNames.add( SET_PREFIX + baseName );
1459         }
1460         else
1461         {
1462             methodNames.add( IS_PREFIX + baseName );
1463             methodNames.add( GET_PREFIX + baseName );
1464         }
1465         for ( String methodName : methodNames )
1466         {
1467             DeclaredMethodCacheEntry key = new DeclaredMethodCacheEntry( targetClass );
1468             List<Method> methodList = cache.getMethod( key ).get( methodName );
1469             if ( methodList != null )
1470             {
1471                 methods.addAll( methodList );
1472             }
1473         }
1474 
1475         return methods;
1476     }
1477 
1478     /**
1479      * Convenience used to check if a method is volatile or synthetic so as to avoid calling un-callable methods.
1480      *
1481      * @param method The method to check.
1482      * @return True if the method should be callable, false otherwise.
1483      */
1484     //TODO: the method was intended as private, so it'd need to move in a util class
1485     public static boolean isMethodCallable( Method method )
1486     {
1487         return !( method.isSynthetic() || Modifier.isVolatile( method.getModifiers() ) );
1488 
1489     }
1490 
1491     public static Method getGetMethod( OgnlContext unused, Class<?> targetClass, String propertyName )
1492         throws IntrospectionException, OgnlException
1493     {
1494         Method result = null;
1495 
1496         List<Method> methods = getDeclaredMethods( targetClass, propertyName, false /* find 'get' methods */ );
1497 
1498         if ( methods != null )
1499         {
1500             for ( Method method : methods )
1501             {
1502                 Class<?>[] mParameterTypes = findParameterTypes( targetClass, method ); // getParameterTypes(method);
1503 
1504                 if ( mParameterTypes.length == 0 )
1505                 {
1506                     result = method;
1507                     break;
1508                 }
1509             }
1510         }
1511 
1512         return result;
1513     }
1514 
1515     public static boolean isMethodAccessible( OgnlContext context, Object target, Method method, String propertyName )
1516     {
1517         return ( method != null ) && context.getMemberAccess().isAccessible( context, target, method, propertyName );
1518     }
1519 
1520     public static boolean hasGetMethod( OgnlContext context, Object target, Class<?> targetClass, String propertyName )
1521         throws IntrospectionException, OgnlException
1522     {
1523         return isMethodAccessible( context, target, getGetMethod( context, targetClass, propertyName ), propertyName );
1524     }
1525 
1526     public static Method getSetMethod( OgnlContext context, Class<?> targetClass, String propertyName )
1527         throws IntrospectionException, OgnlException
1528     {
1529         Method setMethod = null;
1530 
1531         List<Method> methods = getDeclaredMethods( targetClass, propertyName, true /* find 'set' methods */ );
1532 
1533         if ( methods != null )
1534         {
1535             for ( Method method : methods )
1536             {
1537                 Class<?>[] mParameterTypes = findParameterTypes( targetClass, method ); // getParameterTypes(method);
1538 
1539                 if ( mParameterTypes.length == 1 )
1540                 {
1541                     setMethod = method;
1542                     break;
1543                 }
1544             }
1545         }
1546 
1547         return setMethod;
1548     }
1549 
1550     public static boolean hasSetMethod( OgnlContext context, Object target, Class<?> targetClass, String propertyName )
1551         throws IntrospectionException, OgnlException
1552     {
1553         return isMethodAccessible( context, target, getSetMethod( context, targetClass, propertyName ), propertyName );
1554     }
1555 
1556     public static boolean hasGetProperty( OgnlContext context, Object target, Object oname )
1557         throws IntrospectionException, OgnlException
1558     {
1559         Class<?> targetClass = ( target == null ) ? null : target.getClass();
1560         String name = oname.toString();
1561 
1562         return hasGetMethod( context, target, targetClass, name ) || hasField( context, target, targetClass, name );
1563     }
1564 
1565     public static boolean hasSetProperty( OgnlContext context, Object target, Object oname )
1566         throws IntrospectionException, OgnlException
1567     {
1568         Class<?> targetClass = ( target == null ) ? null : target.getClass();
1569         String name = oname.toString();
1570 
1571         return hasSetMethod( context, target, targetClass, name ) || hasField( context, target, targetClass, name );
1572     }
1573 
1574     /**
1575      * This method returns the property descriptors for the given class as a Map.
1576      *
1577      * @param targetClass The class to get the descriptors for.
1578      * @return Map map of property descriptors for class.
1579      * @throws IntrospectionException on errors using {@link Introspector}.
1580      * @throws OgnlException          On general errors.
1581      */
1582     public static Map<String, PropertyDescriptor> getPropertyDescriptors( Class<?> targetClass )
1583         throws IntrospectionException, OgnlException
1584     {
1585         return cache.getPropertyDescriptor( targetClass );
1586     }
1587 
1588     /**
1589      * This method returns a PropertyDescriptor for the given class and property name using a Map lookup (using
1590      * getPropertyDescriptorsMap()).
1591      * @param targetClass a target class.
1592      * @param propertyName a property name.
1593      * @return the PropertyDescriptor for the given targetClass and propertyName.
1594      * @throws java.beans.IntrospectionException
1595      * @throws OgnlException
1596      */
1597     public static PropertyDescriptor getPropertyDescriptor( Class<?> targetClass, String propertyName )
1598         throws IntrospectionException, OgnlException
1599     {
1600         if ( targetClass == null )
1601         {
1602             return null;
1603         }
1604 
1605         return getPropertyDescriptors( targetClass ).get( propertyName );
1606     }
1607 
1608     public static PropertyDescriptor[] getPropertyDescriptorsArray( Class<?> targetClass )
1609         throws IntrospectionException, OgnlException
1610     {
1611         Collection<PropertyDescriptor> propertyDescriptors = getPropertyDescriptors( targetClass ).values();
1612         return propertyDescriptors.toArray( new PropertyDescriptor[propertyDescriptors.size()] );
1613     }
1614 
1615     /**
1616      * Gets the property descriptor with the given name for the target class given.
1617      *
1618      * @param targetClass Class for which property descriptor is desired
1619      * @param name        Name of property
1620      * @return PropertyDescriptor of the named property or null if the class has no property with the given name
1621      * @throws java.beans.IntrospectionException
1622      * @throws OgnlException
1623      */
1624     public static PropertyDescriptor getPropertyDescriptorFromArray( Class<?> targetClass, String name )
1625         throws IntrospectionException, OgnlException
1626     {
1627         PropertyDescriptor result = null;
1628         PropertyDescriptor[] propertyDescriptors = getPropertyDescriptorsArray( targetClass );
1629 
1630         for ( PropertyDescriptor propertyDescriptor : propertyDescriptors )
1631         {
1632             if ( result != null )
1633             {
1634                 break;
1635             }
1636             if ( propertyDescriptor.getName().compareTo( name ) == 0 )
1637             {
1638                 result = propertyDescriptor;
1639             }
1640         }
1641         return result;
1642     }
1643 
1644     public static void setMethodAccessor( Class<?> clazz, MethodAccessor accessor )
1645     {
1646         cache.setMethodAccessor( clazz, accessor );
1647     }
1648 
1649     public static MethodAccessor getMethodAccessor( Class<?> clazz )
1650         throws OgnlException
1651     {
1652         return cache.getMethodAccessor( clazz );
1653     }
1654 
1655     public static void setPropertyAccessor( Class<?> clazz, PropertyAccessor accessor )
1656     {
1657         cache.setPropertyAccessor( clazz, accessor );
1658     }
1659 
1660     public static PropertyAccessor getPropertyAccessor( Class<?> clazz )
1661         throws OgnlException
1662     {
1663         return cache.getPropertyAccessor( clazz );
1664     }
1665 
1666     public static ElementsAccessor getElementsAccessor( Class<?> clazz )
1667         throws OgnlException
1668     {
1669         return cache.getElementsAccessor( clazz );
1670     }
1671 
1672     public static void setElementsAccessor( Class<?> clazz, ElementsAccessor accessor )
1673     {
1674         cache.setElementsAccessor( clazz, accessor );
1675     }
1676 
1677     public static NullHandler getNullHandler( Class<?> clazz )
1678         throws OgnlException
1679     {
1680         return cache.getNullHandler( clazz );
1681     }
1682 
1683     public static void setNullHandler( Class<?> clazz, NullHandler handler )
1684     {
1685         cache.setNullHandler( clazz, handler );
1686     }
1687 
1688     public static Object getProperty( OgnlContext context, Object source, Object name )
1689         throws OgnlException
1690     {
1691         PropertyAccessor accessor;
1692 
1693         if ( source == null )
1694         {
1695             throw new OgnlException( "source is null for getProperty(null, \"" + name + "\")" );
1696         }
1697         if ( ( accessor = getPropertyAccessor( getTargetClass( source ) ) ) == null )
1698         {
1699             throw new OgnlException( "No property accessor for " + getTargetClass( source ).getName() );
1700         }
1701 
1702         return accessor.getProperty( context, source, name );
1703     }
1704 
1705     public static void setProperty( OgnlContext context, Object target, Object name, Object value )
1706         throws OgnlException
1707     {
1708         PropertyAccessor accessor;
1709 
1710         if ( target == null )
1711         {
1712             throw new OgnlException( "target is null for setProperty(null, \"" + name + "\", " + value + ")" );
1713         }
1714         if ( ( accessor = getPropertyAccessor( getTargetClass( target ) ) ) == null )
1715         {
1716             throw new OgnlException( "No property accessor for " + getTargetClass( target ).getName() );
1717         }
1718 
1719         accessor.setProperty( context, target, name, value );
1720     }
1721 
1722     /**
1723      * Determines the index property type, if any. Returns <code>INDEXED_PROPERTY_NONE</code> if the property is not
1724      * index-accessible as determined by OGNL or JavaBeans. If it is indexable then this will return whether it is a
1725      * JavaBeans indexed property, conforming to the indexed property patterns (returns
1726      * <code>INDEXED_PROPERTY_INT</code>) or if it conforms to the OGNL arbitrary object indexable (returns
1727      * <code>INDEXED_PROPERTY_OBJECT</code>).
1728      */
1729     public static int getIndexedPropertyType( OgnlContext context, Class<?> sourceClass, String name )
1730         throws OgnlException
1731     {
1732         int result = INDEXED_PROPERTY_NONE;
1733 
1734         try
1735         {
1736             PropertyDescriptor propertyDescriptor = getPropertyDescriptor( sourceClass, name );
1737             if ( propertyDescriptor != null )
1738             {
1739                 if ( propertyDescriptor instanceof IndexedPropertyDescriptor )
1740                 {
1741                     result = INDEXED_PROPERTY_INT;
1742                 }
1743                 else
1744                 {
1745                     if ( propertyDescriptor instanceof ObjectIndexedPropertyDescriptor )
1746                     {
1747                         result = INDEXED_PROPERTY_OBJECT;
1748                     }
1749                 }
1750             }
1751         }
1752         catch ( Exception ex )
1753         {
1754             throw new OgnlException( "problem determining if '" + name + "' is an indexed property", ex );
1755         }
1756         return result;
1757     }
1758 
1759     public static Object getIndexedProperty( OgnlContext context, Object source, String name, Object index )
1760         throws OgnlException
1761     {
1762         Object[] args = objectArrayPool.create( index );
1763 
1764         try
1765         {
1766             PropertyDescriptor propertyDescriptor = getPropertyDescriptor( ( source == null ) ? null : source.getClass(), name );
1767             Method method;
1768 
1769             if ( propertyDescriptor instanceof IndexedPropertyDescriptor )
1770             {
1771                 method = ( (IndexedPropertyDescriptor) propertyDescriptor ).getIndexedReadMethod();
1772             }
1773             else
1774             {
1775                 if ( propertyDescriptor instanceof ObjectIndexedPropertyDescriptor )
1776                 {
1777                     method = ( (ObjectIndexedPropertyDescriptor) propertyDescriptor ).getIndexedReadMethod();
1778                 }
1779                 else
1780                 {
1781                     throw new OgnlException( "property '" + name + "' is not an indexed property" );
1782                 }
1783             }
1784 
1785             return callMethod( context, source, method.getName(), args );
1786 
1787         }
1788         catch ( OgnlException ex )
1789         {
1790             throw ex;
1791         }
1792         catch ( Exception ex )
1793         {
1794             throw new OgnlException( "getting indexed property descriptor for '" + name + "'", ex );
1795         }
1796         finally
1797         {
1798             objectArrayPool.recycle( args );
1799         }
1800     }
1801 
1802     public static void setIndexedProperty( OgnlContext context, Object source, String name, Object index, Object value )
1803         throws OgnlException
1804     {
1805         Object[] args = objectArrayPool.create( index, value );
1806 
1807         try
1808         {
1809             PropertyDescriptor propertyDescriptor = getPropertyDescriptor( ( source == null ) ? null : source.getClass(), name );
1810             Method method;
1811 
1812             if ( propertyDescriptor instanceof IndexedPropertyDescriptor )
1813             {
1814                 method = ( (IndexedPropertyDescriptor) propertyDescriptor ).getIndexedWriteMethod();
1815             }
1816             else
1817             {
1818                 if ( propertyDescriptor instanceof ObjectIndexedPropertyDescriptor )
1819                 {
1820                     method = ( (ObjectIndexedPropertyDescriptor) propertyDescriptor ).getIndexedWriteMethod();
1821                 }
1822                 else
1823                 {
1824                     throw new OgnlException( "property '" + name + "' is not an indexed property" );
1825                 }
1826             }
1827 
1828             callMethod( context, source, method.getName(), args );
1829 
1830         }
1831         catch ( OgnlException ex )
1832         {
1833             throw ex;
1834         }
1835         catch ( Exception ex )
1836         {
1837             throw new OgnlException( "getting indexed property descriptor for '" + name + "'", ex );
1838         }
1839         finally
1840         {
1841             objectArrayPool.recycle( args );
1842         }
1843     }
1844 
1845     public static EvaluationPool getEvaluationPool()
1846     {
1847         return evaluationPool;
1848     }
1849 
1850     public static ObjectArrayPool getObjectArrayPool()
1851     {
1852         return objectArrayPool;
1853     }
1854 
1855     /**
1856      * Registers the specified {@link ClassCacheInspector} with all class reflection based internal caches. This may
1857      * have a significant performance impact so be careful using this in production scenarios.
1858      *
1859      * @param inspector The inspector instance that will be registered with all internal cache instances.
1860      */
1861     public static void setClassCacheInspector( ClassCacheInspector inspector )
1862     {
1863         cache.setClassCacheInspector( inspector );
1864     }
1865 
1866     public static Method getMethod( OgnlContext context, Class<?> target, String name, Node[] children,
1867                                     boolean includeStatic )
1868         throws Exception
1869     {
1870         Class<?>[] parms;
1871         if ( children != null && children.length > 0 )
1872         {
1873             parms = new Class[children.length];
1874 
1875             // used to reset context after loop
1876             Class<?> currType = context.getCurrentType();
1877             Class<?> currAccessor = context.getCurrentAccessor();
1878             Object cast = context.get( ExpressionCompiler.PRE_CAST );
1879 
1880             context.setCurrentObject( context.getRoot() );
1881             context.setCurrentType( context.getRoot() != null ? context.getRoot().getClass() : null );
1882             context.setCurrentAccessor( null );
1883             context.setPreviousType( null );
1884 
1885             for ( int i = 0; i < children.length; i++ )
1886             {
1887                 children[i].toGetSourceString( context, context.getRoot() );
1888                 parms[i] = context.getCurrentType();
1889             }
1890 
1891             context.put( ExpressionCompiler.PRE_CAST, cast );
1892 
1893             context.setCurrentType( currType );
1894             context.setCurrentAccessor( currAccessor );
1895             context.setCurrentObject( target );
1896         }
1897         else
1898         {
1899             parms = new Class[0];
1900         }
1901 
1902         List<Method> methods = OgnlRuntime.getMethods( target, name, includeStatic );
1903         if ( methods == null )
1904         {
1905             return null;
1906         }
1907 
1908         for ( Method method : methods )
1909         {
1910             boolean varArgs = method.isVarArgs();
1911 
1912             if ( parms.length != method.getParameterTypes().length && !varArgs )
1913             {
1914                 continue;
1915             }
1916 
1917             Class<?>[] methodParameterTypes = method.getParameterTypes();
1918             boolean matched = true;
1919             for ( int i = 0; i < methodParameterTypes.length; i++ )
1920             {
1921                 Class<?> methodParameterType = methodParameterTypes[i];
1922                 if ( varArgs && methodParameterType.isArray() )
1923                 {
1924                     continue;
1925                 }
1926 
1927                 Class<?> parm = parms[i];
1928                 if ( parm == null )
1929                 {
1930                     matched = false;
1931                     break;
1932                 }
1933 
1934                 if ( parm == methodParameterType || methodParameterType.isPrimitive() && Character.TYPE != methodParameterType && Byte.TYPE != methodParameterType
1935                     && Number.class.isAssignableFrom(parm)
1936                     && OgnlRuntime.getPrimitiveWrapperClass(parm) == methodParameterType)
1937                 {
1938                     continue;
1939                 }
1940 
1941                 matched = false;
1942                 break;
1943             }
1944 
1945             if ( matched )
1946             {
1947                 return method;
1948             }
1949         }
1950 
1951         return null;
1952     }
1953 
1954     /**
1955      * Finds the best possible match for a method on the specified target class with a matching name.
1956      * <p>
1957      * The name matched will also try different combinations like <code>is + name, has + name, get + name, etc..</code>
1958      * </p>
1959      *
1960      * @param target The class to find a matching method against.
1961      * @param name   The name of the method.
1962      * @return The most likely matching {@link Method}, or null if none could be found.
1963      */
1964     public static Method getReadMethod( Class<?> target, String name )
1965     {
1966         return getReadMethod( target, name, -1 );
1967     }
1968 
1969     public static Method getReadMethod( Class<?> target, String name, int numParms )
1970     {
1971         try
1972         {
1973             name = name.replaceAll( "\"", "" ).toLowerCase();
1974 
1975             BeanInfo info = Introspector.getBeanInfo( target );
1976             MethodDescriptor[] methodDescriptors = info.getMethodDescriptors();
1977 
1978             // exact matches first
1979 
1980             Method method = null;
1981 
1982             for ( MethodDescriptor methodDescriptor : methodDescriptors )
1983             {
1984                 if ( !isMethodCallable( methodDescriptor.getMethod() ) )
1985                 {
1986                     continue;
1987                 }
1988 
1989                 String methodName = methodDescriptor.getName();
1990                 String lowerMethodName = methodName.toLowerCase();
1991                 int methodParamLen = methodDescriptor.getMethod().getParameterTypes().length;
1992 
1993                 if ( ( methodName.equalsIgnoreCase( name ) || lowerMethodName.equals( "get" + name )
1994                     || lowerMethodName.equals( "has" + name ) || lowerMethodName.equals( "is" + name ) )
1995                     && !methodName.startsWith( "set" ) )
1996                 {
1997                     if ( numParms > 0 && methodParamLen == numParms )
1998                     {
1999                         return methodDescriptor.getMethod();
2000                     }
2001                     else if ( numParms < 0 )
2002                     {
2003                         if ( methodName.equals( name ) )
2004                         {
2005                             return methodDescriptor.getMethod();
2006                         }
2007                         else if ( method == null || ( method.getParameterTypes().length > methodParamLen ) )
2008                         {
2009                             method = methodDescriptor.getMethod();
2010                         }
2011                     }
2012                 }
2013             }
2014 
2015             if ( method != null )
2016             {
2017                 return method;
2018             }
2019 
2020             for ( MethodDescriptor methodDescriptor : methodDescriptors )
2021             {
2022                 if ( !isMethodCallable( methodDescriptor.getMethod() ) )
2023                 {
2024                     continue;
2025                 }
2026 
2027                 if ( methodDescriptor.getName().toLowerCase().endsWith( name ) && !methodDescriptor.getName().startsWith( "set" )
2028                     && methodDescriptor.getMethod().getReturnType() != Void.TYPE )
2029                 {
2030 
2031                     if ( numParms > 0 && methodDescriptor.getMethod().getParameterTypes().length == numParms )
2032                     {
2033                         return methodDescriptor.getMethod();
2034                     }
2035                     else if ( numParms < 0 )
2036                     {
2037                         if ( ( method != null
2038                             && method.getParameterTypes().length > methodDescriptor.getMethod().getParameterTypes().length )
2039                             || method == null )
2040                         {
2041                             method = methodDescriptor.getMethod();
2042                         }
2043                     }
2044                 }
2045             }
2046 
2047             if ( method != null )
2048             {
2049                 return method;
2050             }
2051 
2052             // try one last time adding a get to beginning
2053 
2054             if ( !name.startsWith( "get" ) )
2055             {
2056                 return OgnlRuntime.getReadMethod( target, "get" + name, numParms );
2057             }
2058 
2059         }
2060         catch ( Throwable t )
2061         {
2062             throw OgnlOps.castToRuntime( t );
2063         }
2064 
2065         return null;
2066     }
2067 
2068     public static Method getWriteMethod( Class<?> target, String name )
2069     {
2070         return getWriteMethod( target, name, -1 );
2071     }
2072 
2073     public static Method getWriteMethod( Class<?> target, String name, int numParms )
2074     {
2075         try
2076         {
2077             name = name.replaceAll( "\"", "" );
2078 
2079             BeanInfo info = Introspector.getBeanInfo( target );
2080             MethodDescriptor[] methods = info.getMethodDescriptors();
2081 
2082             for ( MethodDescriptor method : methods )
2083             {
2084                 if ( !isMethodCallable( method.getMethod() ) )
2085                 {
2086                     continue;
2087                 }
2088 
2089                 if ( ( method.getName().equalsIgnoreCase( name ) || method.getName().toLowerCase().equals(
2090                     name.toLowerCase() ) || method.getName().toLowerCase().equals( "set" + name.toLowerCase() ) )
2091                     && !method.getName().startsWith( "get" ) )
2092                 {
2093 
2094                     if ( numParms > 0 && method.getMethod().getParameterTypes().length == numParms )
2095                     {
2096                         return method.getMethod();
2097                     }
2098                     else if ( numParms < 0 )
2099                     {
2100                         return method.getMethod();
2101                     }
2102                 }
2103             }
2104 
2105             // try again on pure class
2106 
2107             Method[] cmethods = target.getClass().getMethods();
2108             for ( Method cmethod : cmethods )
2109             {
2110                 if ( !isMethodCallable( cmethod ) )
2111                 {
2112                     continue;
2113                 }
2114 
2115                 if ( ( cmethod.getName().equalsIgnoreCase( name ) || cmethod.getName().toLowerCase().equals(
2116                     name.toLowerCase() ) || cmethod.getName().toLowerCase().equals( "set" + name.toLowerCase() ) )
2117                     && !cmethod.getName().startsWith( "get" ) )
2118                 {
2119 
2120                     if ( numParms > 0 && cmethod.getParameterTypes().length == numParms )
2121                     {
2122                         return cmethod;
2123                     }
2124                     else if ( numParms < 0 )
2125                     {
2126                         return cmethod;
2127                     }
2128                 }
2129             }
2130 
2131             // try one last time adding a set to beginning
2132 
2133             if ( !name.startsWith( "set" ) )
2134             {
2135                 return OgnlRuntime.getReadMethod( target, "set" + name, numParms );
2136             }
2137 
2138         }
2139         catch ( Throwable t )
2140         {
2141             throw OgnlOps.castToRuntime( t );
2142         }
2143 
2144         return null;
2145     }
2146 
2147     public static PropertyDescriptor getProperty( Class<?> target, String name )
2148     {
2149         try
2150         {
2151             BeanInfo info = Introspector.getBeanInfo( target );
2152 
2153             PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
2154 
2155             for ( PropertyDescriptor propertyDescriptor : propertyDescriptors )
2156             {
2157 
2158                 String propertyDescriptorName = propertyDescriptor.getName();
2159                 if ( propertyDescriptorName.equalsIgnoreCase( name ) || propertyDescriptorName.toLowerCase().equals( name.toLowerCase() )
2160                     || propertyDescriptorName.toLowerCase().endsWith( name.toLowerCase() ) )
2161                 {
2162                     return propertyDescriptor;
2163                 }
2164             }
2165 
2166         }
2167         catch ( Throwable t )
2168         {
2169             throw OgnlOps.castToRuntime( t );
2170         }
2171 
2172         return null;
2173     }
2174 
2175     public static boolean isBoolean( String expression )
2176     {
2177         return expression != null && ( "true".equals( expression ) || "false".equals( expression )
2178             || "!true".equals( expression ) || "!false".equals( expression ) || "(true)".equals( expression )
2179             || "!(true)".equals( expression ) || "(false)".equals( expression ) || "!(false)".equals( expression )
2180             || expression.startsWith( "org.apache.commons.ognl.OgnlOps" ) );
2181     }
2182 
2183     /**
2184      * Compares the {@link OgnlContext#getCurrentType()} and {@link OgnlContext#getPreviousType()} class types on the
2185      * stack to determine if a numeric expression should force object conversion.
2186      * <p/>
2187      * <p/>
2188      * Normally used in conjunction with the <code>forceConversion</code> parameter of
2189      * {@link OgnlRuntime#getChildSource(OgnlContext, Object, Node, boolean)}.
2190      * </p>
2191      *
2192      * @param context The current context.
2193      * @return True, if the class types on the stack wouldn't be comparable in a pure numeric expression such as
2194      *         <code>o1 >= o2</code>.
2195      */
2196     public static boolean shouldConvertNumericTypes( OgnlContext context )
2197     {
2198         Class<?> currentType = context.getCurrentType();
2199         Class<?> previousType = context.getPreviousType();
2200         return currentType == null || previousType == null
2201             || !( currentType == previousType && currentType.isPrimitive() && previousType.isPrimitive() )
2202             && !currentType.isArray() && !previousType.isArray();
2203     }
2204 
2205     /**
2206      * Attempts to get the java source string represented by the specific child expression via the
2207      * {@link JavaSource#toGetSourceString(OgnlContext, Object)} interface method.
2208      *
2209      * @param context The ognl context to pass to the child.
2210      * @param target  The current object target to use.
2211      * @param child   The child expression.
2212      * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext, Object)} plus additional enclosures
2213      *         of {@link OgnlOps#convertValue(Object, Class, boolean)} for conversions.
2214      * @throws OgnlException Mandatory exception throwing catching.. (blehh)
2215      */
2216     public static String getChildSource( OgnlContext context, Object target, Node child )
2217         throws OgnlException
2218     {
2219         return getChildSource( context, target, child, false );
2220     }
2221 
2222     /**
2223      * Attempts to get the java source string represented by the specific child expression via the
2224      * {@link JavaSource#toGetSourceString(OgnlContext, Object)} interface method.
2225      *
2226      * @param context         The ognl context to pass to the child.
2227      * @param target          The current object target to use.
2228      * @param child           The child expression.
2229      * @param forceConversion If true, forces {@link OgnlOps#convertValue(Object, Class)} conversions on the objects.
2230      * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext, Object)} plus additional enclosures
2231      *         of {@link OgnlOps#convertValue(Object, Class, boolean)} for conversions.
2232      * @throws OgnlException Mandatory exception throwing catching.. (blehh)
2233      */
2234     public static String getChildSource( OgnlContext context, Object target, Node child, boolean forceConversion )
2235         throws OgnlException
2236     {
2237         String pre = (String) context.get( "_currentChain" );
2238         if ( pre == null )
2239         {
2240             pre = "";
2241         }
2242 
2243         try
2244         {
2245             child.getValue( context, target );
2246         }
2247         catch ( NullPointerException e )
2248         {
2249             // ignore
2250         }
2251         catch ( ArithmeticException e )
2252         {
2253             context.setCurrentType( int.class );
2254             return "0";
2255         }
2256         catch ( Throwable t )
2257         {
2258             throw OgnlOps.castToRuntime( t );
2259         }
2260 
2261         String source;
2262 
2263         try
2264         {
2265             source = child.toGetSourceString( context, target );
2266         }
2267         catch ( Throwable t )
2268         {
2269             throw OgnlOps.castToRuntime( t );
2270         }
2271 
2272         // handle root / method expressions that may not have proper root java source access
2273 
2274         if ( !ASTConst.class.isInstance( child ) && ( target == null || context.getRoot() != target ) )
2275         {
2276             source = pre + source;
2277         }
2278 
2279         if ( context.getRoot() != null )
2280         {
2281             source = ExpressionCompiler.getRootExpression( child, context.getRoot(), context ) + source;
2282             context.setCurrentAccessor( context.getRoot().getClass() );
2283         }
2284 
2285         if ( ASTChain.class.isInstance( child ) )
2286         {
2287             String cast = (String) context.remove( ExpressionCompiler.PRE_CAST );
2288             if ( cast == null )
2289             {
2290                 cast = "";
2291             }
2292 
2293             source = cast + source;
2294         }
2295 
2296         if ( source == null || source.trim().length() < 1 )
2297         {
2298             source = "null";
2299         }
2300 
2301         return source;
2302     }
2303 }