Author: henrib
Date: Wed Oct 19 16:43:58 2011
New Revision: 1186317

URL: http://svn.apache.org/viewvc?rev=1186317&view=rev
Log:
JEXL-119:
* Exposed methods from internal/introspection to ease solving method * 
parameters matching;
* Modified UberspectImpl to search for "pseudo" indexed property patterns;
* Fixed Interpreter to better report property vs variable error;
* Added specific test;
* Updated changes.xml

Modified:
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
    commons/proper/jexl/trunk/src/site/xdoc/changes.xml
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
 Wed Oct 19 16:43:58 2011
@@ -1269,9 +1269,9 @@ public class Interpreter implements Pars
                      || (numChildren == 1
                          && node.jjtGetChild(0) instanceof ASTIdentifier
                          && ((ASTIdentifier) 
node.jjtGetChild(0)).getRegister() >= 0))) {
-                JexlException xjexl = propertyName != null?
-                                      new JexlException.Property(node, 
propertyName):
-                                      new JexlException.Variable(node, 
variableName.toString());
+                JexlException xjexl = propertyName != null
+                                      ? new JexlException.Property(node, 
propertyName)
+                                      : new JexlException.Variable(node, 
variableName.toString());
                 return unknownVariable(xjexl);
             }
         }
@@ -1482,7 +1482,6 @@ public class Interpreter implements Pars
                 }
             }
         }
-
         return null;
     }
 

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/Introspector.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
 Wed Oct 19 16:43:58 2011
@@ -104,7 +104,15 @@ public class Introspector {
         base().setLoader(loader);
     }
 
-
+    /**
+     * Gets a class by name through this introspector class loader.
+     * @param className the class name
+     * @return the class instance or null if it could not be found
+     */
+    public Class<?> getClassByName(String className) {
+        return base().getClassByName(className);
+    }
+    
     /**
      * Gets the field named by <code>key</code> for the class <code>c</code>.
      *
@@ -112,7 +120,7 @@ public class Introspector {
      * @param key   Name of the field being searched for
      * @return a {@link java.lang.reflect.Field} or null if it does not exist 
or is not accessible
      * */
