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); + } }