// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package ognl;

import ognl.enhance.ExpressionCompiler;
import ognl.enhance.OgnlExpressionCompiler;
import ognl.internal.ClassCache;
import ognl.internal.ClassCacheImpl;

import java.beans.*;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.Permission;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Utility class used by internal OGNL API to do various things like:
 *
 *

 *
Handles majority of reflection logic / caching.

 *
Utility methods for casting strings / various numeric types used by {@link OgnlExpressionCompiler}.</li.
 *
Core runtime configuration point for setting/using global {@link TypeConverter} / {@link OgnlExpressionCompiler} /
 * {@link NullHandler} instances / etc..

 *

 *
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public class OgnlRuntime {

    /**
     * Constant expression used to indicate that a given method / property couldn't be found
     * during reflection operations.
     */
    public static final Object NotFound = new Object();
    public static final List NotFoundList = new ArrayList();
    public static final Map NotFoundMap = new HashMap();
    public static final Object[] NoArguments = new Object[]{};
    public static final Class[] NoArgumentTypes = new Class[]{};

    /**
     * Token returned by TypeConverter for no conversion possible
     */
    public static final Object NoConversionPossible = "ognl.NoConversionPossible";

    /**
     * Not an indexed property
     */
    public static int INDEXED_PROPERTY_NONE = 0;
    /**
     * JavaBeans IndexedProperty
     */
    public static int INDEXED_PROPERTY_INT = 1;
    /**
     * OGNL ObjectIndexedProperty
     */
    public static int INDEXED_PROPERTY_OBJECT = 2;

    /**
     * Constant string representation of null string.
     */
    public static final String NULL_STRING = "" + null;

    /**
     * Java beans standard set method prefix.
     */
    private static final String SET_PREFIX = "set";
    /**
     * Java beans standard get method prefix.
     */
    private static final String GET_PREFIX = "get";
    /**
     * Java beans standard is<Foo> boolean getter prefix.
     */
    private static final String IS_PREFIX = "is";

    /**
     * Prefix padding for hexadecimal numbers to HEX_LENGTH.
     */
    private static final Map HEX_PADDING = new HashMap();

    private static final int HEX_LENGTH = 8;

    /**
     * Returned by getUniqueDescriptor() when the object is null.
     */
    private static final String NULL_OBJECT_STRING = "<null>";

    /**
     * Used to store the result of determining if current jvm is 1.5 language compatible.
     */
    private static boolean _jdk15 = false;
    private static boolean _jdkChecked = false;

    static final ClassCache _methodAccessors = new ClassCacheImpl();
    static final ClassCache _propertyAccessors = new ClassCacheImpl();
    static final ClassCache _elementsAccessors = new ClassCacheImpl();
    static final ClassCache _nullHandlers = new ClassCacheImpl();

    static final ClassCache _propertyDescriptorCache = new ClassCacheImpl();
    static final ClassCache _constructorCache = new ClassCacheImpl();
    static final ClassCache _staticMethodCache = new ClassCacheImpl();
    static final ClassCache _instanceMethodCache = new ClassCacheImpl();
    static final ClassCache _invokePermissionCache = new ClassCacheImpl();
    static final ClassCache _fieldCache = new ClassCacheImpl();
    static final List _superclasses = new ArrayList(); /* Used by fieldCache lookup */
    static final ClassCache[] _declaredMethods = new ClassCache[]{new ClassCacheImpl(), new ClassCacheImpl()};

    static final Map _primitiveTypes = new HashMap(101);
    static final ClassCache _primitiveDefaults = new ClassCacheImpl();
    static final Map _methodParameterTypesCache = new HashMap(101);
    static final Map _genericMethodParameterTypesCache = new HashMap(101);
    static final Map _ctorParameterTypesCache = new HashMap(101);
    static SecurityManager _securityManager = System.getSecurityManager();
    static final EvaluationPool _evaluationPool = new EvaluationPool();
    static final ObjectArrayPool _objectArrayPool = new ObjectArrayPool();

    static final Map<Method, Boolean> _methodAccessCache = new ConcurrentHashMap<Method, Boolean>();
    static final Map<Method, Boolean> _methodPermCache = new ConcurrentHashMap<Method, Boolean>();

    static final ClassPropertyMethodCache cacheSetMethod = new ClassPropertyMethodCache();
    static final ClassPropertyMethodCache cacheGetMethod = new ClassPropertyMethodCache();

    static ClassCacheInspector _cacheInspector;

    /**
     * Expression compiler used by {@link Ognl#compileExpression(OgnlContext, Object, String)} calls.
     */
    private static OgnlExpressionCompiler _compiler;

    /**
     * Lazy loading of Javassist library
     */
    static {
        try {
            Class.forName("javassist.ClassPool");
            _compiler = new ExpressionCompiler();
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Javassist library is missing in classpath! Please add missed dependency!",e);
        } catch (RuntimeException rt) {
            throw new IllegalStateException("Javassist library cannot be loaded, is it restricted by runtime environment?");
        }
    }

    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

    private static IdentityHashMap PRIMITIVE_WRAPPER_CLASSES = new IdentityHashMap();

    /**
     * Used to provide primitive type equivalent conversions into and out of
     * native / object types.
     */
    static {
        PRIMITIVE_WRAPPER_CLASSES.put(Boolean.TYPE, Boolean.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Boolean.class, Boolean.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Byte.TYPE, Byte.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Byte.class, Byte.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Character.TYPE, Character.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Character.class, Character.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Short.TYPE, Short.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Short.class, Short.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Integer.TYPE, Integer.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Integer.class, Integer.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Long.TYPE, Long.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Long.class, Long.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Float.TYPE, Float.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Float.class, Float.TYPE);
        PRIMITIVE_WRAPPER_CLASSES.put(Double.TYPE, Double.class);
        PRIMITIVE_WRAPPER_CLASSES.put(Double.class, Double.TYPE);
    }

    private static final Map NUMERIC_CASTS = new HashMap();

    /**
     * Constant strings for casting different primitive types.
     */
    static {
        NUMERIC_CASTS.put(Double.class, "(double)");
        NUMERIC_CASTS.put(Float.class, "(float)");
        NUMERIC_CASTS.put(Integer.class, "(int)");
        NUMERIC_CASTS.put(Long.class, "(long)");
        NUMERIC_CASTS.put(BigDecimal.class, "(double)");
        NUMERIC_CASTS.put(BigInteger.class, "");
    }

    private static final Map NUMERIC_VALUES = new HashMap();

    /**
     * Constant strings for getting the primitive value of different
     * native types on the generic {@link Number} object interface. (or the less
     * generic BigDecimal/BigInteger types)
     */
    static {
        NUMERIC_VALUES.put(Double.class, "doubleValue()");
        NUMERIC_VALUES.put(Float.class, "floatValue()");
        NUMERIC_VALUES.put(Integer.class, "intValue()");
        NUMERIC_VALUES.put(Long.class, "longValue()");
        NUMERIC_VALUES.put(Short.class, "shortValue()");
        NUMERIC_VALUES.put(Byte.class, "byteValue()");
        NUMERIC_VALUES.put(BigDecimal.class, "doubleValue()");
        NUMERIC_VALUES.put(BigInteger.class, "doubleValue()");
        NUMERIC_VALUES.put(Boolean.class, "booleanValue()");
    }

    private static final Map NUMERIC_LITERALS = new HashMap();

    /**
     * Numeric primitive literal string expressions.
     */
    static {
        NUMERIC_LITERALS.put(Integer.class, "");
        NUMERIC_LITERALS.put(Integer.TYPE, "");
        NUMERIC_LITERALS.put(Long.class, "l");
        NUMERIC_LITERALS.put(Long.TYPE, "l");
        NUMERIC_LITERALS.put(BigInteger.class, "d");
        NUMERIC_LITERALS.put(Float.class, "f");
        NUMERIC_LITERALS.put(Float.TYPE, "f");
        NUMERIC_LITERALS.put(Double.class, "d");
        NUMERIC_LITERALS.put(Double.TYPE, "d");
        NUMERIC_LITERALS.put(BigInteger.class, "d");
        NUMERIC_LITERALS.put(BigDecimal.class, "d");
    }

    private static final Map NUMERIC_DEFAULTS = new HashMap();

    static {
        NUMERIC_DEFAULTS.put(Boolean.class, Boolean.FALSE);
        NUMERIC_DEFAULTS.put(Byte.class, new Byte((byte) 0));
        NUMERIC_DEFAULTS.put(Short.class, new Short((short) 0));
        NUMERIC_DEFAULTS.put(Character.class, new Character((char) 0));
        NUMERIC_DEFAULTS.put(Integer.class, new Integer(0));
        NUMERIC_DEFAULTS.put(Long.class, new Long(0L));
        NUMERIC_DEFAULTS.put(Float.class, new Float(0.0f));
        NUMERIC_DEFAULTS.put(Double.class, new Double(0.0));

        NUMERIC_DEFAULTS.put(BigInteger.class, new BigInteger("0"));
        NUMERIC_DEFAULTS.put(BigDecimal.class, new BigDecimal(0.0));
    }

    static {
        PropertyAccessor p = new ArrayPropertyAccessor();

        setPropertyAccessor(Object.class, new ObjectPropertyAccessor());
        setPropertyAccessor(byte[].class, p);
        setPropertyAccessor(short[].class, p);
        setPropertyAccessor(char[].class, p);
        setPropertyAccessor(int[].class, p);
        setPropertyAccessor(long[].class, p);
        setPropertyAccessor(float[].class, p);
        setPropertyAccessor(double[].class, p);
        setPropertyAccessor(Object[].class, p);
        setPropertyAccessor(List.class, new ListPropertyAccessor());
        setPropertyAccessor(Map.class, new MapPropertyAccessor());
        setPropertyAccessor(Set.class, new SetPropertyAccessor());
        setPropertyAccessor(Iterator.class, new IteratorPropertyAccessor());
        setPropertyAccessor(Enumeration.class, new EnumerationPropertyAccessor());

        ElementsAccessor e = new ArrayElementsAccessor();

        setElementsAccessor(Object.class, new ObjectElementsAccessor());
        setElementsAccessor(byte[].class, e);
        setElementsAccessor(short[].class, e);
        setElementsAccessor(char[].class, e);
        setElementsAccessor(int[].class, e);
        setElementsAccessor(long[].class, e);
        setElementsAccessor(float[].class, e);
        setElementsAccessor(double[].class, e);
        setElementsAccessor(Object[].class, e);
        setElementsAccessor(Collection.class, new CollectionElementsAccessor());
        setElementsAccessor(Map.class, new MapElementsAccessor());
        setElementsAccessor(Iterator.class, new IteratorElementsAccessor());
        setElementsAccessor(Enumeration.class, new EnumerationElementsAccessor());
        setElementsAccessor(Number.class, new NumberElementsAccessor());

        NullHandler nh = new ObjectNullHandler();

        setNullHandler(Object.class, nh);
        setNullHandler(byte[].class, nh);
        setNullHandler(short[].class, nh);
        setNullHandler(char[].class, nh);
        setNullHandler(int[].class, nh);
        setNullHandler(long[].class, nh);
        setNullHandler(float[].class, nh);
        setNullHandler(double[].class, nh);
        setNullHandler(Object[].class, nh);

        MethodAccessor ma = new ObjectMethodAccessor();

        setMethodAccessor(Object.class, ma);
        setMethodAccessor(byte[].class, ma);
        setMethodAccessor(short[].class, ma);
        setMethodAccessor(char[].class, ma);
        setMethodAccessor(int[].class, ma);
        setMethodAccessor(long[].class, ma);
        setMethodAccessor(float[].class, ma);
        setMethodAccessor(double[].class, ma);
        setMethodAccessor(Object[].class, ma);

        _primitiveTypes.put("boolean", Boolean.TYPE);
        _primitiveTypes.put("byte", Byte.TYPE);
        _primitiveTypes.put("short", Short.TYPE);
        _primitiveTypes.put("char", Character.TYPE);
        _primitiveTypes.put("int", Integer.TYPE);
        _primitiveTypes.put("long", Long.TYPE);
        _primitiveTypes.put("float", Float.TYPE);
        _primitiveTypes.put("double", Double.TYPE);

        _primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        _primitiveDefaults.put(Boolean.class, Boolean.FALSE);
        _primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0));
        _primitiveDefaults.put(Byte.class, new Byte((byte) 0));
        _primitiveDefaults.put(Short.TYPE, new Short((short) 0));
        _primitiveDefaults.put(Short.class, new Short((short) 0));
        _primitiveDefaults.put(Character.TYPE, new Character((char) 0));
        _primitiveDefaults.put(Integer.TYPE, new Integer(0));
        _primitiveDefaults.put(Long.TYPE, new Long(0L));
        _primitiveDefaults.put(Float.TYPE, new Float(0.0f));
        _primitiveDefaults.put(Double.TYPE, new Double(0.0));

        _primitiveDefaults.put(BigInteger.class, new BigInteger("0"));
        _primitiveDefaults.put(BigDecimal.class, new BigDecimal(0.0));
    }

    /**
     * Clears all of the cached reflection information normally used
     * to improve the speed of expressions that operate on the same classes
     * or are executed multiple times.
     *
     *

     * Warning: Calling this too often can be a huge performance
     * drain on your expressions - use with care.
     *

     */
    public static void clearCache()
    {
        _methodParameterTypesCache.clear();
        _ctorParameterTypesCache.clear();
        _propertyDescriptorCache.clear();
        _constructorCache.clear();
        _staticMethodCache.clear();
        _instanceMethodCache.clear();
        _invokePermissionCache.clear();
        _fieldCache.clear();
        _superclasses.clear();
        _declaredMethods[0].clear();
        _declaredMethods[1].clear();
        _methodAccessCache.clear();
        _methodPermCache.clear();
    }

    /**
     * Checks if the current jvm is java language >= 1.5 compatible.
     *
     * @return True if jdk15 features are present.
     */
    public static boolean isJdk15()
    {
        if (_jdkChecked)
            return _jdk15;

        try
        {
            Class.forName("java.lang.annotation.Annotation");
            _jdk15 = true;
        } catch (Exception e) { /* ignore */ }

        _jdkChecked = true;

        return _jdk15;
    }

    public static String getNumericValueGetter(Class type)
    {
        return (String) NUMERIC_VALUES.get(type);
    }

    public static Class getPrimitiveWrapperClass(Class primitiveClass)
    {
        return (Class) PRIMITIVE_WRAPPER_CLASSES.get(primitiveClass);
    }

    public static String getNumericCast(Class type)
    {
        return (String) NUMERIC_CASTS.get(type);
    }

    public static String getNumericLiteral(Class type)
    {
        return (String) NUMERIC_LITERALS.get(type);
    }

    public static void setCompiler(OgnlExpressionCompiler compiler)
    {
        _compiler = compiler;
    }

    public static OgnlExpressionCompiler getCompiler()
    {
        return _compiler;
    }

    public static void compileExpression(OgnlContext context, Node expression, Object root)
            throws Exception
    {
        _compiler.compileExpression(context, expression, root);
    }

    /**
     * Gets the "target" class of an object for looking up accessors that are registered on the
     * target. If the object is a Class object this will return the Class itself, else it will
     * return object's getClass() result.
     */
    public static Class getTargetClass(Object o)
    {
        return (o == null) ? null : ((o instanceof Class) ? (Class) o : o.getClass());
    }

    /**
     * Returns the base name (the class name without the package name prepended) of the object
     * given.
     */
    public static String getBaseName(Object o)
    {
        return (o == null) ? null : getClassBaseName(o.getClass());
    }

    /**
     * Returns the base name (the class name without the package name prepended) of the class given.
     */
    public static String getClassBaseName(Class c)
    {
        String s = c.getName();

        return s.substring(s.lastIndexOf('.') + 1);
    }

    public static String getClassName(Object o, boolean fullyQualified)
    {
        if (!(o instanceof Class))
        {
            o = o.getClass();
        }

        return getClassName((Class) o, fullyQualified);
    }

    public static String getClassName(Class c, boolean fullyQualified)
    {
        return fullyQualified ? c.getName() : getClassBaseName(c);
    }

    /**
     * Returns the package name of the object's class.
     */
    public static String getPackageName(Object o)
    {
        return (o == null) ? null : getClassPackageName(o.getClass());
    }

    /**
     * Returns the package name of the class given.
     */
    public static String getClassPackageName(Class c)
    {
        String s = c.getName();
        int i = s.lastIndexOf('.');

        return (i < 0) ? null : s.substring(0, i);
    }

    /**
     * Returns a "pointer" string in the usual format for these things - 0x<hex digits>.
     */
    public static String getPointerString(int num)
    {
        StringBuffer result = new StringBuffer();
        String hex = Integer.toHexString(num), pad;
        Integer l = new Integer(hex.length());

        // result.append(HEX_PREFIX);
        if ((pad = (String) HEX_PADDING.get(l)) == null) {
            StringBuffer pb = new StringBuffer();

            for (int i = hex.length(); i < HEX_LENGTH; i++) {
                pb.append('0');
            }
            pad = new String(pb);
            HEX_PADDING.put(l, pad);
        }
        result.append(pad);
        result.append(hex);
        return new String(result);
    }

    /**
     * Returns a "pointer" string in the usual format for these things - 0x<hex digits> for the
     * object given. This will always return a unique value for each object.
     */
    public static String getPointerString(Object o)
    {
        return getPointerString((o == null) ? 0 : System.identityHashCode(o));
    }

    /**
     * Returns a unique descriptor string that includes the object's class and a unique integer
     * identifier. If fullyQualified is true then the class name will be fully qualified to include
     * the package name, else it will be just the class' base name.
     */
    public static String getUniqueDescriptor(Object object, boolean fullyQualified)
    {
        StringBuffer result = new StringBuffer();

        if (object != null) {
            if (object instanceof Proxy) {
                Class interfaceClass = object.getClass().getInterfaces()[0];

                result.append(getClassName(interfaceClass, fullyQualified));
                result.append('^');
                object = Proxy.getInvocationHandler(object);
            }
            result.append(getClassName(object, fullyQualified));
            result.append('@');
            result.append(getPointerString(object));
        } else {
            result.append(NULL_OBJECT_STRING);
        }
        return new String(result);
    }

    /**
     * Returns a unique descriptor string that includes the object's class' base name and a unique
     * integer identifier.
     */
    public static String getUniqueDescriptor(Object object)
    {
        return getUniqueDescriptor(object, false);
    }

    /**
     * Utility to convert a List into an Object[] array. If the list is zero elements this will
     * return a constant array; toArray() on List always returns a new object and this is wasteful
     * for our purposes.
     */
    public static Object[] toArray(List list)
    {
        Object[] result;
        int size = list.size();

        if (size == 0) {
            result = NoArguments;
        } else {
            result = getObjectArrayPool().create(list.size());
            for (int i = 0; i < size; i++) {
                result[i] = list.get(i);
            }
        }
        return result;
    }

    /**
     * Returns the parameter types of the given method.
     */
    public static Class[] getParameterTypes(Method m)
    {
        synchronized (_methodParameterTypesCache)
        {
            Class[] result;

            if ((result = (Class[]) _methodParameterTypesCache.get(m)) == null)
            {
                _methodParameterTypesCache.put(m, result = m.getParameterTypes());
            }
            return result;
        }
    }

    /**
     * Finds the appropriate parameter types for the given {@link Method} and
     * {@link Class} instance of the type the method is associated with.  Correctly
     * finds generic types if running in >= 1.5 jre as well.
     *
     * @param type The class type the method is being executed against.
     * @param m The method to find types for.
     * @return Array of parameter types for the given method.
     */
    public static Class[] findParameterTypes(Class type, Method m)
    {
        Type[] genTypes = m.getGenericParameterTypes();
        Class[] types = new Class[genTypes.length];;
        boolean noGenericParameter = true;
        for (int i=0; i < genTypes.length; i++)
        {
            if (Class.class.isInstance(genTypes[i]))
            {
                types[i] = (Class) genTypes[i];
                continue;
            }
            noGenericParameter = false;
            break;
        }
        if (noGenericParameter)
        {
            return types;
        }

        if (type == null || !isJdk15())
        {
            return getParameterTypes(m);
        }

        final Type typeGenericSuperclass = type.getGenericSuperclass();
        if (typeGenericSuperclass == null
            || !ParameterizedType.class.isInstance(typeGenericSuperclass)
            || m.getDeclaringClass().getTypeParameters() == null)
        {
            return getParameterTypes(m);
        }

        if ((types = (Class[]) _genericMethodParameterTypesCache.get(m)) != null)
        {
            ParameterizedType genericSuperclass = (ParameterizedType) typeGenericSuperclass;
            if (Arrays.equals(types, genericSuperclass.getActualTypeArguments())) {
                return types;
            }
        }

        ParameterizedType param = (ParameterizedType)typeGenericSuperclass;
        TypeVariable[] declaredTypes = m.getDeclaringClass().getTypeParameters();

        types = new Class[genTypes.length];

        for (int i=0; i < genTypes.length; i++)
        {
            TypeVariable paramType = null;

            if (TypeVariable.class.isInstance(genTypes[i]))
            {
                paramType = (TypeVariable)genTypes[i];
            } else if (GenericArrayType.class.isInstance(genTypes[i]))
            {
                paramType = (TypeVariable) ((GenericArrayType)genTypes[i]).getGenericComponentType();
            }
            else if (ParameterizedType.class.isInstance(genTypes[i]))
            {
                 types[i] = (Class) ((ParameterizedType) genTypes[i]).getRawType();
                 continue;
            } else if (Class.class.isInstance(genTypes[i]))
            {
                types[i] = (Class) genTypes[i];
                continue;
            }

            Class resolved = resolveType(param, paramType, declaredTypes);

            if (resolved != null)
            {
                if (GenericArrayType.class.isInstance(genTypes[i]))
                {
                    resolved = Array.newInstance(resolved, 0).getClass();
                }

                types[i] = resolved;
                continue;
            }

            types[i] = m.getParameterTypes()[i];
        }

        synchronized (_genericMethodParameterTypesCache)
        {
            _genericMethodParameterTypesCache.put(m, types);
        }

        return types;
    }

    static Class resolveType(ParameterizedType param, TypeVariable var, TypeVariable[] declaredTypes)
    {
        if (param.getActualTypeArguments().length < 1)
            return null;

        for (int i=0; i < declaredTypes.length; i++)
        {
            if (!TypeVariable.class.isInstance( param.getActualTypeArguments()[i])
                && declaredTypes[i].getName().equals(var.getName()))
            {
                return (Class) param.getActualTypeArguments()[i];
            }
        }

        /*
        for (int i=0; i < var.getBounds().length; i++)
        {
            Type t = var.getBounds()[i];
            Class resolvedType = null;

            if (ParameterizedType.class.isInstance(t))
            {
                ParameterizedType pparam = (ParameterizedType)t;
                for (int e=0; e < pparam.getActualTypeArguments().length; e++)
                {
                    if (!TypeVariable.class.isInstance(pparam.getActualTypeArguments()[e]))
                        continue;

                    resolvedType = resolveType(pparam, (TypeVariable)pparam.getActualTypeArguments()[e], declaredTypes);
                }
            } else
            {
                resolvedType = findType(param.getActualTypeArguments(), (Class)t);
            }

            if (resolvedType != null)
                return resolvedType;
        }
        */

        return null;
    }

    static Class findType(Type[] types, Class type)
    {
        for (int i = 0; i < types.length; i++)
        {
            if (Class.class.isInstance(types[i]) && type.isAssignableFrom((Class)types[i]))
                return (Class)types[i];
        }

        return null;
    }

    /**
     * Returns the parameter types of the given method.
     */
    public static Class[] getParameterTypes(Constructor c)
    {
        Class[] result;
        if ((result = (Class[]) _ctorParameterTypesCache.get(c)) == null) {
            synchronized (_ctorParameterTypesCache) {
                if ((result = (Class[]) _ctorParameterTypesCache.get(c)) == null) {
                    _ctorParameterTypesCache.put(c, result = c.getParameterTypes());
                }
            }
        }
        return result;
    }

    /**
     * Gets the SecurityManager that OGNL uses to determine permissions for invoking methods.
     *
     * @return SecurityManager for OGNL
     */
    public static SecurityManager getSecurityManager()
    {
        return _securityManager;
    }

    /**
     * Sets the SecurityManager that OGNL uses to determine permissions for invoking methods.
     *
     * @param value SecurityManager to set
     */
    public static void setSecurityManager(SecurityManager value)
    {
        _securityManager = value;
    }

    /**
     * Permission will be named "invoke.<declaring-class>.<method-name>".
     */
    public static Permission getPermission(Method method)
    {
        Permission result;
        Class mc = method.getDeclaringClass();

        synchronized (_invokePermissionCache) {
            Map permissions = (Map) _invokePermissionCache.get(mc);

            if (permissions == null) {
                _invokePermissionCache.put(mc, permissions = new HashMap(101));
            }
            if ((result = (Permission) permissions.get(method.getName())) == null) {
                result = new OgnlInvokePermission("invoke." + mc.getName() + "." + method.getName());
                permissions.put(method.getName(), result);
            }
        }
        return result;
    }

    public static Object invokeMethod(Object target, Method method, Object[] argsArray)
            throws InvocationTargetException, IllegalAccessException
    {
        boolean syncInvoke = false;

        // only synchronize method invocation if it actually requires it

        synchronized(method) {
            if (_methodAccessCache.get(method) == null) {
                if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
                {
                    if (!(((AccessibleObject) method).isAccessible()))
                    {
                        _methodAccessCache.put(method, Boolean.TRUE);
                    } else
                    {
                        _methodAccessCache.put(method, Boolean.FALSE);
                    }
                } else
                {
                    _methodAccessCache.put(method, Boolean.FALSE);
                }
            }
            if (_methodAccessCache.get(method) == Boolean.TRUE) {
                syncInvoke = true;
            }

            if (_methodPermCache.get(method) == null) {
                if (_securityManager != null) {
                    try
                    {
                        _securityManager.checkPermission(getPermission(method));
                        _methodPermCache.put(method, Boolean.TRUE);
                    } catch (SecurityException ex) {
                        _methodPermCache.put(method, Boolean.FALSE);
                        throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                    }
                }
                else {
                    _methodPermCache.put(method, Boolean.TRUE);
                }
            }
            if (_methodPermCache.get(method) == Boolean.FALSE) {
                throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
            }
        }

        Object result;

        if (syncInvoke) //if is not public and is not accessible
        {
            synchronized(method)
            {
                ((AccessibleObject) method).setAccessible(true);
                try {
                    result = method.invoke(target, argsArray);
                } finally {
                    ((AccessibleObject) method).setAccessible(false);
                }
            }
        } else
        {
            result = method.invoke(target, argsArray);
        }

        return result;
    }

    /**
     * Gets the class for a method argument that is appropriate for looking up methods by
     * reflection, by looking for the standard primitive wrapper classes and exchanging for them
     * their underlying primitive class objects. Other classes are passed through unchanged.
     *
     * @param arg an object that is being passed to a method
     * @return the class to use to look up the method
     */
    public static final Class getArgClass(Object arg)
    {
        if (arg == null)
            return null;
        Class c = arg.getClass();
        if (c == Boolean.class)
            return Boolean.TYPE;
        else if (c.getSuperclass() == Number.class) {
            if (c == Integer.class)
                return Integer.TYPE;
            if (c == Double.class)
                return Double.TYPE;
            if (c == Byte.class)
                return Byte.TYPE;
            if (c == Long.class)
                return Long.TYPE;
            if (c == Float.class)
                return Float.TYPE;
            if (c == Short.class)
                return Short.TYPE;
        } else if (c == Character.class)
            return Character.TYPE;
        return c;
    }

    public static Class[] getArgClasses(Object[] args) {
        if (args == null)
            return null;
        Class[] argClasses = new Class[args.length];
        for (int i=0; i<args.length; i++)
            argClasses[i] = getArgClass(args[i]);
        return argClasses;
    }

    /**
     * Tells whether the given object is compatible with the given class ---that is, whether the
     * given object can be passed as an argument to a method or constructor whose parameter type is
     * the given class. If object is null this will return true because null is compatible with any
     * type.
     */
    public static final boolean isTypeCompatible(Object object, Class c) {
        if (object == null)
            return true;
        ArgsCompatbilityReport report = new ArgsCompatbilityReport(0, new boolean[1]);
        if (!isTypeCompatible(getArgClass(object), c, 0, report))
            return false;
        if (report.conversionNeeded[0])
            return false; // we don't allow conversions during this path...
        return true;
    }

    public static final boolean isTypeCompatible(Class parameterClass, Class methodArgumentClass, int index, ArgsCompatbilityReport report)
    {
        if (parameterClass == null) {
            // happens when we cannot determine parameter...
            report.score += 500;
            return true;
        }
        if (parameterClass == methodArgumentClass)
            return true;  // exact match, no additional score
        //if (methodArgumentClass.isPrimitive())
        //    return false; // really? int can be assigned to long... *hmm*
        if (methodArgumentClass.isArray()) {
            if (parameterClass.isArray()) {
                Class pct = parameterClass.getComponentType();
                Class mct = methodArgumentClass.getComponentType();
                if (mct.isAssignableFrom(pct)) {
                    // two arrays are better then a array and a list or other conversions...
                    report.score += 25;
                    return true;
                }
                //return isTypeCompatible(pct, mct, index, report); // check inner classes
            }
            if (Collection.class.isAssignableFrom(parameterClass)) {
                // we have to assume that all Collections carry objects - generics access is of no use during runtime because of
                // Type Erasure - http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Type%20Erasure
                Class mct = methodArgumentClass.getComponentType();
                if (mct == Object.class) {
                    report.conversionNeeded[index]=true;
                    report.score += 30;
                    return true;
                } else {
                    // Okay, the items from the list *might* not match. we better don't do that...
                    return false;
                }
            }
        } else if (Collection.class.isAssignableFrom(methodArgumentClass)) {
            if (parameterClass.isArray()) {
                // TODO get generics type here and do further evaluations...
                report.conversionNeeded[index]=true;
                report.score += 50;
                return true;
            }
            if (Collection.class.isAssignableFrom(parameterClass)) {
                if (methodArgumentClass.isAssignableFrom(parameterClass)) {
                    // direct possible List assignment - good match...
                    report.score += 2;
                    return true;
                }
                // TODO get generics type here and do further evaluations...
                report.conversionNeeded[index]=true;
                report.score += 50;
                return true;
            }
        }
        if (methodArgumentClass.isAssignableFrom(parameterClass)) {
            report.score += 40;  // works but might not the best match - weight of 50..
            return true;
        }
        if (parameterClass.isPrimitive()) {
            Class ptc = (Class)PRIMITIVE_WRAPPER_CLASSES.get(parameterClass);
            if (methodArgumentClass == ptc) {
                report.score += 2;   // quite an good match
                return true;
            }
            if (methodArgumentClass.isAssignableFrom(ptc)) {
                report.score += 10;  // works but might not the best match - weight of 10..
                return true;
            }
        }
        return false;  // dosn't match.
        /*
        boolean result = true;

        if (parameterClass != null) {
            if (methodArgumentClass.isPrimitive()) {
                if (parameterClass != methodArgumentClass) {
                    result = false;
                }
            } else if (!methodArgumentClass.isAssignableFrom(parameterClass)) {
                result = false;
            }
        }
        return result;
        */
    }

    /**
     * Tells whether the given array of objects is compatible with the given array of classes---that
     * is, whether the given array of objects can be passed as arguments to a method or constructor
     * whose parameter types are the given array of classes.
     */
    public static class ArgsCompatbilityReport {
        int score;
        boolean[] conversionNeeded;
        public ArgsCompatbilityReport(int score, boolean[] conversionNeeded) {
            this.score = score;
            this.conversionNeeded = conversionNeeded;
        }
    }

    public static final ArgsCompatbilityReport	NoArgsReport = new ArgsCompatbilityReport(0, new boolean[0]);

    public static boolean areArgsCompatible(Object[] args, Class[] classes)
    {
        ArgsCompatbilityReport report = areArgsCompatible(getArgClasses(args), classes, null);
        if (report == null)
            return false;
        for (boolean conversionNeeded : report.conversionNeeded)
            if (conversionNeeded)
                return false;
        return true;
    }

    public static ArgsCompatbilityReport areArgsCompatible(Class[] args, Class[] classes, Method m)
    {
        boolean varArgs = m != null && isJdk15() && m.isVarArgs();

        if ( args==null || args.length == 0 ) {	// handle methods without arguments
            if ( classes == null || classes.length == 0 )
                return NoArgsReport;
            else
                return null;
        }
        if (args.length != classes.length && !varArgs) {
            return null;
        } else if (varArgs) {
            /*
             *  varArg's start with a penalty of 1000.
             *  There are some java compiler rules that are hopefully reflectet by this penalty:
             *  * Legacy beats Varargs
             *  * Widening beats Varargs
             *  * Boxing beats Varargs
             */
            ArgsCompatbilityReport report = new ArgsCompatbilityReport(1000, new boolean[args.length]);
            /*
             *  varargs signature is: method(type1, type2, typeN, typeV ...)
             *  This means: All arguments up to typeN needs exact matching, all varargs need to match typeV
             */
            if (classes.length - 1 > args.length)
                // we don't have enough arguments to provide the required 'fixed' arguments
                return null;

            // type check on fixed arguments
            for (int index = 0, count = classes.length - 1; index < count; ++index)
                if (!isTypeCompatible(args[index], classes[index], index, report))
                    return null;

            // type check on varargs
            Class varArgsType = classes[classes.length - 1].getComponentType();
            for (int index = classes.length - 1, count = args.length; index < count; ++index)
                if (!isTypeCompatible(args[index], varArgsType, index, report))
                    return null;

            return report;
        } else {
            ArgsCompatbilityReport report = new ArgsCompatbilityReport(0, new boolean[args.length]);
            for (int index = 0, count = args.length; index < count; ++index)
                if (!isTypeCompatible(args[index], classes[index], index, report))
                    return null;
            return report;
        }
    }

    /**
     * Tells whether the first array of classes is more specific than the second. Assumes that the
     * two arrays are of the same length.
     */
    public static final boolean isMoreSpecific(Class[] classes1, Class[] classes2)
    {
        for (int index = 0, count = classes1.length; index < count; ++index) {
            Class c1 = classes1[index], c2 = classes2[index];
            if (c1 == c2)
                continue;
            else if (c1.isPrimitive())
                return true;
            else if (c1.isAssignableFrom(c2))
                return false;
            else if (c2.isAssignableFrom(c1))
                return true;
        }

        // They are the same! So the first is not more specific than the second.
        return false;
    }

    public static String getModifierString(int modifiers)
    {
        String result;

        if (Modifier.isPublic(modifiers))
            result = "public";
        else if (Modifier.isProtected(modifiers))
            result = "protected";
        else if (Modifier.isPrivate(modifiers))
            result = "private";
        else
            result = "";
        if (Modifier.isStatic(modifiers))
            result = "static " + result;
        if (Modifier.isFinal(modifiers))
            result = "final " + result;
        if (Modifier.isNative(modifiers))
            result = "native " + result;
        if (Modifier.isSynchronized(modifiers))
            result = "synchronized " + result;
        if (Modifier.isTransient(modifiers))
            result = "transient " + result;
        return result;
    }

    public static Class classForName(OgnlContext context, String className)
            throws ClassNotFoundException
    {
        Class result = (Class) _primitiveTypes.get(className);

        if (result == null) {
            ClassResolver resolver;

            if ((context == null) || ((resolver = context.getClassResolver()) == null)) {
                resolver = new DefaultClassResolver();
            }
            result = resolver.classForName(className, context);
        }

        if (result == null)
            throw new ClassNotFoundException("Unable to resolve class: " + className);

        return result;
    }

    public static boolean isInstance(OgnlContext context, Object value, String className)
            throws OgnlException
    {
        try {
            Class c = classForName(context, className);
            return c.isInstance(value);
        } catch (ClassNotFoundException e) {
            throw new OgnlException("No such class: " + className, e);
        }
    }

    public static Object getPrimitiveDefaultValue(Class forClass)
    {
        return _primitiveDefaults.get(forClass);
    }

    public static Object getNumericDefaultValue(Class forClass)
    {
        return NUMERIC_DEFAULTS.get(forClass);
    }

    public static Object getConvertedType(OgnlContext context, Object target, Member member, String propertyName,
                                          Object value, Class type)
    {
        return context.getTypeConverter().convertValue(context, target, member, propertyName, value, type);
    }

    public static boolean getConvertedTypes(OgnlContext context, Object target, Member member, String propertyName,
                                            Class[] parameterTypes, Object[] args, Object[] newArgs)
    {
        boolean result = false;

        if (parameterTypes.length == args.length) {
            result = true;
            for (int i = 0, ilast = parameterTypes.length - 1; result && (i <= ilast); i++) {
                Object arg = args[i];
                Class type = parameterTypes[i];

                if (isTypeCompatible(arg, type)) {
                    newArgs[i] = arg;
                } else {
                    Object v = getConvertedType(context, target, member, propertyName, arg, type);

                    if (v == OgnlRuntime.NoConversionPossible) {
                        result = false;
                    } else {
                        newArgs[i] = v;
                    }
                }
            }
        }
        return result;
    }

    public static Constructor getConvertedConstructorAndArgs(OgnlContext context, Object target, List constructors,
                                                             Object[] args, Object[] newArgs)
    {
        Constructor result = null;
        TypeConverter converter = context.getTypeConverter();

        if ((converter != null) && (constructors != null)) {
            for (int i = 0, icount = constructors.size(); (result == null) && (i < icount); i++) {
                Constructor ctor = (Constructor) constructors.get(i);
                Class[] parameterTypes = getParameterTypes(ctor);

                if (getConvertedTypes(context, target, ctor, null, parameterTypes, args, newArgs)) {
                    result = ctor;
                }
            }
        }
        return result;
    }

    /**
     * Gets the appropriate method to be called for the given target, method name and arguments. If
     * successful this method will return the Method within the target that can be called and the
     * converted arguments in actualArgs. If unsuccessful this method will return null and the
     * actualArgs will be empty.
     *
     * @param context The current execution context.
     * @param source Target object to run against or method name.
     * @param target Instance of object to be run against.
     * @param propertyName Name of property to get method of.
     * @param methods List of current known methods.
     * @param args Arguments originally passed in.
     * @param actualArgs Converted arguments.
     *
     * @return Best method match or null if none could be found.
     */
    public static Method getAppropriateMethod(OgnlContext context, Object source, Object target, String propertyName,
                                              String methodName, List methods, Object[] args, Object[] actualArgs)
    {
        Method result = null;

        if (methods != null)
        {
            Class typeClass = target != null ? target.getClass() : null;
            if (typeClass == null && source != null && Class.class.isInstance(source))
            {
                typeClass = (Class)source;
            }
            Class[] argClasses = getArgClasses(args);

            MatchingMethod mm = findBestMethod(methods, typeClass, methodName, argClasses);
            if (mm != null) {
                result = mm.mMethod;
                Class[] mParameterTypes = mm.mParameterTypes;
                System.arraycopy(args, 0, actualArgs, 0, args.length);

                for (int j = 0; j < mParameterTypes.length; j++)
                {
                    Class type = mParameterTypes[j];

                    if (mm.report.conversionNeeded[j] || (type.isPrimitive() && (actualArgs[j] == null)))
                    {
                        actualArgs[j] = getConvertedType(context, source, result, propertyName, args[j], type);
                    }
                }
            }
        }

        if (result == null)
        {
            result = getConvertedMethodAndArgs(context, target, propertyName, methods, args, actualArgs);
        }

        return result;
    }

    public static Method getConvertedMethodAndArgs(OgnlContext context, Object target, String propertyName,
            List methods, Object[] args, Object[] newArgs) {
        Method result = null;
        TypeConverter converter = context.getTypeConverter();

        if ((converter != null) && (methods != null)) {
            for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) {
                Method m = (Method) methods.get(i);
                Class[] parameterTypes = findParameterTypes(target != null ? target.getClass() : null, m);//getParameterTypes(m);

                if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs)) {
                    result = m;
                }
            }
        }
        return result;
    }

    private static class MatchingMethod {
        Method                  mMethod;
        int                     score;
        ArgsCompatbilityReport  report;
        Class[]                 mParameterTypes;

        private MatchingMethod(Method method, int score, ArgsCompatbilityReport report, Class[] mParameterTypes) {
            this.mMethod = method;
            this.score = score;
            this.report = report;
            this.mParameterTypes = mParameterTypes;
        }
    }

    private static MatchingMethod findBestMethod(List methods, Class typeClass, String name, Class[] argClasses) {
        MatchingMethod mm = null;
        IllegalArgumentException failure = null;
        for (int i = 0, icount = methods.size(); i < icount; i++) {
            Method m = (Method) methods.get(i);

            Class[] mParameterTypes = findParameterTypes(typeClass, m);
            ArgsCompatbilityReport report = areArgsCompatible(argClasses, mParameterTypes, m);
            if (report == null)
                continue;

            String methodName = m.getName();
            int score = report.score;
            if (name.equals(methodName)) {
                // exact match - no additinal score...
            } else if (name.equalsIgnoreCase(methodName)) {
                // minimal penalty..
                score += 200;
            } else if (methodName.toLowerCase().endsWith(name.toLowerCase())) {
                // has a prefix...
                score += 500;
            } else {
                // just in case...
                score += 5000;
            }
            if (mm == null || mm.score > score) {
                mm = new MatchingMethod(m, score, report, mParameterTypes);
                failure = null;
            } else if (mm.score == score) {
                // it happens that we see the same method signature multiple times - for the current class or interfaces ...
                // TODO why are all of them on the list and not only the most specific one?
                // check for same signature
                if (Arrays.equals(mm.mMethod.getParameterTypes(), m.getParameterTypes()) && mm.mMethod.getName().equals(m.getName())) {
                    boolean retsAreEqual = mm.mMethod.getReturnType().equals(m.getReturnType());
                    // it is the same method. we use the most specific one...
                    if (mm.mMethod.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) {
                        if (!retsAreEqual && !mm.mMethod.getReturnType().isAssignableFrom(m.getReturnType()))
                            System.err.println("Two methods with same method signature but return types conflict? \""+mm.mMethod+"\" and \""+m+"\" please report!");

                        mm = new MatchingMethod(m, score, report, mParameterTypes);
                        failure = null;
                    } else if (!m.getDeclaringClass().isAssignableFrom(mm.mMethod.getDeclaringClass())) {
                        // this should't happen
                        System.err.println("Two methods with same method signature but not providing classes assignable? \""+mm.mMethod+"\" and \""+m+"\" please report!");
                    } else if (!retsAreEqual && !m.getReturnType().isAssignableFrom(mm.mMethod.getReturnType()))
                        System.err.println("Two methods with same method signature but return types conflict? \""+mm.mMethod+"\" and \""+m+"\" please report!");
                } else {
                    // two methods with same score - direct compare to find the better one...
                    // legacy wins over varargs
                    if (isJdk15() && (m.isVarArgs() || mm.mMethod.isVarArgs())) {
                        if (m.isVarArgs() && !mm.mMethod.isVarArgs()) {
                            // keep with current
                        } else if (!m.isVarArgs() && mm.mMethod.isVarArgs()) {
                            // legacy wins...
                            mm = new MatchingMethod(m, score, report, mParameterTypes);
                            failure = null;
                        } else {
                            // both arguments are varargs...
                            System.err.println("Two vararg methods with same score("+score+"): \""+mm.mMethod+"\" and \""+m+"\" please report!");
                        }
                    } else {
                        int scoreCurr = 0;
                        int scoreOther = 0;
                        for (int j=0; j<argClasses.length; j++) {
                            Class argClass = argClasses[j];
                            Class mcClass = mm.mParameterTypes[j];
                            Class moClass = mParameterTypes[j];
                            if (argClass == null) {	// TODO can we avoid this case?
                                // we don't know the class. use the most generic implementation...
                                if (mcClass == moClass) {
                                    // equal args - no winner...
                                } else if (mcClass.isAssignableFrom(moClass)) {
                                    scoreOther += 1000;	// current wins...
                                } else if (moClass.isAssignableFrom(moClass)) {
                                    scoreCurr += 1000;	// other wins...
                                } else {
                                    // both items can't be assigned to each other..
                                    failure = new IllegalArgumentException("Can't decide wich method to use: \""+mm.mMethod+"\" or \""+m+"\"");
                                }
                            } else {
                                // we try to find the more specific implementation
                                if (mcClass == moClass) {
                                    // equal args - no winner...
                                } else if (mcClass == argClass) {
                                    scoreOther += 100;	// current wins...
                                } else if (moClass == argClass) {
                                    scoreCurr += 100;	// other wins...
                                } else {
                                    // both items can't be assigned to each other..
                                    // TODO: if this happens we have to put some weight on the inheritance...
                                    failure = new IllegalArgumentException("Can't decide wich method to use: \""+mm.mMethod+"\" or \""+m+"\"");
                                }
                            }
                        }
                        if (scoreCurr == scoreOther) {
                            if (failure == null)
                                System.err.println("Two methods with same score("+score+"): \""+mm.mMethod+"\" and \""+m+"\" please report!");
                        } else if (scoreCurr > scoreOther) {
                            // other wins...
                            mm = new MatchingMethod(m, score, report, mParameterTypes);
                            failure = null;
                        } // else current one wins...
                    }
                }
            }
        }
        if (failure != null)
            throw failure;
        return mm;
    }

    public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName,
                                               String propertyName, List methods, Object[] args)
            throws MethodFailedException
    {
        Throwable reason = null;
        Object[] actualArgs = _objectArrayPool.create(args.length);

        try {
            Method method = getAppropriateMethod(context, source, target, propertyName, methodName, methods, args, actualArgs);

            if ((method == null) || !isMethodAccessible(context, source, method, propertyName))
            {
                StringBuffer buffer = new StringBuffer();
                String className = "";

                if (target != null)
                {
                    className = target.getClass().getName() + ".";
                }

                for (int i = 0, ilast = args.length - 1; i <= ilast; i++)
                {
                    Object arg = args[i];

                    buffer.append((arg == null) ? NULL_STRING : arg.getClass().getName());
                    if (i < ilast)
                    {
                        buffer.append(", ");
                    }
                }

                throw new NoSuchMethodException(className + methodName + "(" + buffer + ")");
            }

            Object[] convertedArgs = actualArgs;

            if (isJdk15() && method.isVarArgs())
            {
                Class[] parmTypes = method.getParameterTypes();

                // split arguments in to two dimensional array for varargs reflection invocation
                // where it is expected that the parameter passed in to invoke the method
                // will look like "new Object[] { arrayOfNonVarArgsArguments, arrayOfVarArgsArguments }"

                for (int i=0; i < parmTypes.length; i++)
                {
                    if (parmTypes[i].isArray())
                    {
                        convertedArgs = new Object[i + 1];
                        System.arraycopy(actualArgs, 0, convertedArgs, 0, convertedArgs.length);

                        Object[] varArgs;

                        // if they passed in varargs arguments grab them and dump in to new varargs array

                        if (actualArgs.length > i)
                        {
                            ArrayList varArgsList = new ArrayList();
                            for (int j=i; j < actualArgs.length; j++)
                            {
                                if (actualArgs[j] != null)
                                {
                                    varArgsList.add(actualArgs[j]);
                                }
                            }

                            varArgs = varArgsList.toArray();
                        } else
                        {
                            varArgs = new Object[0];
                        }

                        convertedArgs[i] = varArgs;
                        break;
                    }
                }
            }

            return invokeMethod(target, method, convertedArgs);

        } catch (NoSuchMethodException e) {
            reason = e;
        } catch (IllegalAccessException e) {
            reason = e;
        } catch (InvocationTargetException e) {
            reason = e.getTargetException();
        } finally {
            _objectArrayPool.recycle(actualArgs);
        }

        throw new MethodFailedException(source, methodName, reason);
    }

    public static Object callStaticMethod(OgnlContext context, String className, String methodName, Object[] args)
            throws OgnlException
    {
        try {
            Class targetClass = classForName(context, className);
            if (targetClass == null)
                throw new ClassNotFoundException("Unable to resolve class with name " + className);

            MethodAccessor ma = getMethodAccessor(targetClass);

            return ma.callStaticMethod(context, targetClass, methodName, args);
        } catch (ClassNotFoundException ex) {
            throw new MethodFailedException(className, methodName, ex);
        }
    }

    /**
     * Invokes the specified method against the target object.
     *
     * @param context
     *          The current execution context.
     * @param target
     *          The object to invoke the method on.
     * @param methodName
     *          Name of the method - as in "getValue" or "add", etc..
     * @param propertyName
     *          Name of the property to call instead?
     * @param args
     *          Optional arguments needed for method.
     * @return Result of invoking method.
     *
     * @deprecated Use {@link #callMethod(OgnlContext, Object, String, Object[])} instead.
     * @throws OgnlException For lots of different reasons.
     */
    public static Object callMethod(OgnlContext context, Object target, String methodName, String propertyName, Object[] args)
            throws OgnlException
    {
        return callMethod(context, target, methodName == null ? propertyName : methodName, args);
    }

    /**
     * Invokes the specified method against the target object.
     *
     * @param context
     *          The current execution context.
     * @param target
     *          The object to invoke the method on.
     * @param methodName
     *          Name of the method - as in "getValue" or "add", etc..
     * @param args
     *          Optional arguments needed for method.
     * @return Result of invoking method.
     *
     * @throws OgnlException For lots of different reasons.
     */
    public static Object callMethod(OgnlContext context, Object target, String methodName, Object[] args)
            throws OgnlException
    {
        if (target == null)
            throw new NullPointerException("target is null for method " + methodName);

        return getMethodAccessor(target.getClass()).callMethod(context, target, methodName, args);
    }

    public static Object callConstructor(OgnlContext context, String className, Object[] args)
            throws OgnlException
    {
        Throwable reason = null;
        Object[] actualArgs = args;

        try {
            Constructor ctor = null;
            Class[] ctorParameterTypes = null;
            Class target = classForName(context, className);
            List constructors = getConstructors(target);

            for (int i = 0, icount = constructors.size(); i < icount; i++) {
                Constructor c = (Constructor) constructors.get(i);
                Class[] cParameterTypes = getParameterTypes(c);

                if (areArgsCompatible(args, cParameterTypes)
                    && (ctor == null || isMoreSpecific(cParameterTypes, ctorParameterTypes))) {
                    ctor = c;
                    ctorParameterTypes = cParameterTypes;
                }
            }
            if (ctor == null) {
                actualArgs = _objectArrayPool.create(args.length);
                if ((ctor = getConvertedConstructorAndArgs(context, target, constructors, args, actualArgs)) == null) {
                    throw new NoSuchMethodException();
                }
            }
            if (!context.getMemberAccess().isAccessible(context, target, ctor, null)) {
                throw new IllegalAccessException(
                        "access denied to " + target.getName() + "()");
            }
            return ctor.newInstance(actualArgs);
        } catch (ClassNotFoundException e) {
            reason = e;
        } catch (NoSuchMethodException e) {
            reason = e;
        } catch (IllegalAccessException e) {
            reason = e;
        } catch (InvocationTargetException e) {
            reason = e.getTargetException();
        } catch (InstantiationException e) {
            reason = e;
        } finally {
            if (actualArgs != args) {
                _objectArrayPool.recycle(actualArgs);
            }
        }

        throw new MethodFailedException(className, "new", reason);
    }

    /**
     * Don't use this method as it doesn't check member access rights via {@link MemberAccess} interface
     */
    @Deprecated
    public static final Object getMethodValue(OgnlContext context, Object target, String propertyName)
            throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        return getMethodValue(context, target, propertyName, false);
    }

    /**
     * If the checkAccessAndExistence flag is true this method will check to see if the method
     * exists and if it is accessible according to the context's MemberAccess. If neither test
     * passes this will return NotFound.
     */
    public static final Object getMethodValue(OgnlContext context, Object target, String propertyName,
                                              boolean checkAccessAndExistence)
            throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        Object result = null;
        Method m = getGetMethod(context, (target == null) ? null : target.getClass() , propertyName);
        if (m == null)
            m = getReadMethod((target == null) ? null : target.getClass(), propertyName, null);

        if (checkAccessAndExistence)
        {
            if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName))
            {
                result = NotFound;
            }
        }
        if (result == null) {
            if (m != null) {
                try {
                    result = invokeMethod(target, m, NoArguments);
                } catch (InvocationTargetException ex) {
                    throw new OgnlException(propertyName, ex.getTargetException());
                }
            } else {
                throw new NoSuchMethodException(propertyName);
            }
        }
        return result;
    }

    /**
     * Don't use this method as it doesn't check member access rights via {@link MemberAccess} interface
     */
    @Deprecated
    public static boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value)
            throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        return setMethodValue(context, target, propertyName, value, false);
    }

    public static boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value,
                                         boolean checkAccessAndExistence)
            throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        boolean result = true;
        Method m = getSetMethod(context, (target == null) ? null : target.getClass(), propertyName);

        if (checkAccessAndExistence)
        {
            if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName))
            {
                result = false;
            }
        }

        if (result)
        {
            if (m != null)
            {
                Object[] args = _objectArrayPool.create(value);

                try
                {
                    callAppropriateMethod(context, target, target, m.getName(), propertyName,
                                          Collections.nCopies(1, m), args);
                } finally
                {
                    _objectArrayPool.recycle(args);
                }
            } else
            {
                result = false;
            }
        }

        return result;
    }

    public static List getConstructors(Class targetClass)
    {
        List result;
        if ((result = (List) _constructorCache.get(targetClass)) == null) {
            synchronized (_constructorCache) {
                if ((result = (List) _constructorCache.get(targetClass)) == null) {
                    _constructorCache.put(targetClass, result = Arrays.asList(targetClass.getConstructors()));
                }
            }
        }
        return result;
    }

    public static Map getMethods(Class targetClass, boolean staticMethods)
    {
        ClassCache cache = (staticMethods ? _staticMethodCache : _instanceMethodCache);
        Map result;

        if ((result = (Map) cache.get(targetClass)) == null) {
            synchronized (cache)
            {
                if ((result = (Map) cache.get(targetClass)) == null)
                {
                    result = new HashMap(23);
                    collectMethods(targetClass, result, staticMethods);
                    cache.put(targetClass, result);
                }
            }
        }
        return result;
    }

    private static void collectMethods(Class c, Map result, boolean staticMethods) {
        final Method[] ma = c.getDeclaredMethods();
        for (int i = 0, icount = ma.length; i < icount; i++)
        {
            if (c.isInterface())
            {
                if (isDefaultMethod(ma[i]))
                    addMethodToResult(result, ma[i]);
                continue;
            }

            // skip over synthetic methods
            if (!isMethodCallable(ma[i]))
                continue;

            if (Modifier.isStatic(ma[i].getModifiers()) == staticMethods)
                addMethodToResult(result, ma[i]);
        }

        final Class superclass = c.getSuperclass();
        if (superclass != null)
            collectMethods(superclass, result, staticMethods);

        for (final Class iface : c.getInterfaces())
            collectMethods(iface, result, staticMethods);
    }

    private static void addMethodToResult(Map result, Method method)
    {
        List ml = (List) result.get(method.getName());
        if (ml == null)
            result.put(method.getName(), ml = new ArrayList());
        ml.add(method);
    }

    /**
     * Backport of java.lang.reflect.Method#isDefault()
     */
    private static boolean isDefaultMethod(Method method)
    {
        return ((method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
                && method.getDeclaringClass().isInterface();
    }

    public static Map getAllMethods(Class targetClass, boolean staticMethods)
    {
        ClassCache cache = (staticMethods ? _staticMethodCache : _instanceMethodCache);
        Map result;

        if ((result = (Map) cache.get(targetClass)) == null) {
            synchronized (cache)
            {
                if ((result = (Map) cache.get(targetClass)) == null)
                {
                    result = new HashMap(23);

                    for (Class c = targetClass; c != null; c = c.getSuperclass())
                    {
                        Method[] ma = c.getMethods();

                        for (int i = 0, icount = ma.length; i < icount; i++)
                        {
                            // skip over synthetic methods

                            if (!isMethodCallable(ma[i]))
                                continue;

                            if (Modifier.isStatic(ma[i].getModifiers()) == staticMethods)
                            {
                                List ml = (List) result.get(ma[i].getName());

                                if (ml == null)
                                    result.put(ma[i].getName(), ml = new ArrayList());

                                ml.add(ma[i]);
                            }
                        }
                    }
                    cache.put(targetClass, result);
                }
            }
        }
        return result;
    }

    public static List getMethods(Class targetClass, String name, boolean staticMethods)
    {
        return (List) getMethods(targetClass, staticMethods).get(name);
    }

    public static List getAllMethods(Class targetClass, String name, boolean staticMethods)
    {
        return (List) getAllMethods(targetClass, staticMethods).get(name);
    }

    public static Map getFields(Class targetClass)
    {
        Map result;

        if ((result = (Map) _fieldCache.get(targetClass)) == null) {
            synchronized (_fieldCache) {
                if ((result = (Map) _fieldCache.get(targetClass)) == null) {
                    Field fa[];

                    result = new HashMap(23);
                    fa = targetClass.getDeclaredFields();
                    for (int i = 0; i < fa.length; i++) {
                        result.put(fa[i].getName(), fa[i]);
                    }
                    _fieldCache.put(targetClass, result);
                }
            }
        }
        return result;
    }

    public static Field getField(Class inClass, String name)
    {
        Field result = null;

        Object o = getFields(inClass).get(name);
        if(o == null) {
            synchronized (_fieldCache) {
                o = getFields(inClass).get(name);

                if (o == null) {
                    _superclasses.clear();
                    for (Class sc = inClass; (sc != null); sc = sc.getSuperclass()) {
                        if ((o = getFields(sc).get(name)) == NotFound)
                            break;

                        _superclasses.add(sc);

                        if ((result = (Field) o) != null)
                            break;
                    }
                    /*
                     * Bubble the found value (either cache miss or actual field) to all supeclasses
                     * that we saw for quicker access next time.
                     */
                    for (int i = 0, icount = _superclasses.size(); i < icount; i++) {
                        getFields((Class) _superclasses.get(i)).put(name, (result == null) ? NotFound : result);
                    }
                } else {
                    if (o instanceof Field) {
                        result = (Field) o;
                    } else {
                        if (result == NotFound)
                            result = null;
                    }
                }
            }
        }
        else {
            if (o instanceof Field) {
                result = (Field) o;
            } else {
                if (result == NotFound)
                    result = null;
            }
        }
        return result;
    }

    /**
     * Don't use this method as it doesn't check member access rights via {@link MemberAccess} interface
     */
    @Deprecated
    public static Object getFieldValue(OgnlContext context, Object target, String propertyName)
            throws NoSuchFieldException
    {
        return getFieldValue(context, target, propertyName, false);
    }

    public static Object getFieldValue(OgnlContext context, Object target, String propertyName,
                                       boolean checkAccessAndExistence)
            throws NoSuchFieldException
    {
        Object result = null;
        final Field f = getField((target == null) ? null : target.getClass(), propertyName);

        if (checkAccessAndExistence) {
            if ((f == null) || !context.getMemberAccess().isAccessible(context, target, f, propertyName)) {
                result = NotFound;
            }
        }
        if (result == null) {
            if (f == null) {
                throw new NoSuchFieldException(propertyName);
            } else {
                try {

                    if (!Modifier.isStatic(f.getModifiers())) {
                        final Object state = context.getMemberAccess().setup(context, target, f, propertyName);
                        try {
                            result = f.get(target);
                        } finally {
                            context.getMemberAccess().restore(context, target, f, propertyName, state);
                        }
                    } else {
                        throw new NoSuchFieldException(propertyName);
                    }

                } catch (IllegalAccessException ex) {
                    throw new NoSuchFieldException(propertyName);
                }
            }
        }
        return result;
    }

    public static boolean setFieldValue(OgnlContext context, Object target, String propertyName, Object value)
            throws OgnlException
    {
        boolean result = false;

        try {
            final Field f = getField((target == null) ? null : target.getClass(), propertyName);

            if (f != null) {
                final int fModifiers = f.getModifiers();
                if (!Modifier.isStatic(f.getModifiers()) && !Modifier.isFinal(fModifiers)) {
                    final Object state = context.getMemberAccess().setup(context, target, f, propertyName);
                    try {
                        if (isTypeCompatible(value, f.getType())
                            || ((value = getConvertedType(context, target, f, propertyName, value, f.getType())) != null)) {
                            f.set(target, value);
                            result = true;
                        }
                    } finally {
                        context.getMemberAccess().restore(context, target, f, propertyName, state);
                    }
                }
            }
        } catch (IllegalAccessException ex) {
            throw new NoSuchPropertyException(target, propertyName, ex);
        }
        return result;
    }

    public static boolean isFieldAccessible(OgnlContext context, Object target, Class inClass, String propertyName)
    {
        return isFieldAccessible(context, target, getField(inClass, propertyName), propertyName);
    }

    public static boolean isFieldAccessible(OgnlContext context, Object target, Field field, String propertyName)
    {
        return context.getMemberAccess().isAccessible(context, target, field, propertyName);
    }

    public static boolean hasField(OgnlContext context, Object target, Class inClass, String propertyName)
    {
        Field f = getField(inClass, propertyName);

        return (f != null) && isFieldAccessible(context, target, f, propertyName);
    }

    /**
     * Method name is getStaticField(), but actually behaves more like "getStaticFieldValue()".
     *

     * Typical usage: Returns the value (not the actual {@link Field}) for the given (static) fieldName.
     * May return the {@link Enum} constant value for the given fieldName when className is an {@link Enum}.
     * May return a {@link Class} instance when the given fieldName is "class".
     *

     * @param context    The current ognl context
     * @param className  The name of the class which contains the field
     * @param fieldName  The name of the field whose value should be returned
     *
     * @return           The value of the (static) fieldName
     * @throws OgnlException
     */
    public static Object getStaticField(OgnlContext context, String className, String fieldName)
            throws OgnlException
    {
        Exception reason = null;
        try {
            final Class c = classForName(context, className);

            if (c == null) {
                throw new OgnlException("Unable to find class " + className + " when resolving field name of " + fieldName);
            }

            /*
             * Check for virtual static field "class"; this cannot interfere with normal static
             * fields because it is a reserved word.
             */
            if (fieldName.equals("class"))
            {
                return c;
            } else if (OgnlRuntime.isJdk15() && c.isEnum())
            {
                try {
                    return Enum.valueOf(c, fieldName);
                } catch (IllegalArgumentException e) {
                    // ignore it, try static field
                }
            }

            final Field f = getField(c, fieldName);
            if (f == null) {
                throw new NoSuchFieldException(fieldName);
            }
            if (!Modifier.isStatic(f.getModifiers())) {
                throw new OgnlException("Field " + fieldName + " of class " + className + " is not static");
            }

            Object result = null;
            if (context.getMemberAccess().isAccessible(context, null, f, null)) {
                final Object state = context.getMemberAccess().setup(context, null, f, null);
                try {
                    result = f.get(null);
                } finally {
                    context.getMemberAccess().restore(context, null, f, null, state);
                }
            } else {
                throw new IllegalAccessException("Access to " + fieldName  + " of class " + className + " is forbidden");
            }

            return result;
        } catch (ClassNotFoundException e) {
            reason = e;
        } catch (NoSuchFieldException e) {
            reason = e;
        } catch (SecurityException e) {
            reason = e;
        } catch (IllegalAccessException e) {
            reason = e;
        }

        throw new OgnlException("Could not get static field " + fieldName + " from class " + className, reason);
    }

    private static String capitalizeBeanPropertyName(String propertyName) {
        if (propertyName.length() == 1) {
            return propertyName.toUpperCase();
        }
        // don't capitalize getters/setters
        if (propertyName.startsWith(GET_PREFIX) && propertyName.endsWith("()")) {
            if (Character.isUpperCase(propertyName.substring(3,4).charAt(0))) {
                return propertyName;
            }
        }
        if (propertyName.startsWith(SET_PREFIX) && propertyName.endsWith(")")) {
            if (Character.isUpperCase(propertyName.substring(3,4).charAt(0))) {
                return propertyName;
            }
        }
        if (propertyName.startsWith(IS_PREFIX) && propertyName.endsWith("()")) {
            if (Character.isUpperCase(propertyName.substring(2,3).charAt(0))) {
                return propertyName;
            }
        }
        char first = propertyName.charAt(0);
        char second = propertyName.charAt(1);
        if (Character.isLowerCase(first) && Character.isUpperCase(second)) {
            return propertyName;
        } else {
            char[] chars = propertyName.toCharArray();
            chars[0] = Character.toUpperCase(chars[0]);
            return new String(chars);
        }
    }

    public static List getDeclaredMethods(Class targetClass, String propertyName, boolean findSets)
    {
        List result = null;
        ClassCache cache = _declaredMethods[findSets ? 0 : 1];

        Map propertyCache = (Map) cache.get(targetClass);
        if ((propertyCache == null) || ((result = (List) propertyCache.get(propertyName)) == null)) {
            synchronized (cache) {
                propertyCache = (Map) cache.get(targetClass);

                if ((propertyCache == null) || ((result = (List) propertyCache.get(propertyName)) == null)) {

                    String baseName = capitalizeBeanPropertyName(propertyName);
                    result = new ArrayList();
                    collectAccessors(targetClass, baseName, result, findSets);

                    if (propertyCache == null) {
                        cache.put(targetClass, propertyCache = new HashMap(101));
                    }
                    propertyCache.put(propertyName, result.isEmpty() ? NotFoundList : result);

                    return result.isEmpty() ? null : result;
                }
            }
        }
        return (result == NotFoundList) ? null : result;
    }

    private static void collectAccessors(Class c, String baseName, List result, boolean findSets)
    {
        final Method[] methods = c.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            if (c.isInterface()) {
                if (isDefaultMethod(methods[i])) {
                    addIfAccessor(result, methods[i], baseName, findSets);
                }
                continue;
            }

            if (!isMethodCallable(methods[i]))
                continue;

            addIfAccessor(result, methods[i], baseName, findSets);
        }

        final Class superclass = c.getSuperclass();
        if (superclass != null)
            collectAccessors(superclass, baseName, result, findSets);

        for (final Class iface : c.getInterfaces())
            collectAccessors(iface, baseName, result, findSets);
    }

    private static void addIfAccessor(List result, Method method, String baseName, boolean findSets)
    {
        final String ms = method.getName();
        if (ms.endsWith(baseName)) {
            boolean isSet = false, isIs = false;
            if ((isSet = ms.startsWith(SET_PREFIX)) || ms.startsWith(GET_PREFIX)
                    || (isIs = ms.startsWith(IS_PREFIX))) {
                int prefixLength = (isIs ? 2 : 3);
                if (isSet == findSets) {
                    if (baseName.length() == (ms.length() - prefixLength)) {
                        result.add(method);
                    }
                }
            }
        }
    }

    /**
     * Convenience used to check if a method is volatile or synthetic so as to avoid
     * calling un-callable methods.
     *
     * @param m The method to check.
     * @return True if the method should be callable, false otherwise.
     */
    static boolean isMethodCallable(Method m)
    {
        if ((isJdk15() && m.isSynthetic()) || Modifier.isVolatile(m.getModifiers()))
            return false;

        return true;
    }

    /**
     * cache get methods
     */
    public static Method getGetMethod(OgnlContext context, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        // Cache is a map in two levels, so we provide two keys (see comments in ClassPropertyMethodCache below)
        Method method = cacheGetMethod.get(targetClass, propertyName);
        if (method != null)
            return method;

        // By checking key existence now and not before calling 'get', we will save a map resolution 90% of the times
        if (cacheGetMethod.containsKey(targetClass, propertyName))
            return null;

        method = _getGetMethod(context, targetClass, propertyName); // will be null if not found - will cache it anyway
        cacheGetMethod.put(targetClass, propertyName, method);

        return method;
    }

    private static Method _getGetMethod(OgnlContext context, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        Method result = null;

        List methods = getDeclaredMethods(targetClass, propertyName, false /* find 'get' methods */);

        if (methods != null)
        {
            for (int i = 0, icount = methods.size(); i < icount; i++)
            {
                Method m = (Method) methods.get(i);
                Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m);

                if (mParameterTypes.length == 0)
                {
                    result = m;
                    break;
                }
            }
        }

        return result;
    }

    public static boolean isMethodAccessible(OgnlContext context, Object target, Method method, String propertyName)
    {
        return (method != null) && context.getMemberAccess().isAccessible(context, target, method, propertyName);
    }

    public static boolean hasGetMethod(OgnlContext context, Object target, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        return isMethodAccessible(context, target, getGetMethod(context, targetClass, propertyName), propertyName);
    }

    /**
     * cache set methods method
     */
    public static Method getSetMethod(OgnlContext context, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        // Cache is a map in two levels, so we provide two keys (see comments in ClassPropertyMethodCache below)
        Method method = cacheSetMethod.get(targetClass, propertyName);
        if (method != null)
            return method;

        // By checking key existence now and not before calling 'get', we will save a map resolution 90% of the times
        if (cacheSetMethod.containsKey(targetClass, propertyName))
            return null;

        method = _getSetMethod(context, targetClass, propertyName); // will be null if not found - will cache it anyway
        cacheSetMethod.put(targetClass, propertyName, method);

        return method;
    }

    private static Method _getSetMethod(OgnlContext context, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        Method result = null;

        List methods = getDeclaredMethods(targetClass, propertyName, true /* find 'set' methods */);

        if (methods != null)
        {
            for (int i = 0, icount = methods.size(); i < icount; i++)
            {
                Method m = (Method) methods.get(i);
                Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m);

                if (mParameterTypes.length == 1) {
                    result = m;
                    break;
                }
            }
        }

        return result;
    }

    public static final boolean hasSetMethod(OgnlContext context, Object target, Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        return isMethodAccessible(context, target, getSetMethod(context, targetClass, propertyName), propertyName);
    }

    public static final boolean hasGetProperty(OgnlContext context, Object target, Object oname)
            throws IntrospectionException, OgnlException
    {
        Class targetClass = (target == null) ? null : target.getClass();
        String name = oname.toString();

        return hasGetMethod(context, target, targetClass, name) || hasField(context, target, targetClass, name);
    }

    public static final boolean hasSetProperty(OgnlContext context, Object target, Object oname)
            throws IntrospectionException, OgnlException
    {
        Class targetClass = (target == null) ? null : target.getClass();
        String name = oname.toString();

        return hasSetMethod(context, target, targetClass, name) || hasField(context, target, targetClass, name);
    }

    private static final boolean indexMethodCheck(List methods)
    {
        boolean result = false;

        if (methods.size() > 0) {
            Method fm = (Method) methods.get(0);
            Class[] fmpt = getParameterTypes(fm);
            int fmpc = fmpt.length;
            Class lastMethodClass = fm.getDeclaringClass();

            result = true;
            for (int i = 1; result && (i < methods.size()); i++) {
                Method m = (Method) methods.get(i);
                Class c = m.getDeclaringClass();

                // Check to see if more than one method implemented per class
                if (lastMethodClass == c) {
                    result = false;
                } else {
                    Class[] mpt = getParameterTypes(fm);
                    int mpc = fmpt.length;

                    if (fmpc != mpc) {
                        result = false;
                    }
                    for (int j = 0; j < fmpc; j++) {
                        if (fmpt[j] != mpt[j]) {
                            result = false;
                            break;
                        }
                    }
                }
                lastMethodClass = c;
            }
        }
        return result;
    }

    static void findObjectIndexedPropertyDescriptors(Class targetClass, Map intoMap)
            throws OgnlException
    {
        Map allMethods = getMethods(targetClass, false);
        Map pairs = new HashMap(101);

        for (Iterator it = allMethods.keySet().iterator(); it.hasNext();) {
            String methodName = (String) it.next();
            List methods = (List) allMethods.get(methodName);

            /*
             * Only process set/get where there is exactly one implementation of the method per
             * class and those implementations are all the same
             */
            if (indexMethodCheck(methods)) {
                boolean isGet = false, isSet = false;
                Method m = (Method) methods.get(0);

                if (((isSet = methodName.startsWith(SET_PREFIX)) || (isGet = methodName.startsWith(GET_PREFIX)))
                    && (methodName.length() > 3)) {
                    String propertyName = Introspector.decapitalize(methodName.substring(3));
                    Class[] parameterTypes = getParameterTypes(m);
                    int parameterCount = parameterTypes.length;

                    if (isGet && (parameterCount == 1) && (m.getReturnType() != Void.TYPE)) {
                        List pair = (List) pairs.get(propertyName);

                        if (pair == null) {
                            pairs.put(propertyName, pair = new ArrayList());
                        }
                        pair.add(m);
                    }
                    if (isSet && (parameterCount == 2) && (m.getReturnType() == Void.TYPE)) {
                        List pair = (List) pairs.get(propertyName);

                        if (pair == null) {
                            pairs.put(propertyName, pair = new ArrayList());
                        }
                        pair.add(m);
                    }
                }
            }
        }

        for (Iterator it = pairs.keySet().iterator(); it.hasNext();) {
            String propertyName = (String) it.next();
            List methods = (List) pairs.get(propertyName);

            if (methods.size() == 2) {
                Method method1 = (Method) methods.get(0), method2 = (Method) methods.get(1), setMethod = (method1
                        .getParameterTypes().length == 2) ? method1 : method2, getMethod = (setMethod == method1) ? method2
                        : method1;
                Class keyType = getMethod.getParameterTypes()[0], propertyType = getMethod.getReturnType();

                if (keyType == setMethod.getParameterTypes()[0]) {
                    if (propertyType == setMethod.getParameterTypes()[1]) {
                        ObjectIndexedPropertyDescriptor propertyDescriptor;

                        try {
                            propertyDescriptor = new ObjectIndexedPropertyDescriptor(propertyName, propertyType,
                                                                                getMethod, setMethod);
                        } catch (Exception ex) {
                            throw new OgnlException("creating object indexed property descriptor for '" + propertyName
                                                    + "' in " + targetClass, ex);
                        }
                        intoMap.put(propertyName, propertyDescriptor);
                    }
                }

            }
        }
    }

    /**
     * This method returns the property descriptors for the given class as a Map.
     *
     * @param targetClass The class to get the descriptors for.
     * @return Map map of property descriptors for class.
     *
     * @throws IntrospectionException on errors using {@link Introspector}.
     * @throws OgnlException On general errors.
     */
    public static Map getPropertyDescriptors(Class targetClass)
            throws IntrospectionException, OgnlException
    {
        Map result;

        if ((result = (Map) _propertyDescriptorCache.get(targetClass)) == null)
        {
            synchronized (_propertyDescriptorCache) {
                if ((result = (Map) _propertyDescriptorCache.get(targetClass)) == null)
                {
                    PropertyDescriptor[] pda = Introspector.getBeanInfo(targetClass).getPropertyDescriptors();

                    result = new HashMap(101);
                    for (int i = 0, icount = pda.length; i < icount; i++)
                    {
                        // workaround for Introspector bug 6528714 (bugs.sun.com)
                        if (pda[i].getReadMethod() != null && !isMethodCallable(pda[i].getReadMethod()))
                        {
                            pda[i].setReadMethod(findClosestMatchingMethod(targetClass, pda[i].getReadMethod(), pda[i].getName(),
                                                                           pda[i].getPropertyType(), true));
                        }
                        if (pda[i].getWriteMethod() != null && !isMethodCallable(pda[i].getWriteMethod()))
                        {
                            pda[i].setWriteMethod(findClosestMatchingMethod(targetClass, pda[i].getWriteMethod(), pda[i].getName(),
                                                                            pda[i].getPropertyType(), false));
                        }

                        result.put(pda[i].getName(), pda[i]);
                    }

                    findObjectIndexedPropertyDescriptors(targetClass, result);
                    _propertyDescriptorCache.put(targetClass, result);
                }
            }
        }
        return result;
    }

    /**
     * This method returns a PropertyDescriptor for the given class and property name using a Map
     * lookup (using getPropertyDescriptorsMap()).
     */
    public static PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName)
            throws IntrospectionException, OgnlException
    {
        if (targetClass == null)
            return null;

        return (PropertyDescriptor) getPropertyDescriptors(targetClass).get(propertyName);
    }

    static Method findClosestMatchingMethod(Class targetClass, Method m, String propertyName,
                                            Class propertyType, boolean isReadMethod)
    {
        List methods = getDeclaredMethods(targetClass, propertyName, !isReadMethod);

        if (methods != null)
        {
            for (Object method1 : methods)
            {
                Method method = (Method) method1;

                if (method.getName().equals(m.getName())
                        && m.getReturnType().isAssignableFrom(m.getReturnType())
                        && method.getReturnType() == propertyType
                        && method.getParameterTypes().length == m.getParameterTypes().length) {
                    return method;
                }
            }
        }
        return m;
    }

    public static PropertyDescriptor[] getPropertyDescriptorsArray(Class targetClass)
            throws IntrospectionException
    {
        PropertyDescriptor[] result = null;

        if (targetClass != null) {
            if ((result = (PropertyDescriptor[]) _propertyDescriptorCache.get(targetClass)) == null) {
                synchronized (_propertyDescriptorCache) {
                    if ((result = (PropertyDescriptor[]) _propertyDescriptorCache.get(targetClass)) == null) {
                        _propertyDescriptorCache.put(targetClass, result = Introspector.getBeanInfo(targetClass)
                                .getPropertyDescriptors());
                    }
                }
            }
        }
        return result;
    }

    /**
     * Gets the property descriptor with the given name for the target class given.
     *
     * @param targetClass Class for which property descriptor is desired
     * @param name        Name of property
     * @return PropertyDescriptor of the named property or null if the class has no property with
     *         the given name
     */
    public static PropertyDescriptor getPropertyDescriptorFromArray(Class targetClass, String name)
            throws IntrospectionException
    {
        PropertyDescriptor result = null;
        PropertyDescriptor[] pda = getPropertyDescriptorsArray(targetClass);

        for (int i = 0, icount = pda.length; (result == null) && (i < icount); i++) {
            if (pda[i].getName().compareTo(name) == 0) {
                result = pda[i];
            }
        }
        return result;
    }

    public static void setMethodAccessor(Class cls, MethodAccessor accessor)
    {
        synchronized (_methodAccessors) {
            _methodAccessors.put(cls, accessor);
        }
    }

    public static MethodAccessor getMethodAccessor(Class cls)
            throws OgnlException
    {
        MethodAccessor answer = (MethodAccessor) getHandler(cls, _methodAccessors);
        if (answer != null)
            return answer;
        throw new OgnlException("No method accessor for " + cls);
    }

    public static void setPropertyAccessor(Class cls, PropertyAccessor accessor)
    {
        synchronized (_propertyAccessors) {
            _propertyAccessors.put(cls, accessor);
        }
    }

    public static PropertyAccessor getPropertyAccessor(Class cls)
            throws OgnlException
    {
        PropertyAccessor answer = (PropertyAccessor) getHandler(cls, _propertyAccessors);
        if (answer != null)
            return answer;

        throw new OgnlException("No property accessor for class " + cls);
    }

    public static ElementsAccessor getElementsAccessor(Class cls)
            throws OgnlException
    {
        ElementsAccessor answer = (ElementsAccessor) getHandler(cls, _elementsAccessors);
        if (answer != null)
            return answer;
        throw new OgnlException("No elements accessor for class " + cls);
    }

    public static void setElementsAccessor(Class cls, ElementsAccessor accessor)
    {
        synchronized (_elementsAccessors) {
            _elementsAccessors.put(cls, accessor);
        }
    }

    public static NullHandler getNullHandler(Class cls)
            throws OgnlException
    {
        NullHandler answer = (NullHandler) getHandler(cls, _nullHandlers);
        if (answer != null)
            return answer;
        throw new OgnlException("No null handler for class " + cls);
    }

    public static void setNullHandler(Class cls, NullHandler handler)
    {
        synchronized (_nullHandlers) {
            _nullHandlers.put(cls, handler);
        }
    }

    private static Object getHandler(Class forClass, ClassCache handlers)
    {
        Object answer = null;

        if ((answer = handlers.get(forClass)) == null) {
            synchronized (handlers) {
                if ((answer = handlers.get(forClass)) == null) {
                    Class keyFound;

                    if (forClass.isArray()) {
                        answer = handlers.get(Object[].class);
                        keyFound = null;
                    } else {
                        keyFound = forClass;
                        outer:
                        for (Class c = forClass; c != null; c = c.getSuperclass()) {
                            answer = handlers.get(c);
                            if (answer == null) {
                                Class[] interfaces = c.getInterfaces();
                                for (int index = 0, count = interfaces.length; index < count; ++index) {
                                    Class iface = interfaces[index];

                                    answer = handlers.get(iface);
                                    if (answer == null) {
                                        /* Try super-interfaces */
                                        answer = getHandler(iface, handlers);
                                    }
                                    if (answer != null) {
                                        keyFound = iface;
                                        break outer;
                                    }
                                }
                            } else {
                                keyFound = c;
                                break;
                            }
                        }
                    }
                    if (answer != null) {
                        if (keyFound != forClass) {
                            handlers.put(forClass, answer);
                        }
                    }
                }
            }
        }
        return answer;
    }

    public static Object getProperty(OgnlContext context, Object source, Object name)
            throws OgnlException
    {
        PropertyAccessor accessor;

        if (source == null)
        {
            throw new OgnlException("source is null for getProperty(null, \"" + name + "\")");
        }
        if ((accessor = getPropertyAccessor(getTargetClass(source))) == null)
        {
            throw new OgnlException("No property accessor for " + getTargetClass(source).getName());
        }

        return accessor.getProperty(context, source, name);
    }

    public static void setProperty(OgnlContext context, Object target, Object name, Object value)
            throws OgnlException
    {
        PropertyAccessor accessor;

        if (target == null) {
            throw new OgnlException("target is null for setProperty(null, \"" + name + "\", " + value + ")");
        }
        if ((accessor = getPropertyAccessor(getTargetClass(target))) == null) {
            throw new OgnlException("No property accessor for " + getTargetClass(target).getName());
        }

        accessor.setProperty(context, target, name, value);
    }

    /**
     * Determines the index property type, if any. Returns INDEXED_PROPERTY_NONE if
     * the property is not index-accessible as determined by OGNL or JavaBeans. If it is indexable
     * then this will return whether it is a JavaBeans indexed property, conforming to the indexed
     * property patterns (returns INDEXED_PROPERTY_INT) or if it conforms to the
     * OGNL arbitrary object indexable (returns INDEXED_PROPERTY_OBJECT).
     */
    public static int getIndexedPropertyType(OgnlContext context, Class sourceClass, String name)
            throws OgnlException
    {
        int result = INDEXED_PROPERTY_NONE;

        try {
            PropertyDescriptor pd = getPropertyDescriptor(sourceClass, name);
            if (pd != null) {
                if (pd instanceof IndexedPropertyDescriptor) {
                    result = INDEXED_PROPERTY_INT;
                } else {
                    if (pd instanceof ObjectIndexedPropertyDescriptor) {
                        result = INDEXED_PROPERTY_OBJECT;
                    }
                }
            }
        } catch (Exception ex) {
            throw new OgnlException("problem determining if '" + name + "' is an indexed property", ex);
        }
        return result;
    }

    public static Object getIndexedProperty(OgnlContext context, Object source, String name, Object index)
            throws OgnlException
    {
        Object[] args = _objectArrayPool.create(index);

        try {
            PropertyDescriptor pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name);
            Method m;

            if (pd instanceof IndexedPropertyDescriptor) {
                m = ((IndexedPropertyDescriptor) pd).getIndexedReadMethod();
            } else {
                if (pd instanceof ObjectIndexedPropertyDescriptor) {
                    m = ((ObjectIndexedPropertyDescriptor) pd).getIndexedReadMethod();
                } else {
                    throw new OgnlException("property '" + name + "' is not an indexed property");
                }
            }

            return callMethod(context, source, m.getName(), args);

        } catch (OgnlException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new OgnlException("getting indexed property descriptor for '" + name + "'", ex);
        } finally {
            _objectArrayPool.recycle(args);
        }
    }

    public static void setIndexedProperty(OgnlContext context, Object source, String name, Object index,
                                          Object value)
            throws OgnlException
    {
        Object[] args = _objectArrayPool.create(index, value);

        try {
            PropertyDescriptor pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name);
            Method m;

            if (pd instanceof IndexedPropertyDescriptor) {
                m = ((IndexedPropertyDescriptor) pd).getIndexedWriteMethod();
            } else {
                if (pd instanceof ObjectIndexedPropertyDescriptor) {
                    m = ((ObjectIndexedPropertyDescriptor) pd).getIndexedWriteMethod();
                } else {
                    throw new OgnlException("property '" + name + "' is not an indexed property");
                }
            }

            callMethod(context, source, m.getName(), args);

        } catch (OgnlException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new OgnlException("getting indexed property descriptor for '" + name + "'", ex);
        } finally {
            _objectArrayPool.recycle(args);
        }
    }

    public static EvaluationPool getEvaluationPool()
    {
        return _evaluationPool;
    }

    public static ObjectArrayPool getObjectArrayPool()
    {
        return _objectArrayPool;
    }

    /**
     * Registers the specified {@link ClassCacheInspector} with all class reflection based internal
     * caches.  This may have a significant performance impact so be careful using this in production scenarios.
     *
     * @param inspector
     *          The inspector instance that will be registered with all internal cache instances.
     */
    public static void setClassCacheInspector(ClassCacheInspector inspector)
    {
        _cacheInspector = inspector;

        _propertyDescriptorCache.setClassInspector(_cacheInspector);
        _constructorCache.setClassInspector(_cacheInspector);
        _staticMethodCache.setClassInspector(_cacheInspector);
        _instanceMethodCache.setClassInspector(_cacheInspector);
        _invokePermissionCache.setClassInspector(_cacheInspector);
        _fieldCache.setClassInspector(_cacheInspector);
        _declaredMethods[0].setClassInspector(_cacheInspector);
        _declaredMethods[1].setClassInspector(_cacheInspector);
    }

    public static Method getMethod(OgnlContext context, Class target, String name,
                                   Node[] children, boolean includeStatic)
            throws Exception
    {
        Class[] parms;
        if (children != null && children.length > 0)
        {
            parms = new Class[children.length];

            // used to reset context after loop
            Class currType = context.getCurrentType();
            Class currAccessor = context.getCurrentAccessor();
            Object cast = context.get(ExpressionCompiler.PRE_CAST);

            context.setCurrentObject(context.getRoot());
            context.setCurrentType(context.getRoot() != null ? context.getRoot().getClass() : null);
            context.setCurrentAccessor(null);
            context.setPreviousType(null);

            for (int i=0; i < children.length; i++)
            {
                children[i].toGetSourceString(context, context.getRoot());
                parms[i] = context.getCurrentType();
            }

            context.put(ExpressionCompiler.PRE_CAST, cast);

            context.setCurrentType(currType);
            context.setCurrentAccessor(currAccessor);
            context.setCurrentObject(target);
        } else
        {
            parms = EMPTY_CLASS_ARRAY;
        }

        List methods = OgnlRuntime.getMethods(target, name, includeStatic);
        if (methods == null)
            return null;

        for (int i = 0; i < methods.size(); i++)
        {
            Method m = (Method) methods.get(i);
            boolean varArgs = isJdk15() && m.isVarArgs();

            if (parms.length != m.getParameterTypes().length && !varArgs)
                continue;

            Class[] mparms = m.getParameterTypes();
            boolean matched = true;
            for (int p = 0; p < mparms.length; p++)
            {
                if (varArgs && mparms[p].isArray()){
                    continue;
                }

                if (parms[p] == null)
                {
                    matched = false;
                    break;
                }

                if (parms[p] == mparms[p])
                    continue;

                if (mparms[p].isPrimitive()
                    && Character.TYPE != mparms[p] && Byte.TYPE != mparms[p]
                    && Number.class.isAssignableFrom(parms[p])
                    && OgnlRuntime.getPrimitiveWrapperClass(parms[p]) == mparms[p])
                {
                    continue;
                }

                matched = false;
                break;
            }

            if (matched)
                return m;
        }

        return null;
    }

    /**
     * Finds the best possible match for a method on the specified target class with a matching
     * name.
     *
     *

     *  The name matched will also try different combinations like is + name, has + name, get + name, etc..
     *

     *
     * @param target
     *          The class to find a matching method against.
     * @param name
     *          The name of the method.
     * @return The most likely matching {@link Method}, or null if none could be found.
     */
    public static Method getReadMethod(Class target, String name)
    {
        return getReadMethod(target, name, null);
    }

    public static Method getReadMethod(Class target, String name, Class[] argClasses)
    {
        try {
            if (name.indexOf('"') >= 0)
                name = name.replaceAll("\"", "");

            name = name.toLowerCase();

            Method[] methods = target.getMethods();

            // exact matches first
            ArrayList<Method> candidates = new ArrayList<Method>();

            for (int i = 0; i < methods.length; i++)
            {
                if (!isMethodCallable(methods[i]))
                    continue;

                if ((methods[i].getName().equalsIgnoreCase(name)
                     || methods[i].getName().toLowerCase().equals("get" + name)
                     || methods[i].getName().toLowerCase().equals("has" + name)
                     || methods[i].getName().toLowerCase().equals("is" + name))
                    && !methods[i].getName().startsWith("set"))
                {
                    candidates.add(methods[i]);
                }
            }
            if (!candidates.isEmpty()) {
                MatchingMethod mm = findBestMethod(candidates, target, name, argClasses);
                if (mm != null)
                    return mm.mMethod;
            }

            for (int i = 0; i < methods.length; i++)
            {
                if (!isMethodCallable(methods[i]))
                    continue;

                if (methods[i].getName().equalsIgnoreCase(name)
                    && !methods[i].getName().startsWith("set")
                    && !methods[i].getName().startsWith("get")
                    && !methods[i].getName().startsWith("is")
                    && !methods[i].getName().startsWith("has")
                    && methods[i].getReturnType() != Void.TYPE) {

                    Method m = methods[i];
                    if (!candidates.contains(m))
                        candidates.add(m);
                }
            }

            if (!candidates.isEmpty()) {
                MatchingMethod mm = findBestMethod(candidates, target, name, argClasses);
                if (mm != null)
                    return mm.mMethod;
            }

            // try one last time adding a get to beginning

            if (!name.startsWith("get")) {
                Method ret = OgnlRuntime.getReadMethod(target, "get" + name, argClasses);
                if (ret != null)
                    return ret;
            }

            if (!candidates.isEmpty()) {
                // we need to do conversions.
                // TODO we have to find out which conversions are possible!
                int reqArgCount = argClasses==null?0:argClasses.length;
                for (Method m : candidates) {
                    if (m.getParameterTypes().length == reqArgCount)
                        return m;
                }
            }

        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        return null;
    }

    public static Method getWriteMethod(Class target, String name)
    {
        return getWriteMethod(target, name, null);
    }

    public static Method getWriteMethod(Class target, String name, Class[] argClasses)
    {
        try {
            if (name.indexOf('"') >= 0)
                name = name.replaceAll("\"", "");

            BeanInfo info = Introspector.getBeanInfo(target);
            MethodDescriptor[] methods = info.getMethodDescriptors();

            ArrayList<Method> candidates = new ArrayList<Method>();

            for (int i = 0; i < methods.length; i++) {
                if (!isMethodCallable(methods[i].getMethod()))
                    continue;

                if ((methods[i].getName().equalsIgnoreCase(name)
                     || methods[i].getName().toLowerCase().equals(name.toLowerCase())
                     || methods[i].getName().toLowerCase().equals("set" + name.toLowerCase()))
                    && !methods[i].getName().startsWith("get")) {

                    candidates.add(methods[i].getMethod());
                }
            }

            if (!candidates.isEmpty()) {
                MatchingMethod mm = findBestMethod(candidates, target, name, argClasses);
                if (mm != null)
                    return mm.mMethod;
            }
            // try again on pure class

            Method[] cmethods = target.getClass().getMethods();
            for (int i = 0; i < cmethods.length; i++) {
                if (!isMethodCallable(cmethods[i]))
                    continue;

                if ((cmethods[i].getName().equalsIgnoreCase(name)
                     || cmethods[i].getName().toLowerCase().equals(name.toLowerCase())
                     || cmethods[i].getName().toLowerCase().equals("set" + name.toLowerCase()))
                    && !cmethods[i].getName().startsWith("get")) {

                    Method m = methods[i].getMethod();
                    if (!candidates.contains(m))
                        candidates.add(m);
                }
            }

            if (!candidates.isEmpty()) {
                MatchingMethod mm = findBestMethod(candidates, target, name, argClasses);
                if (mm != null)
                    return mm.mMethod;
            }
            // try one last time adding a set to beginning

            if (!name.startsWith("set")) {
                Method ret = OgnlRuntime.getReadMethod(target, "set" + name, argClasses);
                if (ret != null)
                    return ret;
            }

            if (!candidates.isEmpty()) {
                // we need to do conversions.
                // TODO we have to find out which conversions are possible!
                int reqArgCount = argClasses==null?0:argClasses.length;
                for (Method m : candidates) {
                    if (m.getParameterTypes().length == reqArgCount)
                        return m;
                }

                if ( argClasses == null && candidates.size() == 1 ) {
                    // this seems to be the TestCase TestOgnlRuntime.test_Complicated_Inheritance() - is this a real world use case?
                    return candidates.get(0);
                }
            }
        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        return null;
    }

    public static PropertyDescriptor getProperty(Class target, String name)
    {
        try {
            BeanInfo info = Introspector.getBeanInfo(target);

            PropertyDescriptor[] pds = info.getPropertyDescriptors();

            for (int i = 0; i < pds.length; i++) {

                if (pds[i].getName().equalsIgnoreCase(name)
                    || pds[i].getName().toLowerCase().equals(name.toLowerCase())
                    || pds[i].getName().toLowerCase().endsWith(name.toLowerCase()))
                    return pds[i];
            }

        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        return null;
    }

    public static boolean isBoolean(String expression)
    {
        if (expression == null)
            return false;

        if ("true".equals(expression) || "false".equals(expression)
            || "!true".equals(expression) || "!false".equals(expression)
            || "(true)".equals(expression)
            || "!(true)".equals(expression)
            || "(false)".equals(expression)
            || "!(false)".equals(expression)
            || expression.startsWith("ognl.OgnlOps"))
            return true;

        return false;
    }

    /**
     * Compares the {@link OgnlContext#getCurrentType()} and {@link OgnlContext#getPreviousType()} class types
     * on the stack to determine if a numeric expression should force object conversion.
     *

     *

     * Normally used in conjunction with the forceConversion parameter of
     * {@link OgnlRuntime#getChildSource(OgnlContext,Object,Node,boolean)}.
     *

     *
     * @param context The current context.
     * @return True, if the class types on the stack wouldn't be comparable in a pure numeric expression such as o1 >= o2.
     */
    public static boolean shouldConvertNumericTypes(OgnlContext context)
    {
        if (context.getCurrentType() == null || context.getPreviousType() == null)
            return true;

        if (context.getCurrentType() == context.getPreviousType()
            && context.getCurrentType().isPrimitive() && context.getPreviousType().isPrimitive())
            return false;

        return context.getCurrentType() != null && !context.getCurrentType().isArray()
               && context.getPreviousType() != null && !context.getPreviousType().isArray();
    }

    /**
     * Attempts to get the java source string represented by the specific child expression
     * via the {@link JavaSource#toGetSourceString(OgnlContext,Object)} interface method.
     *
     * @param context The ognl context to pass to the child.
     * @param target  The current object target to use.
     * @param child   The child expression.
     * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext,Object)} plus additional
     *         enclosures of {@link OgnlOps#convertValue(Object,Class,boolean)} for conversions.
     * @throws OgnlException Mandatory exception throwing catching.. (blehh)
     */
    public static String getChildSource(OgnlContext context, Object target, Node child)
            throws OgnlException
    {
        return getChildSource(context, target, child, false);
    }

    /**
     * Attempts to get the java source string represented by the specific child expression
     * via the {@link JavaSource#toGetSourceString(OgnlContext,Object)} interface method.
     *
     * @param context         The ognl context to pass to the child.
     * @param target          The current object target to use.
     * @param child           The child expression.
     * @param forceConversion If true, forces {@link OgnlOps#convertValue(Object,Class)} conversions on the objects.
     * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext,Object)} plus additional
     *         enclosures of {@link OgnlOps#convertValue(Object,Class,boolean)} for conversions.
     * @throws OgnlException Mandatory exception throwing catching.. (blehh)
     */
    public static String getChildSource(OgnlContext context, Object target, Node child, boolean forceConversion)
            throws OgnlException
    {
        String pre = (String) context.get("_currentChain");
        if (pre == null)
            pre = "";

        try
        {
            child.getValue(context, target);
        } catch (NullPointerException e)
        {
            // ignore
        } catch (ArithmeticException e)
        {
            context.setCurrentType(int.class);
            return "0";
        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        String source = null;

        try
        {
            source = child.toGetSourceString(context, target);
        }
        catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        // handle root / method expressions that may not have proper root java source access

        if (!ASTConst.class.isInstance(child)
            && (target == null || context.getRoot() != target))
        {
            source = pre + source;
        }

        if (context.getRoot() != null)
        {
            source = ExpressionCompiler.getRootExpression(child, context.getRoot(), context) + source;
            context.setCurrentAccessor(context.getRoot().getClass());
        }

        if (ASTChain.class.isInstance(child))
        {
            String cast = (String) context.remove(ExpressionCompiler.PRE_CAST);
            if (cast == null)
                cast = "";

            source = cast + source;
        }

        if (source == null || source.trim().length() < 1)
            source = "null";

        return source;
    }

    /*
     * The idea behind this class is to provide a very fast way to cache getter/setter methods indexed by their class
     * and property name.
     *
     * Instead of creating any kind of complex key object (or a String key by appending class name and property), this
     * class directly uses the Class clazz and the String propertyName as keys of two levels of ConcurrentHashMaps,
     * so that it takes advantage of the fact that these two classes are immutable and that their respective hashCode()
     * and equals() methods are extremely fast and optimized. These two aspects should improve Map access performance.
     *
     * Also, using these structure instead of any other kind of key on a single-level map should save a lot of memory
     * given no specialized cache objects (be them of a specific CacheKey class or mere Strings) ever have to be created
     * for simply accessing the cache in search for a getter/setter method.
     *
     */
    private static final class ClassPropertyMethodCache {

        // ConcurrentHashMaps do not allow null keys or values, so we will use one of this class's own methods as
        // a replacement for signaling when the true cached value is 'null'
        private static final Method NULL_REPLACEMENT;

        private final ConcurrentHashMap<Class<?>,ConcurrentHashMap<String,Method>> cache =
                new ConcurrentHashMap<Class<?>,ConcurrentHashMap<String,Method>>();

        static
        {
            try
            {
                NULL_REPLACEMENT =
                        ClassPropertyMethodCache.class.getDeclaredMethod("get", new Class[] {Class.class,String.class});
            } catch (NoSuchMethodException e)
            {
                throw new RuntimeException(e); // Will never happen, it's our own method, we know it exists
            }
        }

        ClassPropertyMethodCache()
        {
            super();
        }

        Method get(Class clazz, String propertyName)
        {
            ConcurrentHashMap<String,Method> methodsByPropertyName = this.cache.get(clazz);
            if (methodsByPropertyName == null)
            {
                methodsByPropertyName = new ConcurrentHashMap<String, Method>();
                this.cache.put(clazz, methodsByPropertyName);
            }
            Method method = methodsByPropertyName.get(propertyName);
            if (method == NULL_REPLACEMENT)
                return null;
            return method;
        }

        void put(Class clazz, String propertyName, Method method)
        {
            ConcurrentHashMap<String,Method> methodsByPropertyName = this.cache.get(clazz);
            if (methodsByPropertyName == null)
            {
                methodsByPropertyName = new ConcurrentHashMap<String, Method>();
                this.cache.put(clazz, methodsByPropertyName);
            }
            methodsByPropertyName.put(propertyName, (method == null? NULL_REPLACEMENT : method));
        }

        boolean containsKey(Class clazz, String propertyName)
        {
            ConcurrentHashMap<String,Method> methodsByPropertyName = this.cache.get(clazz);
            if (methodsByPropertyName == null)
                return false;

            return methodsByPropertyName.containsKey(propertyName);
        }

    }

}