Author: henrib Date: Thu Jan 7 18:21:29 2010 New Revision: 896952 URL: http://svn.apache.org/viewvc?rev=896952&view=rev Log: Made public fields 'first class' properties; Added a field cache to ClassMap; Added getField/getFieldNames in appropriate classes; Updated tests accordingly
Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.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/introspection/Uberspect.java commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/IssuesTest.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java?rev=896952&r1=896951&r2=896952&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java Thu Jan 7 18:21:29 2010 @@ -116,7 +116,7 @@ */ private static final class UberspectHolder { /** The default uberspector that handles all introspection patterns. */ - private static final Uberspect UBERSPECT = new UberspectImpl(LogFactory.getLog(JexlEngine.class), false); + private static final Uberspect UBERSPECT = new UberspectImpl(LogFactory.getLog(JexlEngine.class)); /** Non-instantiable. */ private UberspectHolder() {} } @@ -202,7 +202,7 @@ if (logger == null || logger.equals(LogFactory.getLog(JexlEngine.class))) { return UberspectHolder.UBERSPECT; } - return new UberspectImpl(logger, false); + return new UberspectImpl(logger); } /** 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=896952&r1=896951&r2=896952&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 Thu Jan 7 18:21:29 2010 @@ -19,6 +19,7 @@ import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import org.apache.commons.jexl2.internal.introspection.IntrospectorBase; import org.apache.commons.jexl2.internal.introspection.MethodKey; @@ -105,6 +106,26 @@ /** + * Gets the field named by <code>key</code> for the class <code>c</code>. + * + * @param c Class in which the field search is taking place + * @param key Name of the field being searched for + * @return the desired field or null if it does not exist or is not accessible + * */ + protected final Field getField(Class<?> c, String key) { + return base().getField(c, key); + } + + /** + * Gets the accessible field names known for a given class. + * @param c the class + * @return the class field names + */ + public final String[] getFieldNames(Class<?> c) { + return base().getFieldNames(c); + } + + /** * Gets the method defined by <code>name</code> and * <code>params</code> for the Class <code>c</code>. * 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=896952&r1=896951&r2=896952&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 Thu Jan 7 18:21:29 2010 @@ -16,8 +16,10 @@ */ package org.apache.commons.jexl2.internal.introspection; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -43,6 +45,8 @@ final class ClassMap { /** cache of methods. */ private final MethodCache methodCache; + /** cache of fields. */ + private final Map<String, Field> fieldCache; /** * Standard constructor. @@ -50,11 +54,52 @@ * @param aClass the class to deconstruct. * @param log the logger. */ - public ClassMap(Class<?> aClass, Log log) { + ClassMap(Class<?> aClass, Log log) { + // eagerly cache methods methodCache = createMethodCache(aClass, log); + // eagerly cache public fields + fieldCache = createFieldCache(aClass); } /** + * Find a Field using its name. + * <p>The clazz parameter <strong>must</strong> be this ClassMap key.</p> + * @param clazz the class to introspect + * @param fname the field name + * @return A Field object representing the field to invoke or null. + */ + Field findField(final Class<?> clazz, final String fname) { + return fieldCache.get(fname); + } + + /** + * Gets the field names cached by this map. + * @return the array of field names + */ + String[] getFieldNames() { + return fieldCache.keySet().toArray(new String[fieldCache.size()]); + } + + /** + * Creates a map of all public fields of a given class. + * @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) { + Field[] fields = clazz.getFields(); + if (fields.length > 0) { + Map<String, Field> cache = new HashMap<String, Field>(); + for(Field field : fields) { + cache.put(field.getName(), field); + } + return cache; + } else { + return Collections.emptyMap(); + } + } + + + /** * Gets the methods names cached by this map. * @return the array of method names */ @@ -69,7 +114,7 @@ * @return A Method object representing the method to invoke or null. * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters. */ - public Method findMethod(final MethodKey key) + Method findMethod(final MethodKey key) throws MethodKey.AmbiguousException { return methodCache.get(key); } @@ -220,7 +265,7 @@ * name and actual arguments used to find it. * </p> */ - private final Map<MethodKey, Method> cache = new HashMap<MethodKey, Method>(); + private final Map<MethodKey, Method> methods = new HashMap<MethodKey, Method>(); /** * Map of methods that are searchable according to method parameters to find a match. */ @@ -242,10 +287,9 @@ * @return A Method object representing the method to invoke or null. * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters. */ - Method get(final MethodKey methodKey) - throws MethodKey.AmbiguousException { + Method get(final MethodKey methodKey) throws MethodKey.AmbiguousException { synchronized (methodMap) { - Method cacheEntry = cache.get(methodKey); + Method cacheEntry = methods.get(methodKey); // We looked this up before and failed. if (cacheEntry == CACHE_MISS) { return null; @@ -256,13 +300,13 @@ // That one is expensive... cacheEntry = methodMap.find(methodKey); if (cacheEntry != null) { - cache.put(methodKey, cacheEntry); + methods.put(methodKey, cacheEntry); } else { - cache.put(methodKey, CACHE_MISS); + methods.put(methodKey, CACHE_MISS); } } catch (MethodKey.AmbiguousException ae) { // that's a miss :-) - cache.put(methodKey, CACHE_MISS); + methods.put(methodKey, CACHE_MISS); throw ae; } } @@ -283,8 +327,8 @@ // cache from defined class towards java.lang.Object because // abstract methods in superclasses would else overwrite concrete // classes further down the hierarchy. - if (cache.get(methodKey) == null) { - cache.put(methodKey, method); + if (methods.get(methodKey) == null) { + methods.put(methodKey, method); methodMap.add(method); } } 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=896952&r1=896951&r2=896952&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 Thu Jan 7 18:21:29 2010 @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.util.Map; import java.util.HashMap; import java.util.List; @@ -110,8 +111,34 @@ } // CSON: RedundantThrows + + /** + * Gets the field named by <code>key</code> for the class <code>c</code>. + * + * @param c Class in which the field search is taking place + * @param key Name of the field being searched for + * @return the desired field or null if it does not exist or is not accessible + * */ + public Field getField(Class<?> c, String key) { + ClassMap classMap = getMap(c); + return classMap.findField(c, key); + } + + /** + * Gets the array of accessible field names known for a given class. + * @param c the class + * @return the class field names + */ + public String[] getFieldNames(Class<?> c) { + if (c == null) { + return new String[0]; + } + ClassMap classMap = getMap(c); + return classMap.getFieldNames(); + } + /** - * Gets the accessible methods names known for a given class. + * Gets the array of accessible methods names known for a given class. * @param c the class * @return the class method names */ Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java?rev=896952&r1=896951&r2=896952&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java Thu Jan 7 18:21:29 2010 @@ -40,8 +40,8 @@ * Returns a class constructor. * @param ctorHandle a class or class name * @param args constructor arguments - * @param info template info - * @return a {...@link Constructor}. + * @param info contextual information + * @return a {...@link Constructor} */ Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info); /** @@ -49,18 +49,18 @@ * @param obj the object * @param method the method name * @param args method arguments - * @param info template info - * @return a {...@link JexlMethod}. + * @param info contextual information + * @return a {...@link JexlMethod} */ JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info); /** * Property getter. * <p>Returns JexlPropertyGet appropos for ${bar.woogie}. - * @param obj the object to get the property from. + * @param obj the object to get the property from * @param identifier property name - * @param info template info - * @return a {...@link JexlPropertyGet}. + * @param info contextual information + * @return a {...@link JexlPropertyGet} */ JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info); @@ -69,17 +69,17 @@ * <p>returns JelPropertySet appropos for ${foo.bar = "geir"}</p>. * @param obj the object to get the property from. * @param identifier property name - * @param arg value to set. - * @param info template info + * @param arg value to set + * @param info contextual information * @return a {...@link JexlPropertySet}. */ JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg, JexlInfo info); /** * Gets an iterator from an object. - * @param obj to get the iterator for. - * @param info some info. - * @return an iterator over obj. + * @param obj to get the iterator for + * @param info contextual information + * @return an iterator over obj */ Iterator<?> getIterator(Object obj, JexlInfo info); 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=896952&r1=896951&r2=896952&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 Thu Jan 7 18:21:29 2010 @@ -19,6 +19,7 @@ import org.apache.commons.jexl2.internal.Introspector; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Enumeration; import java.util.Iterator; @@ -46,19 +47,13 @@ * Publicly exposed special failure object returned by tryInvoke. */ public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED; - /** - * Whether public fields can be considered as properties. - */ - protected final boolean publicProperties; /** * Creates a new UberspectImpl. * @param runtimeLogger the logger used for all logging needs - * @param publicFields whether public fields should be considered as properties */ - public UberspectImpl(Log runtimeLogger, boolean publicFields) { + public UberspectImpl(Log runtimeLogger) { super(runtimeLogger); - publicProperties = publicFields; } /** @@ -96,6 +91,18 @@ } /** + * Returns a class field. + * @param obj the object + * @param name the field name + * @param info debug info + * @return a {...@link Field}. + */ + public Field getField(Object obj, String name, JexlInfo info) { + final Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass(); + return getField(clazz, name); + } + + /** * {...@inheritdoc} */ public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) { @@ -110,26 +117,6 @@ } /** - * Gets a field by name from a class. - * @param clazz the class to find the field in - * @param name the field name - * @return the field instance or null if it could not be found - */ - protected static Field getField(Class<?> clazz, String name) { - try { - Field field = clazz.getField(name); - if (!field.isAccessible()) { - field.setAccessible(true); - } - return field; - } catch (NoSuchFieldException xnsf) { - return null; - } catch (SecurityException xsec) { - return null; - } - } - - /** * A JexlPropertyGet for public fields. */ public static final class FieldPropertyGet implements JexlPropertyGet { @@ -187,9 +174,8 @@ */ public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { JexlPropertyGet get = getGetExecutor(obj, identifier); - if (get == null && publicProperties && obj != null && identifier != null) { - Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass(); - Field field = getField(clazz, identifier.toString()); + if (get == null && obj != null && identifier != null) { + Field field = getField(obj, identifier.toString(), info); if (field != null) { return new FieldPropertyGet(field); } @@ -259,10 +245,10 @@ */ public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) { JexlPropertySet set = getSetExecutor(obj, identifier, arg); - if (set == null && publicProperties && obj != null && identifier != null) { - Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass(); - Field field = getField(clazz, identifier.toString()); + 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); } 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=896952&r1=896951&r2=896952&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 Thu Jan 7 18:21:29 2010 @@ -19,6 +19,7 @@ import org.apache.commons.jexl2.internal.Introspector; import java.util.HashMap; import java.util.Map; +import org.apache.commons.jexl2.introspection.UberspectImpl; /** * Test cases for reported issues @@ -42,6 +43,7 @@ // JEXL-48: bad assignment detection public static class Another { + public String name = "whatever"; private Boolean foo = Boolean.TRUE; public Boolean foo() { @@ -157,6 +159,16 @@ } } assertTrue("should have foo & goo", found == 2); + + names = ((UberspectImpl) uber).getFieldNames(Another.class); + assertTrue("should find fields", names.length > 0); + found = 0; + for (String name : names) { + if ("name".equals(name)) { + found += 1; + } + } + assertTrue("should have name", found == 1); } // JEXL-10/JEXL-11: variable checking, null operand is error Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java?rev=896952&r1=896951&r2=896952&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java (original) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java Thu Jan 7 18:21:29 2010 @@ -48,8 +48,6 @@ private JexlContext ctxt; public PublicFieldsTest() { - // create an Uberspect that considers public fields as properties - super(new JexlEngine(new UberspectImpl(LogFactory.getLog(PublicFieldsTest.class), true), null, null, null)); JEXL.setLenient(false); }