Commits

Anonymous committed 48a8cc5

Fixes OGNL-126 and OGNL-80. Added more support for generics.

Comments (0)

Files changed (15)

src/java/ognl/ASTMethod.java

         Method m = null;
         
         try {
-            
+
             m = OgnlRuntime.getMethod(context, context.getCurrentType() != null ? context.getCurrentType() : target.getClass(), _methodName, _children, false);
             if (m == null)
                 m = OgnlRuntime.getReadMethod(target.getClass(), _methodName, _children != null ? _children.length : -1);
             {    
                 Class[] parms = m.getParameterTypes();
                 String prevCast = (String)context.remove(ExpressionCompiler.PRE_CAST);
-                
 /*
                 System.out.println("before children methodName is " + _methodName + " for target " + target + " target class: " + (target != null ? target.getClass() : null)
                            + " current type: " + context.getCurrentType() + " and previous type: " + context.getPreviousType());*/
             throw OgnlOps.castToRuntime(t);
         }
 
-        try {
-
+        try
+        {
             Object contextObj = getValueBody(context, target);
-            context.setCurrentObject(contextObj);
-            
-        } catch (Throwable t) {
-            // ignore 
+            context.setCurrentObject(contextObj);    
+        } catch (Throwable t)
+        {
+            throw OgnlOps.castToRuntime(t);
         }
 
         result += ")" + post;

src/java/ognl/ObjectPropertyAccessor.java

         OgnlContext ognlContext = (OgnlContext) context;
 
         try {
-            if (!OgnlRuntime.setMethodValue(ognlContext, target, name, value, true)) {
+            if (!OgnlRuntime.setMethodValue(ognlContext, target, name, value, true))
+            {
                 result = OgnlRuntime.setFieldValue(ognlContext, target, name, value) ? null : OgnlRuntime.NotFound;
             }
 
-            if (result == OgnlRuntime.NotFound) {
+            if (result == OgnlRuntime.NotFound)
+            {
                 Method m = OgnlRuntime.getWriteMethod(target.getClass(), name);
-                if (m != null) {
+                if (m != null)
+                {
                     result = m.invoke(target, new Object[] { value});
                 }
             }
-
         } catch (IntrospectionException ex) {
             throw new OgnlException(name, ex);
         } catch (OgnlException ex) {
         } catch (Exception ex) {
             throw new OgnlException(name, ex);
         }
+        
         return result;
     }
 

src/java/ognl/OgnlRuntime.java

 
 import ognl.enhance.ExpressionCompiler;
 import ognl.enhance.OgnlExpressionCompiler;
+import ognl.internal.ClassCache;
+import ognl.internal.ClassCacheImpl;
 
 import java.beans.*;
 import java.lang.reflect.*;
      */
     public static int INDEXED_PROPERTY_OBJECT = 2;
 
+    /**
+     * Constant string representation of null string.
+     */
     public static final String NULL_STRING = "" + null;
 
     /**
     private static final Map HEX_PADDING = new HashMap();
 
     private static final int HEX_LENGTH = 8;
+
     /**
      * Returned by <CODE>getUniqueDescriptor()</CODE> when the object is <CODE>null</CODE>.
      */
     private static boolean _jdk15 = false;
     private static boolean _jdkChecked = false;
 
-    static final ClassCache _methodAccessors = new ClassCache();
-    static final ClassCache _propertyAccessors = new ClassCache();
-    static final ClassCache _elementsAccessors = new ClassCache();
-    static final ClassCache _nullHandlers = new ClassCache();
+    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 ClassCache();
-    static final ClassCache _constructorCache = new ClassCache();
-    static final ClassCache _staticMethodCache = new ClassCache();
-    static final ClassCache _instanceMethodCache = new ClassCache();
-    static final ClassCache _invokePermissionCache = new ClassCache();
-    static final ClassCache _fieldCache = new ClassCache();
+    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 ClassCache(), new ClassCache()};
+    static final ClassCache[] _declaredMethods = new ClassCache[]{new ClassCacheImpl(), new ClassCacheImpl()};
 
     static final Map _primitiveTypes = new HashMap(101);
-    static final ClassCache _primitiveDefaults = new ClassCache();
+    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();
      */
     private static OgnlExpressionCompiler _compiler = new ExpressionCompiler();
 
-    /**
-     * This is a highly specialized map for storing values keyed by Class objects.
-     */
-    static class ClassCache {
-
-        /* this MUST be a power of 2 */
-        private static final int TABLE_SIZE = 512;
-
-        /* ...and now you see why. The table size is used as a mask for generating hashes */
-        private static final int TABLE_SIZE_MASK = TABLE_SIZE - 1;
-
-        private Entry[] table;
-
-        private ClassCacheInspector _classInspector;
-
-        private static class Entry {
-
-            protected Entry next;
-            protected Class key;
-            protected Object value;
-
-            public Entry(Class key, Object value)
-            {
-                super();
-                this.key = key;
-                this.value = value;
-            }
-        }
-
-        public ClassCache()
-        {
-            super();
-            this.table = new Entry[TABLE_SIZE];
-        }
-
-        public void setClassInspector(ClassCacheInspector inspector)
-        {
-            _classInspector = inspector;
-        }
-
-        public void clear()
-        {
-            for (int i=0; i < this.table.length; i++)
-            {
-                this.table[i] = null;
-            }
-        }
-
-        public int getSize()
-        {
-            int counter = 0;
-            for (int i=0; i < table.length; i++)
-            {
-                if (table[i] != null)
-                    counter++;
-            }
-
-            return counter;
-        }
-
-        public final Object get(Class key)
-        {
-            Object result = null;
-            int i = key.hashCode() & TABLE_SIZE_MASK;
-
-            for (Entry entry = table[i]; entry != null; entry = entry.next)
-            {
-                if (entry.key == key)
-                {
-                    result = entry.value;
-                    break;
-                }
-            }
-
-            return result;
-        }
-
-        public final Object put(Class key, Object value)
-        {
-            if (_classInspector != null && !_classInspector.shouldCache(key))
-                return value;
-
-            Object result = null;
-            int i = key.hashCode() & TABLE_SIZE_MASK;
-            Entry entry = table[i];
-
-            if (entry == null)
-            {
-                table[i] = new Entry(key, value);
-            } else {
-
-                if (entry.key == key)
-                {
-                    result = entry.value;
-                    entry.value = value;
-                } else {
-
-                    while (true)
-                    {
-                        if (entry.key == key)
-                        {
-                            /* replace value */
-                            result = entry.value;
-                            entry.value = value;
-                            break;
-                        } else {
-                            if (entry.next == null)
-                            {
-                                /* add value */
-                                entry.next = new Entry(key, value);
-                                break;
-                            }
-                        }
-                        entry = entry.next;
-                    }
-                }
-            }
-            return result;
-        }
-    }
-
     private static IdentityHashMap PRIMITIVE_WRAPPER_CLASSES = new IdentityHashMap();
 
     /**
         if (_jdkChecked)
             return _jdk15;
 
-        try {
+        try
+        {
             Class.forName("java.lang.annotation.Annotation");
             _jdk15 = true;
-        } catch (Exception e) {}
+        } catch (Exception e) { /* ignore */ }
 
         _jdkChecked = true;
 
 
     public static String getClassName(Object o, boolean fullyQualified)
     {
-        if (!(o instanceof Class)) {
+        if (!(o instanceof Class))
+        {
             o = o.getClass();
         }
+
         return getClassName((Class) o, fullyQualified);
     }
 
      */
     public static Class[] getParameterTypes(Method m)
     {
-        synchronized (_methodParameterTypesCache) {
+        synchronized (_methodParameterTypesCache)
+        {
             Class[] result;
 
-            if ((result = (Class[]) _methodParameterTypesCache.get(m)) == null) {
+            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)
+    {
+        if (type == null)
+        {
+            return getParameterTypes(m);
+        }
+
+        if (!isJdk15()
+            || type.getGenericSuperclass() == null
+            || !ParameterizedType.class.isInstance(type.getGenericSuperclass())
+            || m.getDeclaringClass().getTypeParameters() == null)
+        {
+            return getParameterTypes(m);
+        }
+
+        synchronized (_genericMethodParameterTypesCache)
+        {
+            Class[] types;
+
+            if ((types = (Class[]) _genericMethodParameterTypesCache.get(m)) != null)
+            {
+                return types;
+            }
+
+            ParameterizedType param = (ParameterizedType)type.getGenericSuperclass();
+            Type[] genTypes = m.getGenericParameterTypes();
+            TypeVariable[] declaredTypes = m.getDeclaringClass().getTypeParameters();
+            
+            types = new Class[genTypes.length];
+
+            typeSearch:
+            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 (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];
+            }
+
+            _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)
 
         synchronized(method) {
 
-            if (_securityManager != null) {
+            if (_securityManager != null)
+            {
                 try {
                     _securityManager.checkPermission(getPermission(method));
                 } catch (SecurityException ex) {
                     throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                 }
             }
-            if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
-                if (!(wasAccessible = ((AccessibleObject) method).isAccessible())) {
+
+            if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+            {
+                if (!(wasAccessible = ((AccessibleObject) method).isAccessible()))
+                {
                     ((AccessibleObject) method).setAccessible(true);
                 }
             }
 
             result = method.invoke(target, argsArray);
-            if (!wasAccessible) {
+
+            if (!wasAccessible)
+            {
                 ((AccessibleObject) method).setAccessible(false);
             }
         }
         Method result = null;
         TypeConverter converter = context.getTypeConverter();
 
-        if ((converter != null) && (methods != null)) {
-            for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) {
+        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 = getParameterTypes(m);
+                Class[] parameterTypes = findParameterTypes(target != null ? target.getClass() : null, m);//getParameterTypes(m);
 
-                if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs)) {
+                if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs))
+                {
                     result = m;
                 }
             }
      * 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 methodName,
-                                              String propertyName, List methods, Object[] args, Object[] actualArgs)
+    public static Method getAppropriateMethod(OgnlContext context, Object source, Object target, String propertyName,
+                                              List methods, Object[] args, Object[] actualArgs)
     {
         Method result = null;
         Class[] resultParameterTypes = null;
             for (int i = 0, icount = methods.size(); i < icount; i++)
             {
                 Method m = (Method) methods.get(i);
-                Class[] mParameterTypes = getParameterTypes(m);
+
+                Class typeClass = target != null ? target.getClass() : null;
+                if (typeClass == null && source != null && Class.class.isInstance(source))
+                {
+                    typeClass = (Class)source;
+                }
+                
+                Class[] mParameterTypes = findParameterTypes(typeClass, m);//getParameterTypes(m);
 
                 if (areArgsCompatible(args, mParameterTypes, m)
                     && ((result == null) || isMoreSpecific(mParameterTypes, resultParameterTypes)))
         Object[] actualArgs = _objectArrayPool.create(args.length);
 
         try {
-            Method method = getAppropriateMethod(context, source, target, methodName,
-                                                 propertyName, methods, args, actualArgs);
+            Method method = getAppropriateMethod(context, source, target, propertyName, methods, args, actualArgs);
 
             if ((method == null) || !isMethodAccessible(context, source, method, propertyName))
             {
                 // 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())
                         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();
                                     varArgsList.add(actualArgs[j]);
                                 }
                             }
-                            
+
                             varArgs = varArgsList.toArray();
                         } else
                         {
     }
 
     public static Object callStaticMethod(OgnlContext context, String className, String methodName, Object[] args)
-            throws OgnlException, MethodFailedException
+            throws OgnlException
     {
         try {
             Class targetClass = classForName(context, className);
         return result;
     }
 
-    public static final boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value)
-            throws OgnlException, IllegalAccessException, NoSuchMethodException, MethodFailedException,
-            IntrospectionException
+    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 final boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value,
-                                               boolean checkAccessAndExistence)
-            throws OgnlException, IllegalAccessException, NoSuchMethodException, MethodFailedException,
-            IntrospectionException
+    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)) {
+        if (checkAccessAndExistence)
+        {
+            if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName))
+            {
                 result = false;
             }
         }
-        if (result) {
-            if (m != null) {
+
+        if (result)
+        {
+            if (m != null)
+            {
                 Object[] args = _objectArrayPool.create(value);
 
-                try {
+                try
+                {
                     callAppropriateMethod(context, target, target, m.getName(), propertyName,
                                           Collections.nCopies(1, m), args);
-                } finally {
+                } finally
+                {
                     _objectArrayPool.recycle(args);
                 }
-            } else {
+            } else
+            {
                 result = false;
             }
         }
+
         return result;
     }
 
-    public static final List getConstructors(Class targetClass)
+    public static List getConstructors(Class targetClass)
     {
         List result;
 
             throws IntrospectionException, OgnlException
     {
         Method result = null;
-        PropertyDescriptor pd = getPropertyDescriptor(targetClass, propertyName);
+        PropertyDescriptor pd = null; //getPropertyDescriptor(targetClass, propertyName);
 
-        if (pd == null) {
-            List methods = getDeclaredMethods(targetClass, propertyName, false /*
-                                                                                 * find 'get'
-                                                                                 * methods
-                                                                                 */);
+        if (pd == null)
+        {
+            List methods = getDeclaredMethods(targetClass, propertyName, false /* find 'get' methods */);
 
-            if (methods != null) {
-                for (int i = 0, icount = methods.size(); i < icount; i++) {
+            if (methods != null)
+            {
+                for (int i = 0, icount = methods.size(); i < icount; i++)
+                {
                     Method m = (Method) methods.get(i);
-                    Class[] mParameterTypes = getParameterTypes(m);
+                    Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m);
 
-                    if (mParameterTypes.length == 0) {
+                    if (mParameterTypes.length == 0)
+                    {
                         result = m;
                         break;
                     }
                 }
             }
-        } else {
+        } else
+        {
             result = pd.getReadMethod();
         }
+
         return result;
     }
 
             throws IntrospectionException, OgnlException
     {
         Method result = null;
-        PropertyDescriptor pd = getPropertyDescriptor(targetClass, propertyName);
+        PropertyDescriptor pd = null; //getPropertyDescriptor(targetClass, propertyName);
 
-        if (pd == null) {
+        if (pd == null)
+        {
             List methods = getDeclaredMethods(targetClass, propertyName, true /* find 'set' methods */);
 
-            if (methods != null) {
-                for (int i = 0, icount = methods.size(); i < icount; i++) {
+            if (methods != null)
+            {
+                for (int i = 0, icount = methods.size(); i < icount; i++)
+                {
                     Method m = (Method) methods.get(i);
-                    Class[] mParameterTypes = getParameterTypes(m);
+                    Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m);
 
                     if (mParameterTypes.length == 1) {
                         result = m;
                     }
                 }
             }
-        } else {
+        } else
+        {
             result = pd.getWriteMethod();
         }
+
         return result;
     }
 
         Map result;
 
         synchronized (_propertyDescriptorCache) {
-            if ((result = (Map) _propertyDescriptorCache.get(targetClass)) == null) {
+            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++) {
+                for (int i = 0, icount = pda.length; i < icount; i++)
+                {
                     result.put(pda[i].getName(), pda[i]);
                 }
 
         {
             throw new OgnlException("No property accessor for " + getTargetClass(source).getName());
         }
-        
+
         return accessor.getProperty(context, source, name);
     }
 
         PropertyAccessor accessor;
 
         if (target == null) {
-            throw new OgnlException("target is null for setProperty(null, \"" + name + "\", " + value
-                                    + ")");
+            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());
+            throw new OgnlException("No property accessor for " + getTargetClass(target).getName());
         }
+
         accessor.setProperty(context, target, name, value);
     }
 
         {
             Method m = (Method) methods.get(i);
             boolean varArgs = isJdk15() && m.isVarArgs();
-            
+
             if (parms.length != m.getParameterTypes().length && !varArgs)
                 continue;
 
                 if (varArgs && mparms[p].isArray()){
                     continue;
                 }
-                
+
                 if (parms[p] == null)
                 {
                     matched = false;

src/java/ognl/internal/ClassCache.java

+package ognl.internal;
+
+import ognl.ClassCacheInspector;
+
+/**
+ * This is a highly specialized map for storing values keyed by Class objects.
+ */
+public interface ClassCache {
+
+    void setClassInspector(ClassCacheInspector inspector);
+
+    void clear();
+
+    int getSize();
+
+    Object get(Class key);
+
+    Object put(Class key, Object value);
+}

src/java/ognl/internal/ClassCacheImpl.java

+package ognl.internal;
+
+import ognl.ClassCacheInspector;
+
+import java.util.Arrays;
+
+/**
+ * Implementation of {@link ClassCache}.
+ */
+public class ClassCacheImpl implements ClassCache {
+
+    /* this MUST be a power of 2 */
+    private static final int TABLE_SIZE = 512;
+    /* ...and now you see why. The table size is used as a mask for generating hashes */
+    private static final int TABLE_SIZE_MASK = TABLE_SIZE - 1;
+
+    private Entry[] _table;
+    private ClassCacheInspector _classInspector;
+    private int _size = 0;
+
+    public ClassCacheImpl()
+    {
+        _table = new Entry[TABLE_SIZE];
+    }
+
+    public void setClassInspector(ClassCacheInspector inspector)
+    {
+        _classInspector = inspector;
+    }
+
+    public void clear()
+    {
+        for (int i=0; i < _table.length; i++)
+        {
+            _table[i] = null;
+        }
+
+        _size = 0;
+    }
+
+    public int getSize()
+    {
+        return _size;
+    }
+
+    public final Object get(Class key)
+    {
+        Object result = null;
+        int i = key.hashCode() & TABLE_SIZE_MASK;
+
+        for (Entry entry = _table[i]; entry != null; entry = entry.next)
+        {
+            if (entry.key == key)
+            {
+                result = entry.value;
+                break;
+            }
+        }
+
+        return result;
+    }
+
+    public final Object put(Class key, Object value)
+    {
+        if (_classInspector != null && !_classInspector.shouldCache(key))
+            return value;
+
+        Object result = null;
+        int i = key.hashCode() & TABLE_SIZE_MASK;
+        Entry entry = _table[i];
+
+        if (entry == null)
+        {
+            _table[i] = new Entry(key, value);
+            _size++;
+        } else
+        {
+            if (entry.key == key)
+            {
+                result = entry.value;
+                entry.value = value;
+            } else
+            {
+                while (true)
+                {
+                    if (entry.key == key)
+                    {
+                        /* replace value */
+                        result = entry.value;
+                        entry.value = value;
+                        break;
+                    } else
+                    {
+                        if (entry.next == null)
+                        {
+                            /* add value */
+                            entry.next = new Entry(key, value);
+                            break;
+                        }
+                    }
+                    entry = entry.next;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public String toString()
+    {
+        return "ClassCacheImpl[" +
+               "_table=" + (_table == null ? null : Arrays.asList(_table)) +
+               '\n' +
+               ", _classInspector=" + _classInspector +
+               '\n' +
+               ", _size=" + _size +
+               '\n' +
+               ']';
+    }
+}

src/java/ognl/internal/Entry.java

+package ognl.internal;
+
+/**
+ * Used by {@link ClassCacheImpl} to store entries in the cache.
+ */
+class Entry {
+
+    Entry next;
+    Class key;
+    Object value;
+
+    public Entry(Class key, Object value)
+    {
+        this.key = key;
+        this.value = value;
+    }
+
+    public String toString()
+    {
+        return "Entry[" +
+               "next=" + next +
+               '\n' +
+               ", key=" + key +
+               '\n' +
+               ", value=" + value +
+               '\n' +
+               ']';
+    }
+}

src/test/java/ognl/TestOgnlRuntime.java

 import junit.framework.TestCase;
 import org.ognl.test.objects.*;
 
+import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
  */
 public class TestOgnlRuntime extends TestCase {
 
+    
     public void test_Get_Super_Or_Interface_Class() throws Exception
     {
         ListSource list = new ListSourceImpl();
             return true;
         }
     }
+
+    public void test_Set_Generic_Parameter_Types()
+        throws Exception
+    {
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        Method m = OgnlRuntime.getSetMethod(context, GenericCracker.class, "param");
+        assertNotNull(m);
+
+        Class[] types = m.getParameterTypes();
+        assertEquals(1, types.length);
+        assertEquals(Integer.class, types[0]);
+    }
+
+    public void test_Get_Generic_Parameter_Types()
+        throws Exception
+    {
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        Method m = OgnlRuntime.getGetMethod(context, GenericCracker.class, "param");
+        assertNotNull(m);
+
+        assertEquals(Integer.class, m.getReturnType());
+    }
+
+    public void test_Find_Parameter_Types()
+            throws Exception
+    {
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        Method m = OgnlRuntime.getSetMethod(context, GameGeneric.class, "ids");
+        assertNotNull(m);
+
+        Class[] types = OgnlRuntime.findParameterTypes(GameGeneric.class, m);
+        assertEquals(1, types.length);
+        assertEquals(new Long[0].getClass(), types[0]);
+    }
+
+    public void test_Find_Parameter_Types_Superclass()
+            throws Exception
+    {
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        Method m = OgnlRuntime.getSetMethod(context, BaseGeneric.class, "ids");
+        assertNotNull(m);
+
+        Class[] types = OgnlRuntime.findParameterTypes(BaseGeneric.class, m);
+        assertEquals(1, types.length);
+        assertEquals(new Serializable[0].getClass(), types[0]);
+    }
 }

src/test/java/org/ognl/test/ASTMethodTest.java

         
         assertEquals(p.toGetSourceString(context, root.getMap()), ".get(\"value\")");
         assertEquals(context.getCurrentType(), Object.class);
-        assertEquals(context.getCurrentObject(), root.getMap().get("value"));
+        assertEquals(root.getMap().get("value"), context.getCurrentObject());
         assert Map.class.isAssignableFrom(context.getCurrentAccessor());
         assert Map.class.isAssignableFrom(context.getPreviousType());
         assert context.getPreviousAccessor() == null;

src/test/java/org/ognl/test/ASTPropertyTest.java

 
 import junit.framework.TestCase;
 import ognl.*;
-import org.ognl.test.objects.Root;
+import static org.ognl.test.OgnlTestCase.isEqual;
+import org.ognl.test.objects.*;
 
 import java.util.List;
 import java.util.Map;
         SimpleNode node = (SimpleNode) Ognl.parseExpression("tab.searchCriteriaSelections[index1][index2]");
         node.setValue(context, root, Boolean.FALSE);
     }
+
+    public void test_Set_Generic_Property() throws Exception
+    {
+        GenericRoot root = new GenericRoot();
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        context.setRoot(root);
+        context.setCurrentObject(root);
+
+        SimpleNode node = (SimpleNode) Ognl.parseExpression("cracker.param");
+        node.setValue(context, root, "0");
+
+        assertEquals( new Integer(0), root.getCracker().getParam());
+
+        node.setValue(context, root, "10");
+
+        assertEquals(new Integer(10), root.getCracker().getParam());
+    }
+
+    public void test_Get_Generic_Property() throws Exception
+    {
+        GenericRoot root = new GenericRoot();
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        context.setRoot(root);
+        context.setCurrentObject(root);
+
+        SimpleNode node = (SimpleNode) Ognl.parseExpression("cracker.param");
+        node.setValue(context, root, "0");
+
+        assertEquals(new Integer(0), node.getValue(context, root));
+
+        node.setValue(context, root, "10");
+
+        assertEquals(new Integer(10), node.getValue(context, root));
+    }
+
+    public void test_Set_Get_Multiple_Generic_Types_Property() throws Exception
+    {
+        BaseGeneric<GameGenericObject, Long> root = new GameGeneric();
+        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
+
+        context.setRoot(root);
+        context.setCurrentObject(root);
+
+        SimpleNode node = (SimpleNode) Ognl.parseExpression("ids");
+        node.setValue(context, root, new String[] {"0", "20", "43"});
+
+        isEqual(new Long[] {new Long(0), new Long(20), new Long(43)}, root.getIds());
+        isEqual(node.getValue(context, root), root.getIds());
+    }
 }

src/test/java/org/ognl/test/GenericsTest.java

 package org.ognl.test;
 
+import junit.framework.TestSuite;
 import org.ognl.test.objects.BaseGeneric;
 import org.ognl.test.objects.GameGeneric;
+import org.ognl.test.objects.GameGenericObject;
+import org.ognl.test.objects.GenericRoot;
 
 /**
  * Tests java >= 1.5 generics support in ognl.
  */
-public class GenericsTest 
+public class GenericsTest extends OgnlTestCase
 {
-    private static BaseGeneric ROOT = new GameGeneric();
+    static GenericRoot ROOT = new GenericRoot();
+    static BaseGeneric<GameGenericObject, Long> GENERIC = new GameGeneric();
 
-    private static Object[][] TESTS = {
-            { ROOT, "ids", new Long[] { 10l, 20l}},
+    static Object[][] TESTS = {
+            { ROOT, "cracker.param", null, new Integer(2), new Integer(2)},
+            { GENERIC, "ids", null, new Long[] {1l, 101l}, new Long[] {1l, 101l}},
     };
+
+    public static TestSuite suite()
+    {
+        TestSuite result = new TestSuite();
+
+        for(int i = 0; i < TESTS.length; i++)
+        {
+            if (TESTS[i].length == 5)
+            {
+                result.addTest(new GenericsTest((String) TESTS[i][1] + " (" + TESTS[i][2] + ")", TESTS[i][0], (String) TESTS[i][1],
+                                                TESTS[i][2], TESTS[i][3], TESTS[i][4]));
+            }
+        }
+
+        return result;
+    }
+
+    public GenericsTest(String name, Object root, String expressionString,
+                        Object expectedResult, Object setValue, Object expectedAfterSetResult)
+    {
+        super(name, root, expressionString, expectedResult, setValue, expectedAfterSetResult);
+    }
 }

src/test/java/org/ognl/test/enhance/TestExpressionCompiler.java

         expression = Ognl.compileExpression(_context, this, "object[propertyKey]");
         assertEquals("propertyValue", expression.getAccessor().get(_context, this));
     }
+
+    public void test_Set_Generic_Property() throws Exception
+    {
+        _context.clear();
+        
+        GenericRoot root = new GenericRoot();
+
+        Node node = Ognl.compileExpression(_context, root, "cracker.param");
+        assertEquals(null, node.getAccessor().get(_context, root));
+
+        node.getAccessor().set(_context, root, new Integer(0));
+        assertEquals(new Integer(0), node.getAccessor().get(_context, root));
+
+        node.getAccessor().set(_context, root, new Integer(12));
+        assertEquals(new Integer(12), node.getAccessor().get(_context, root));
+    }
 }

src/test/java/org/ognl/test/objects/BaseGeneric.java

         this.ids = ids;
     }
 
+    public I[] getIds()
+    {
+        return this.ids;
+    }
+
     public String getMessage()
     {
         return "Message";

src/test/java/org/ognl/test/objects/Cracker.java

+package org.ognl.test.objects;
+
+import java.io.Serializable;
+
+/**
+ * Generic test object.
+ */
+public interface Cracker<T extends Serializable>{
+
+    T getParam();
+    
+    void setParam(T param);
+}

src/test/java/org/ognl/test/objects/GenericCracker.java

+package org.ognl.test.objects;
+
+/**
+ *
+ */
+public class GenericCracker implements Cracker<Integer> {
+
+    Integer _param;
+
+    public Integer getParam()
+    {
+        return _param;
+    }
+
+    public void setParam(Integer param)
+    {
+        _param = param;
+    }
+}

src/test/java/org/ognl/test/objects/GenericRoot.java

+package org.ognl.test.objects;
+
+/**
+ *
+ */
+public class GenericRoot {
+
+    Root _root = new Root();
+    GenericCracker _cracker = new GenericCracker();
+
+    public Root getRoot()
+    {
+        return _root;
+    }
+
+    public void setRoot(Root root)
+    {
+        _root = root;
+    }
+
+    public GenericCracker getCracker()
+    {
+        return _cracker;
+    }
+
+    public void setCracker(GenericCracker cracker)
+    {
+        _cracker = cracker;
+    }
+}