-    protected final Field getField(Class<?> c, String key) {
+    public final Field getField(Class<?> c, String key) {
         return base().getField(c, key);
     }
 
@@ -137,7 +145,7 @@ public class Introspector {
      * @return a {@link java.lang.reflect.Method}
      *  or null if no unambiguous method could be found through introspection.
      */
-    protected final Method getMethod(Class<?> c, String name, Object[] params) 
{
+    public final Method getMethod(Class<?> c, String name, Object[] params) {
         return base().getMethod(c, new MethodKey(name, params));
     }
 
@@ -150,7 +158,7 @@ public class Introspector {
      * @return a {@link java.lang.reflect.Method}
      *  or null if no unambiguous method could be found through introspection.
      */
-    protected final Method getMethod(Class<?> c, MethodKey key) {
+    public final Method getMethod(Class<?> c, MethodKey key) {
         return base().getMethod(c, key);
     }
 
@@ -163,6 +171,16 @@ public class Introspector {
     public final String[] getMethodNames(Class<?> c) {
         return base().getMethodNames(c);
     }
+            
+    /**
+     * Gets all the methods with a given name from this map.
+     * @param c the class
+     * @param methodName the seeked methods name
+     * @return the array of methods
+     */
+    public final Method[] getMethods(Class<?> c, final String methodName) {
+        return base().getMethods(c, methodName);
+    }
 
     /**
      * Returns a general constructor.
@@ -213,9 +231,9 @@ public class Introspector {
             if (executor.isAlive()) {
                 return executor;
             }
-        }
+        //}
         // look for boolean isFoo()
-        if (property != null) {
+        //if (property != null) {
             executor = new BooleanGetExecutor(this, claz, property);
             if (executor.isAlive()) {
                 return executor;
@@ -240,6 +258,11 @@ public class Introspector {
         if (executor.isAlive()) {
             return executor;
         }
+        // if that didn't work, look for set("foo")
+        executor = new DuckGetExecutor(this, claz, property);
+        if (executor.isAlive()) {
+            return executor;
+        }
         return null;
     }
 
@@ -275,6 +298,11 @@ public class Introspector {
                 return executor;
             }
         }
+        // if that didn't work, look for set(foo)
+        executor = new DuckSetExecutor(this, claz, identifier, arg);
+        if (executor.isAlive()) {
+            return executor;
+        }
         // if that didn't work, look for set("foo")
         executor = new DuckSetExecutor(this, claz, property, arg);
         if (executor.isAlive()) {

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
 Wed Oct 19 16:43:58 2011
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.logging.Log;
@@ -78,11 +79,11 @@ final class ClassMap {
      * @param clazz the class to introspect
      * @return the map of fields (may be the empty map, can not be null)
      */
-    private static Map<String,Field> createFieldCache(Class<?> clazz) {
+    private static Map<String, Field> createFieldCache(Class<?> clazz) {
         Field[] fields = clazz.getFields();
         if (fields.length > 0) {
             Map<String, Field> cache = new HashMap<String, Field>();
-            for(Field field : fields) {
+            for (Field field : fields) {
                 cache.put(field.getName(), field);
             }
             return cache;
@@ -91,7 +92,6 @@ final class ClassMap {
         }
     }
 
-
     /**
      * Gets the methods names cached by this map.
      * @return the array of method names
@@ -101,6 +101,15 @@ final class ClassMap {
     }
 
     /**
+     * Gets all the methods with a given name from this map.
+     * @param methodName the seeked methods name
+     * @return the array of methods
+     */
+    Method[] get(final String methodName) {
+        return methodCache.get(methodName);
+    }
+
+    /**
      * Find a Method using the method name and parameter objects.
      *
      * @param key the method key
@@ -135,7 +144,7 @@ final class ClassMap {
         //
         // Ah, the miracles of Java for(;;) ...
         MethodCache cache = new MethodCache();
-        for (;classToReflect != null; classToReflect = 
classToReflect.getSuperclass()) {
+        for (; classToReflect != null; classToReflect = 
classToReflect.getSuperclass()) {
             if (Modifier.isPublic(classToReflect.getModifiers())) {
                 populateMethodCacheWith(cache, classToReflect, log);
             }
@@ -219,6 +228,7 @@ final class ClassMap {
         private static final int PRIMITIVE_SIZE = 13;
         /** The primitive type to class conversion map. */
         private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPES;
+
         static {
             PRIMITIVE_TYPES = new HashMap<Class<?>, Class<?>>(PRIMITIVE_SIZE);
             PRIMITIVE_TYPES.put(Boolean.TYPE, Boolean.class);
@@ -332,5 +342,21 @@ final class ClassMap {
                 return methodMap.names();
             }
         }
+
+        /**
+         * Gets all the methods with a given name from this map.
+         * @param methodName the seeked methods name
+         * @return the array of methods (null or non-empty)
+         */
+        Method[] get(final String methodName) {
+            synchronized (methodMap) {
+                List<Method> lm = methodMap.get(methodName);
+                if (lm != null && !lm.isEmpty()) {
+                    return lm.toArray(new Method[lm.size()]);
+                } else {
+                    return null;
+                }
+            }
+        }
     }
 }
\ No newline at end of file

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
 Wed Oct 19 16:43:58 2011
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.commons.jexl2.internal.introspection;
 
 import java.lang.reflect.Method;
@@ -77,7 +76,7 @@ public class IntrospectorBase {
         this.rlog = log;
         loader = getClass().getClassLoader();
     }
-    
+
     /**
      * Gets a class by name through this introspector class loader.
      * @param className the class name
@@ -86,7 +85,7 @@ public class IntrospectorBase {
     public Class<?> getClassByName(String className) {
         try {
             return Class.forName(className, false, loader);
-        } catch(ClassNotFoundException xignore) {
+        } catch (ClassNotFoundException xignore) {
             return null;
         }
     }
@@ -107,14 +106,13 @@ public class IntrospectorBase {
             // whoops.  Ambiguous.  Make a nice log message and return null...
             if (rlog != null && rlog.isInfoEnabled()) {
                 rlog.info("ambiguous method invocation: "
-                           + c.getName() + "."
-                           + key.debugString(), xambiguous);
+                        + c.getName() + "."
+                        + key.debugString(), xambiguous);
             }
             return null;
         }
     }
 
-
     /**
      * Gets the field named by <code>key</code> for the class <code>c</code>.
      *
@@ -154,6 +152,20 @@ public class IntrospectorBase {
     }
 
     /**
+     * Gets the array of accessible method known for a given class.
+     * @param c the class
+     * @param methodName the method name
+     * @return the array of methods (null or not empty)
+     */
+    public Method[] getMethods(Class<?> c, String methodName) {
+        if (c == null) {
+            return null;
+        }
+        ClassMap classMap = getMap(c);
+        return classMap.get(methodName);
+    }
+
+    /**
      * A Constructor get cache-miss.
      */
     private static class CacheMiss {
@@ -161,9 +173,10 @@ public class IntrospectorBase {
         @SuppressWarnings("unused")
         public CacheMiss() {}
     }
+    
     /** The cache-miss marker for the constructors map. */
     private static final Constructor<?> CTOR_MISS = 
CacheMiss.class.getConstructors()[0];
-    
+
     /**
      * Sets the class loader used to solve constructors.
      * <p>Also cleans the constructors and methods caches.</p>
@@ -176,9 +189,9 @@ public class IntrospectorBase {
         }
         if (!cloader.equals(loader)) {
             // clean up constructor and class maps
-            synchronized(constructorsMap) {
+            synchronized (constructorsMap) {
                 Iterator<Map.Entry<MethodKey, Constructor<?>>> entries = 
constructorsMap.entrySet().iterator();
-                while(entries.hasNext()) {
+                while (entries.hasNext()) {
                     Map.Entry<MethodKey, Constructor<?>> entry = 
entries.next();
                     Class<?> clazz = entry.getValue().getDeclaringClass();
                     if (isLoadedBy(previous, clazz)) {
@@ -191,7 +204,7 @@ public class IntrospectorBase {
             // clean up method maps
             synchronized (classMethodMaps) {
                 Iterator<Map.Entry<Class<?>, ClassMap>> entries = 
classMethodMaps.entrySet().iterator();
-                while(entries.hasNext()) {
+                while (entries.hasNext()) {
                     Map.Entry<Class<?>, ClassMap> entry = entries.next();
                     Class<?> clazz = entry.getKey();
                     if (isLoadedBy(previous, clazz)) {
@@ -212,7 +225,7 @@ public class IntrospectorBase {
     private static boolean isLoadedBy(ClassLoader loader, Class<?> clazz) {
         if (loader != null) {
             ClassLoader cloader = clazz.getClassLoader();
-            while(cloader != null) {
+            while (cloader != null) {
                 if (cloader.equals(loader)) {
                     return true;
                 } else {
@@ -233,7 +246,7 @@ public class IntrospectorBase {
     public Constructor<?> getConstructor(final MethodKey key) {
         return getConstructor(null, key);
     }
-    
+
     /**
      * Gets the constructor defined by the <code>MethodKey</code>.
      * @param c the class we want to instantiate
@@ -243,7 +256,7 @@ public class IntrospectorBase {
      */
     public Constructor<?> getConstructor(final Class<?> c, final MethodKey 
key) {
         Constructor<?> ctor = null;
-        synchronized(constructorsMap) {
+        synchronized (constructorsMap) {
             ctor = constructorsMap.get(key);
             // that's a clear miss
             if (CTOR_MISS.equals(ctor)) {
@@ -266,7 +279,7 @@ public class IntrospectorBase {
                         constructibleClasses.put(cname, clazz);
                     }
                     List<Constructor<?>> l = new LinkedList<Constructor<?>>();
-                    for(Constructor<?> ictor : clazz.getConstructors()) {
+                    for (Constructor<?> ictor : clazz.getConstructors()) {
                         l.add(ictor);
                     }
                     // try to find one
@@ -305,7 +318,7 @@ public class IntrospectorBase {
         synchronized (classMethodMaps) {
             ClassMap classMap = classMethodMaps.get(c);
             if (classMap == null) {
-                classMap = new ClassMap(c,rlog);
+                classMap = new ClassMap(c, rlog);
                 classMethodMaps.put(c, classMap);
             }
             return classMap;

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
 Wed Oct 19 16:43:58 2011
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 package org.apache.commons.jexl2.internal.introspection;
+
 import java.util.List;
 import java.util.LinkedList;
 import java.util.Iterator;
@@ -79,7 +80,7 @@ public final class MethodKey {
         }
         this.hashCode = hash;
     }
-
+    
     /**
      * Creates a key from a method.
      * @param aMethod the method to generate the key from.
@@ -191,7 +192,7 @@ public final class MethodKey {
     public Constructor<?> getMostSpecificConstructor(List<Constructor<?>> 
methods) {
         return CONSTRUCTORS.getMostSpecific(methods, params);
     }
-
+    
     /**
      * Determines whether a type represented by a class object is
      * convertible to another type represented by a class object using a
@@ -311,23 +312,23 @@ public final class MethodKey {
                 return true;
             }
             if (formal == Integer.TYPE
-                && (actual == Short.TYPE || actual == Byte.TYPE)) {
+                    && (actual == Short.TYPE || actual == Byte.TYPE)) {
                 return true;
             }
             if (formal == Long.TYPE
-                && (actual == Integer.TYPE || actual == Short.TYPE
-                || actual == Byte.TYPE)) {
+                    && (actual == Integer.TYPE || actual == Short.TYPE
+                    || actual == Byte.TYPE)) {
                 return true;
             }
             if (formal == Float.TYPE
-                && (actual == Long.TYPE || actual == Integer.TYPE
-                || actual == Short.TYPE || actual == Byte.TYPE)) {
+                    && (actual == Long.TYPE || actual == Integer.TYPE
+                    || actual == Short.TYPE || actual == Byte.TYPE)) {
                 return true;
             }
             if (formal == Double.TYPE
-                && (actual == Float.TYPE || actual == Long.TYPE
-                || actual == Integer.TYPE || actual == Short.TYPE
-                || actual == Byte.TYPE)) {
+                    && (actual == Float.TYPE || actual == Long.TYPE
+                    || actual == Integer.TYPE || actual == Short.TYPE
+                    || actual == Byte.TYPE)) {
                 return true;
             }
         }
@@ -342,7 +343,7 @@ public final class MethodKey {
         }
         return false;
     }
-
+    
     /**
      * whether a method/ctor is more specific than a previously compared one.
      */
@@ -367,12 +368,12 @@ public final class MethodKey {
          */
         private static final long serialVersionUID = -2314636505414551664L;
     }
-        
+
     /**
      * Utility for parameters matching.
      * @param <T> Method or Constructor
      */
-     private abstract static class Parameters<T> {
+    private abstract static class Parameters<T> {
         /**
          * Extract the parameter types from its applicable argument.
          * @param app a method or constructor
@@ -497,7 +498,7 @@ public final class MethodKey {
 
             // attempt to choose by picking the one with the greater number of 
primitives or latest primitive parameter
             int primDiff = 0;
-            for(int c = 0; c < c1.length; ++c) {
+            for (int c = 0; c < c1.length; ++c) {
                 if (c1[c].isPrimitive()) {
                     primDiff += 1 << c;
                 }
@@ -553,7 +554,7 @@ public final class MethodKey {
             // there's just one more methodArg than class arg
             // and the last methodArg is an array, then treat it as a vararg
             if (methodArgs.length == classes.length
-                || methodArgs.length == classes.length + 1 && 
methodArgs[methodArgs.length - 1].isArray()) {
+                    || methodArgs.length == classes.length + 1 && 
methodArgs[methodArgs.length - 1].isArray()) {
                 // this will properly match when the last methodArg
                 // is an array/varargs and the last class is the type of array
                 // (e.g. String when the method is expecting String...)
@@ -610,7 +611,7 @@ public final class MethodKey {
         private boolean isConvertible(Class<?> formal, Class<?> actual,
                 boolean possibleVarArg) {
             // if we see Void.class, the argument was null
-            return isInvocationConvertible(formal, actual.equals(Void.class)? 
null : actual, possibleVarArg);
+            return isInvocationConvertible(formal, actual.equals(Void.class) ? 
null : actual, possibleVarArg);
         }
 
         /**
@@ -625,11 +626,10 @@ public final class MethodKey {
         private boolean isStrictConvertible(Class<?> formal, Class<?> actual,
                 boolean possibleVarArg) {
             // if we see Void.class, the argument was null
-            return isStrictInvocationConvertible(formal, 
actual.equals(Void.class)? null : actual, possibleVarArg);
+            return isStrictInvocationConvertible(formal, 
actual.equals(Void.class) ? null : actual, possibleVarArg);
         }
-
     }
-
+    
     /**
      * The parameter matching service for methods.
      */
@@ -639,8 +639,7 @@ public final class MethodKey {
             return app.getParameterTypes();
         }
     };
-
-
+    
     /**
      * The parameter matching service for constructors.
      */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
 Wed Oct 19 16:43:58 2011
@@ -22,6 +22,8 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.InvocationTargetException;
 
+import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.Map;
@@ -96,8 +98,60 @@ public class UberspectImpl extends Intro
         }
         return null;
     }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public JexlMethod getMethod(Object obj, String method, Object[] args, 
JexlInfo info) {
+        return getMethodExecutor(obj, method, args);
+    }
 
     /**
+     * {@inheritDoc}
+     */
+    public JexlMethod getConstructor(Object ctorHandle, Object[] args, 
JexlInfo info) {
+        final Constructor<?> ctor = getConstructor(ctorHandle, args);
+        if (ctor != null) {
+            return new ConstructorMethod(ctor);
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public JexlPropertyGet getPropertyGet(Object obj, Object identifier, 
JexlInfo info) {
+        JexlPropertyGet get = getGetExecutor(obj, identifier);
+        if (get == null && obj != null && identifier != null) {
+            get = getIndexedGet(obj, identifier.toString());
+            if (get == null) {
+                Field field = getField(obj, identifier.toString(), info);
+                if (field != null) {
+                    return new FieldPropertyGet(field);
+                }
+            }
+        }
+        return get;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public JexlPropertySet getPropertySet(final Object obj, final Object 
identifier, Object arg, JexlInfo info) {
+        JexlPropertySet set = getSetExecutor(obj, identifier, arg);
+        if (set == null && obj != null && identifier != null) {
+            Field field = getField(obj, identifier.toString(), info);
+            if (field != null
+                    && !Modifier.isFinal(field.getModifiers())
+                    && (arg == null || 
MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) {
+                return new FieldPropertySet(field);
+            }
+        }
+        return set;
+    }
+    
+    /**
      * Returns a class field.
      * @param obj the object
      * @param name the field name
@@ -109,83 +163,275 @@ public class UberspectImpl extends Intro
         return getField(clazz, name);
     }
 
+
     /**
-     * {@inheritDoc}
-     */
-    public JexlMethod getConstructor(Object ctorHandle, Object[] args, 
JexlInfo info) {
-        final Constructor<?> ctor = getConstructor(ctorHandle, args);
-        if (ctor != null) {
-            JexlMethod jctor = new JexlMethod() {
-                public Object invoke(Object obj, Object[] params) throws 
Exception {
-                    Class<?> clazz = null;
-                    if (obj instanceof Class<?>) {
-                        clazz = (Class<?>) obj;
-                    } else if (obj != null) {
-                        clazz = base().getClassByName(obj.toString());
-                    } else {
-                        clazz = ctor.getDeclaringClass();
-                    }
-                    if (clazz.equals(ctor.getDeclaringClass())) {
-                        return ctor.newInstance(params);
-                    } else {
-                        return null;
-                    }
-                }
+     * Attempts to find an indexed-property getter in an object.
+     * The code attempts to find the list of methods getXXX() and setXXX().
+     * Note that this is not equivalent to the strict bean definition of 
indexed properties; the type of the key
+     * is not necessarily an int and the set/get arrays are not resolved.
+     * @param object the object
+     * @param name the container name
+     * @return a JexlPropertyGet is successfull, null otherwise
+     */
+    protected JexlPropertyGet getIndexedGet(Object object, String name) {
+        String base = name.substring(0, 1).toUpperCase() + name.substring(1);
+        final String container = name;
+        final Class<?> clazz = object.getClass();
+        final Method[] getters = getMethods(object.getClass(), "get" + base);
+        final Method[] setters = getMethods(object.getClass(), "set" + base);
+        if (getters != null) {
+            return new IndexedType(container, clazz, getters, setters);
+        } else {
+            return null;
+        }
+    }
 
-                public Object tryInvoke(String name, Object obj, Object[] 
params) {
-                    Class<?> clazz = null;
-                    if (obj instanceof Class<?>) {
-                        clazz = (Class<?>) obj;
-                    } else if (obj != null) {
-                        clazz = base().getClassByName(obj.toString());
-                    } else {
-                        clazz = ctor.getDeclaringClass();
-                    }
-                    if (clazz.equals(ctor.getDeclaringClass())
-                        && (name == null || name.equals(clazz.getName()))) {
-                        try {
-                            return ctor.newInstance(params);
-                        } catch (InstantiationException xinstance) {
-                            return TRY_FAILED;
-                        } catch (IllegalAccessException xaccess) {
-                            return TRY_FAILED;
-                        } catch (IllegalArgumentException xargument) {
-                            return TRY_FAILED;
-                        } catch (InvocationTargetException xinvoke) {
-                            return TRY_FAILED;
-                        }
-                    }
-                    return TRY_FAILED;
-                }
+    /**
+     * Abstract an indexed property container.
+     * This stores the container name and owning class as well as the list of 
available getter and setter methods.
+     * It implements JexlPropertyGet since such a container can only be 
accessed from its owning instance (not set).
+     */
+    private static final class IndexedType implements JexlPropertyGet {
+        /** The container name. */
+        private final String container;
+        /** The owning class. */
+        private final Class<?> clazz;
+        /** The array of getter methods. */
+        private final Method[] getters;
+        /** The array of setter methods. */
+        private final Method[] setters;
+
+        /**
+         * Creates a new indexed type.
+         * @param name the container name
+         * @param c the owning class
+         * @param gets the array of getter methods
+         * @param sets the array of setter methods
+         */
+        IndexedType(String name, Class<?> c, Method[] gets, Method[] sets) {
+            this.container = name;
+            this.clazz = c;
+            this.getters = gets;
+            this.setters = sets;
+        }
 
-                public boolean tryFailed(Object rval) {
-                    return rval == TRY_FAILED;
-                }
+        /**
+         * {@inheritDoc}
+         */
+        public Object invoke(Object obj) throws Exception {
+            if (clazz.equals(obj.getClass())) {
+                return new IndexedContainer(this, obj);
+            } else {
+                return null;
+            }
+        }
 
-                public boolean isCacheable() {
-                    return true;
+        /**
+         * {@inheritDoc}
+         */
+        public Object tryInvoke(Object obj, Object key) {
+            if (clazz.equals(obj.getClass()) && 
container.equals(key.toString())) {
+                return new IndexedContainer(this, obj);
+            } else {
+                return TRY_FAILED;
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean tryFailed(Object rval) {
+            return rval == TRY_FAILED;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isCacheable() {
+            return true;
+        }
+
+        /**
+         * Gets the value of a property from a container.
+         * @param object the instance owning the container
+         * @param key the property key
+         * @return the property value
+         * @throws Exception if the property can not be resolved
+         */
+        private Object invokeGet(Object object, Object key) throws Exception {
+            if (getters != null) {
+                final Object[] args = {key};
+                final Method jm;
+                if (getters.length == 1) {
+                    jm = getters[0];
+                } else {
+                    jm = new MethodKey(getters[0].getName(), 
args).getMostSpecificMethod(Arrays.asList(getters));
                 }
+                if (jm != null) {
+                    return jm.invoke(object, args);
+                }
+            }
+            throw new Exception("property resolution error");
+        }
 
-                public Class<?> getReturnType() {
-                    return ctor.getDeclaringClass();
+        /**
+         * Sets the value of a property in a container.
+         * @param object the instance owning the container
+         * @param key the property key
+         * @param value the property value
+         * @return the result of the method invocation (frequently null)
+         * @throws Exception if the property can not be resolved
+         */
+        private Object invokeSet(Object object, Object key, Object value) 
throws Exception {
+            if (setters != null) {
+                final Object[] args = {key, value};
+                final Method jm;
+                if (setters.length == 1) {
+                    jm = setters[0];
+                } else {
+                    jm = new MethodKey(setters[0].getName(), 
args).getMostSpecificMethod(Arrays.asList(setters));
+                }
+                if (jm != null) {
+                    return jm.invoke(object, args);
                 }
-            };
-            return jctor;
+            }
+            throw new Exception("property resolution error");
         }
-        return null;
+
     }
 
     /**
-     * {@inheritDoc}
+     * A generic indexed property container, exposes get(key) and set(key, 
value) and solves method call dynamically
+     * based on arguments.
      */
-    public JexlMethod getMethod(Object obj, String method, Object[] args, 
JexlInfo info) {
-        return getMethodExecutor(obj, method, args);
+    public static final class IndexedContainer {
+        /** The instance owning the container. */
+        private final Object object;
+        /** The container type instance. */
+        private final IndexedType type;
+
+        /**
+         * Creates a new duck container.
+         * @param theType the container type
+         * @param theObject the instance owning the container
+         */
+        private IndexedContainer(IndexedType theType, Object theObject) {
+            this.type = theType;
+            this.object = theObject;
+        }
+
+        /**
+         * Gets a property from a container.
+         * @param key the property key
+         * @return the property value
+         * @throws Exception if inner invocation fails
+         */
+        public Object get(Object key) throws Exception {
+            return type.invokeGet(object, key);
+        }
+
+        /**
+         * Sets a property in a container.
+         * @param key the property key
+         * @param value the property value
+         * @return the invocation result (frequently null)
+         * @throws Exception if inner invocation fails
+         */
+        public Object set(Object key, Object value) throws Exception {
+            return type.invokeSet(object, key, value);
+        }
+    }
+
+    /**
+     * A JexlMethod that wraps constructor.
+     */
+    private final class ConstructorMethod implements JexlMethod {
+        /** The wrapped constructor. */
+        private final Constructor<?> ctor;
+
+        /**
+         * Creates a constructor method.
+         * @param theCtor the constructor to wrap
+         */
+        private ConstructorMethod(Constructor<?> theCtor) {
+            this.ctor = theCtor;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Object invoke(Object obj, Object[] params) throws Exception {
+            Class<?> clazz = null;
+            if (obj instanceof Class<?>) {
+                clazz = (Class<?>) obj;
+            } else if (obj != null) {
+                clazz = getClassByName(obj.toString());
+            } else {
+                clazz = ctor.getDeclaringClass();
+            }
+            if (clazz.equals(ctor.getDeclaringClass())) {
+                return ctor.newInstance(params);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Object tryInvoke(String name, Object obj, Object[] params) {
+            Class<?> clazz = null;
+            if (obj instanceof Class<?>) {
+                clazz = (Class<?>) obj;
+            } else if (obj != null) {
+                clazz = getClassByName(obj.toString());
+            } else {
+                clazz = ctor.getDeclaringClass();
+            }
+            if (clazz.equals(ctor.getDeclaringClass())
+                    && (name == null || name.equals(clazz.getName()))) {
+                try {
+                    return ctor.newInstance(params);
+                } catch (InstantiationException xinstance) {
+                    return TRY_FAILED;
+                } catch (IllegalAccessException xaccess) {
+                    return TRY_FAILED;
+                } catch (IllegalArgumentException xargument) {
+                    return TRY_FAILED;
+                } catch (InvocationTargetException xinvoke) {
+                    return TRY_FAILED;
+                }
+            }
+            return TRY_FAILED;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean tryFailed(Object rval) {
+            return rval == TRY_FAILED;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isCacheable() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Class<?> getReturnType() {
+            return ctor.getDeclaringClass();
+        }
     }
 
+
     /**
      * A JexlPropertyGet for public fields.
      */
-    public static final class FieldPropertyGet implements JexlPropertyGet {
+    private static final class FieldPropertyGet implements JexlPropertyGet {
         /**
          * The public field.
          */
@@ -235,24 +481,11 @@ public class UberspectImpl extends Intro
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public JexlPropertyGet getPropertyGet(Object obj, Object identifier, 
JexlInfo info) {
-        JexlPropertyGet get = getGetExecutor(obj, identifier);
-        if (get == null && obj != null && identifier != null) {
-            Field field = getField(obj, identifier.toString(), info);
-            if (field != null) {
-                return new FieldPropertyGet(field);
-            }
-        }
-        return get;
-    }
 
     /**
      * A JexlPropertySet for public fields.
      */
-    public static final class FieldPropertySet implements JexlPropertySet {
+    private static final class FieldPropertySet implements JexlPropertySet {
         /**
          * The public field.
          */
@@ -306,19 +539,4 @@ public class UberspectImpl extends Intro
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public JexlPropertySet getPropertySet(final Object obj, final Object 
identifier, Object arg, JexlInfo info) {
-        JexlPropertySet set = getSetExecutor(obj, identifier, arg);
-        if (set == null && obj != null && identifier != null) {
-            Field field = getField(obj, identifier.toString(), info);
-            if (field != null
-                    && !Modifier.isFinal(field.getModifiers())
-                    && (arg == null || 
MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) {
-                return new FieldPropertySet(field);
-            }
-        }
-        return set;
-    }
 }

Modified: commons/proper/jexl/trunk/src/site/xdoc/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/changes.xml?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Wed Oct 19 16:43:58 2011
@@ -26,6 +26,9 @@
   </properties>
   <body>
     <release version="2.1" date="unreleased">
+        <action dev="henrib" type="add" issue="JEXL-119">
+            Allow indexed properties container resolution in expressions
+        </action>
         <action dev="henrib" type="add" issue="JEXL-118" due-to="Max 
Tardiveau">
             Provide an IN operator: =~ / match operator extended to provide IN 
behavior (!~ as NOT IN)
         </action>

Modified: 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java?rev=1186317&r1=1186316&r2=1186317&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java
 (original)
+++ 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java
 Wed Oct 19 16:43:58 2011
@@ -725,4 +725,106 @@ public class IssuesTest extends JexlTest
         String dbgdata = dbg.data();
         assertEquals("foo.'q u u x';", dbgdata);
     }
+        
+    public static class Container {
+        String value0;
+        int value1;
+        public Container(String name, int number) {
+            value0 = name;
+            value1 = number;
+        }
+        
+        public Object getProperty(String name) {
+            if ("name".equals(name)) {
+                return value0;
+            } else if ("number".equals(name)) {
+                return value1;
+            } else {
+                return null;
+            }
+        }
+        public Object getProperty(int ref) {
+            if (0 == ref) {
+                return value0;
+            } else if (1 == ref) {
+                return value1;
+            } else {
+                return null;
+            }
+        }
+        
+        public void setProperty(String name, String value) {
+            if ("name".equals(name)) {
+                this.value0 = value;
+            }
+        }  
+        
+        public void setProperty(String name, int value) {
+            if ("number".equals(name)) {
+                this.value1 = value;
+            }
+        }        
+        public void setProperty(int ref, String value) {
+            if (0 == ref) {
+                this.value0 = value;
+            }
+        }  
+        
+        public void setProperty(int ref, int value) {
+            if (1 == ref) {
+                this.value1 = value;
+            }
+        }
+    }
+
+    public void test119() throws Exception {
+        JexlEngine jexl = new JexlEngine();
+        Container quux = new Container("quux", 42);
+        Script get;
+        Object result;
+        
+        Script getName = jexl.createScript("foo.property.name", "foo");
+        result = getName.execute(null, quux);
+        assertEquals("quux", result);
+        
+        Script get0 = jexl.createScript("foo.property.0", "foo");
+        result = get0.execute(null, quux);
+        assertEquals("quux", result);
+        
+        Script getNumber = jexl.createScript("foo.property.number", "foo");
+        result = getNumber.execute(null, quux);
+        assertEquals(42, result);
+        
+        Script get1 = jexl.createScript("foo.property.1", "foo");
+        result = get1.execute(null, quux);
+        assertEquals(42, result);
+        
+        Script setName = jexl.createScript("foo.property.name = $0", "foo", 
"$0");
+        setName.execute(null, quux, "QUUX");
+        result = getName.execute(null, quux);
+        assertEquals("QUUX", result);
+        result = get0.execute(null, quux);
+        assertEquals("QUUX", result);
+        
+        Script set0 = jexl.createScript("foo.property.0 = $0", "foo", "$0");
+        set0.execute(null, quux, "BAR");
+        result = getName.execute(null, quux);
+        assertEquals("BAR", result);
+        result = get0.execute(null, quux);
+        assertEquals("BAR", result);
+        
+        Script setNumber = jexl.createScript("foo.property.number = $0", 
"foo", "$0");
+        setNumber.execute(null, quux, -42);
+        result = getNumber.execute(null, quux);
+        assertEquals(-42, result);
+        result = get1.execute(null, quux);
+        assertEquals(-42, result);
+        
+        Script set1 = jexl.createScript("foo.property.1 = $0", "foo", "$0");
+        set1.execute(null, quux, 24);
+        result = getNumber.execute(null, quux);
+        assertEquals(24, result);
+        result = get1.execute(null, quux);
+        assertEquals(24, result);
+    }
 }


Reply via email to