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