001    package org.apache.commons.ognl;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *  http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.commons.ognl.enhance.ExpressionCompiler;
023    import org.apache.commons.ognl.enhance.OgnlExpressionCompiler;
024    import org.apache.commons.ognl.internal.CacheException;
025    import org.apache.commons.ognl.internal.entry.DeclaredMethodCacheEntry;
026    import org.apache.commons.ognl.internal.entry.GenericMethodParameterTypeCacheEntry;
027    import org.apache.commons.ognl.internal.entry.MethodAccessEntryValue;
028    import org.apache.commons.ognl.internal.entry.PermissionCacheEntry;
029    
030    import java.beans.BeanInfo;
031    import java.beans.IndexedPropertyDescriptor;
032    import java.beans.IntrospectionException;
033    import java.beans.Introspector;
034    import java.beans.MethodDescriptor;
035    import java.beans.PropertyDescriptor;
036    import java.lang.reflect.Constructor;
037    import java.lang.reflect.Field;
038    import java.lang.reflect.InvocationTargetException;
039    import java.lang.reflect.Member;
040    import java.lang.reflect.Method;
041    import java.lang.reflect.Modifier;
042    import java.lang.reflect.ParameterizedType;
043    import java.lang.reflect.Proxy;
044    import java.security.Permission;
045    import java.util.ArrayList;
046    import java.util.Collection;
047    import java.util.Collections;
048    import java.util.HashMap;
049    import java.util.List;
050    import java.util.Map;
051    
052    /**
053     * Utility class used by internal OGNL API to do various things like:
054     * <ul>
055     * <li>Handles majority of reflection logic / caching.</li>
056     * <li>Utility methods for casting strings / various numeric types used by {@link OgnlExpressionCompiler}.</li.
057     * <li>Core runtime configuration point for setting/using global {@link TypeConverter} / {@link OgnlExpressionCompiler}
058     * / {@link NullHandler} instances / etc..</li>
059     * </ul>
060     *
061     * $Id: OgnlRuntime.java 1239219 2012-02-01 17:27:54Z mcucchiara $
062     *
063     * @author Luke Blanshard (blanshlu@netscape.net)
064     * @author Drew Davidson (drew@ognl.org)
065     */
066    public class OgnlRuntime
067    {
068        /**
069         * Constant expression used to indicate that a given method / property couldn't be found during reflection
070         * operations.
071         */
072        public static final Object NotFound = new Object();
073    
074        public static final Object[] NoArguments = new Object[]{ };
075    
076        /**
077         * Token returned by TypeConverter for no conversion possible
078         */
079        public static final Object NoConversionPossible = "ognl.NoConversionPossible";
080    
081        /**
082         * Not an indexed property
083         */
084        public static final int INDEXED_PROPERTY_NONE = 0;
085    
086        /**
087         * JavaBeans IndexedProperty
088         */
089        public static final int INDEXED_PROPERTY_INT = 1;
090    
091        /**
092         * OGNL ObjectIndexedProperty
093         */
094        public static final int INDEXED_PROPERTY_OBJECT = 2;
095    
096        /**
097         * Constant string representation of null string.
098         */
099        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    }