WICKET-5623 extensible PropertyResolver

Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/4f3ec335
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/4f3ec335
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/4f3ec335

Branch: refs/heads/wicket-7.x
Commit: 4f3ec33568b74c87b3700930bdcf1c9ba4f5e101
Parents: 3830160
Author: Sven Meier <svenme...@apache.org>
Authored: Thu Jul 14 00:16:48 2016 +0200
Committer: Sven Meier <svenme...@apache.org>
Committed: Wed Jul 20 00:25:47 2016 +0200

----------------------------------------------------------------------
 .../wicket/core/util/lang/PropertyResolver.java | 813 ++++++++++---------
 1 file changed, 440 insertions(+), 373 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/4f3ec335/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
index 37d5edb..c3e3025 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.wicket.Application;
 import org.apache.wicket.Session;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.core.util.lang.PropertyResolverConverter;
 import org.apache.wicket.util.convert.ConversionException;
 import org.apache.wicket.util.lang.Generics;
 import org.apache.wicket.util.string.Strings;
@@ -39,36 +40,45 @@ import org.slf4j.LoggerFactory;
  * <p>
  * This class parses expressions to lookup or set a value on the object that 
is given. <br/>
  * The supported expressions are:
- * <p>
- * "property": This could be a bean property with get and set method. Or if a 
map is given as
- * an object it will be lookup with the property as a key when there is not 
get method for that
+ * <dl>
+ * <dt>"property"</dt>
+ * <dd>
+ * This could be a bean property with getter and setter. Or if a map is given 
as
+ * an object it will be lookup with the expression as a key when there is not 
getter for that
  * property.
- * <p/>
- * <p>
- * "property1.property2": Both properties are looked up as described above. If 
property1 evaluates to
- * null then if there is a setMethod (or if it is a map) and the Class of the 
property has a default
+ * </dd>
+ * <dt>"property1.property2"</dt>
+ * <dd>
+ * Both properties are looked up as described above. If property1 evaluates to
+ * null then if there is a setter (or if it is a map) and the Class of the 
property has a default
  * constructor then the object will be constructed and set on the object.
- * <p/>
- * <p>
- * "property.index": If the property is a List or Array then the second 
property can be a index on
- * that list like: 'mylist.0' this expression will also map on a 
getProperty(index) or
- * setProperty(index,value) methods. If the object is a List then the list 
will grow automatically
- * if the index is greater than the size
- * <p/>
- * <p>
- * Index or map properties can also be written as: "property[index]" or 
"property[key]"
- * <p/>
+ * </dd>
+ * <dt>"method()"</dt>
+ * <dd>
+ * The corresponding method is invoked.
+ * </dd>
+ * <dt>"property.index" or "property[index]"</dt>
+ * <dd>
+ * If the property is a List or Array then the following expression can be a 
index on
+ * that list like: 'mylist.0'. The list will grow automatically if the index 
is greater than the size.<p>
+ * This expression will also map on indexed properties, i.e. {@code 
getProperty(index)} and {@code setProperty(index,value)}
+ * methods.
+ * </dd>
+ * <dt>"property.key" or "property[key]"</dt>
+ * <dd>
+ * If the property is a Map then the following expression can be a key in that 
map like: 'myMap.key'.
+ * </dd>
+ * </dl>
+ * <strong>Note that the {@link DefaultGetAndSetLocator} by default provides 
access to private members
+ * and methods. If guaranteeing encapsulation of the target objects is a big 
concern, you should consider
+ * using an alternative implementation.</strong>
  * <p>
- * <strong>Note that the property resolver by default provides access to 
private members and methods. If
- * guaranteeing encapsulation of the target objects is a big concern, you 
should consider using an
- * alternative implementation.</strong>
- * </p>
- * <p><strong>Note: If a property evaluates to an instance of {@link 
org.apache.wicket.model.IModel} then
- * the expression should use '.object' to work with its value.</strong></p>
+ * <strong>Note: If a property evaluates to an instance of {@link 
org.apache.wicket.model.IModel} then
+ * the expression should use '.object' to work with its value.</strong>
  *
  * @author jcompagner
+ * @author svenmeier
  */
-@SuppressWarnings("unused")
 public final class PropertyResolver
 {
        /** Log. */
@@ -78,7 +88,7 @@ public final class PropertyResolver
        private final static int CREATE_NEW_VALUE = 1;
        private final static int RESOLVE_CLASS = 2;
 
-       private final static ConcurrentHashMap<Object, IClassCache> 
applicationToClassesToGetAndSetters = Generics.newConcurrentHashMap(2);
+       private final static ConcurrentHashMap<Object, IGetAndSetLocator> 
applicationToLocators = Generics.newConcurrentHashMap(2);
 
        private static final String GET = "get";
        private static final String IS = "is";
@@ -101,13 +111,13 @@ public final class PropertyResolver
                        return object;
                }
 
-               ObjectAndGetSetter getter = getObjectAndGetSetter(expression, 
object, RETURN_NULL);
-               if (getter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, RETURN_NULL);
+               if (objectWithGetAndSet == null)
                {
                        return null;
                }
 
-               return getter.getValue();
+               return objectWithGetAndSet.getValue();
        }
 
        /**
@@ -142,13 +152,13 @@ public final class PropertyResolver
                                        expression + " Value: " + value);
                }
 
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
object, CREATE_NEW_VALUE);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, CREATE_NEW_VALUE);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("Null object returned 
for expression: " + expression +
                                " for setting value: " + value + " on: " + 
object);
                }
-               setter.setValue(value, converter == null ? new 
PropertyResolverConverter(Application.get()
+               objectWithGetAndSet.setValue(value, converter == null ? new 
PropertyResolverConverter(Application.get()
                        .getConverterLocator(), Session.get().getLocale()) : 
converter);
        }
 
@@ -160,13 +170,13 @@ public final class PropertyResolver
         */
        public static Class<?> getPropertyClass(final String expression, final 
Object object)
        {
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
object, RESOLVE_CLASS);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, RESOLVE_CLASS);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("Null object returned 
for expression: " + expression +
                                " for getting the target class of: " + object);
                }
-               return setter.getTargetClass();
+               return objectWithGetAndSet.getTargetClass();
        }
 
        /**
@@ -179,13 +189,13 @@ public final class PropertyResolver
        @SuppressWarnings("unchecked")
        public static <T> Class<T> getPropertyClass(final String expression, 
final Class<?> clz)
        {
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
null, RESOLVE_CLASS, clz);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, null, RESOLVE_CLASS, clz);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("No Class returned for 
expression: " + expression +
                                " for getting the target class of: " + clz);
                }
-               return (Class<T>)setter.getTargetClass();
+               return (Class<T>)objectWithGetAndSet.getTargetClass();
        }
 
        /**
@@ -196,13 +206,13 @@ public final class PropertyResolver
         */
        public static Field getPropertyField(final String expression, final 
Object object)
        {
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
object, RESOLVE_CLASS);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, RESOLVE_CLASS);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("Null object returned 
for expression: " + expression +
                                " for getting the target class of: " + object);
                }
-               return setter.getField();
+               return objectWithGetAndSet.getField();
        }
 
        /**
@@ -213,13 +223,13 @@ public final class PropertyResolver
         */
        public static Method getPropertyGetter(final String expression, final 
Object object)
        {
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
object, RESOLVE_CLASS);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, RESOLVE_CLASS);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("Null object returned 
for expression: " + expression +
                                " for getting the target class of: " + object);
                }
-               return setter.getGetter();
+               return objectWithGetAndSet.getGetter();
        }
 
        /**
@@ -230,13 +240,13 @@ public final class PropertyResolver
         */
        public static Method getPropertySetter(final String expression, final 
Object object)
        {
-               ObjectAndGetSetter setter = getObjectAndGetSetter(expression, 
object, RESOLVE_CLASS);
-               if (setter == null)
+               ObjectWithGetAndSet objectWithGetAndSet = 
getObjectWithGetAndSet(expression, object, RESOLVE_CLASS);
+               if (objectWithGetAndSet == null)
                {
                        throw new WicketRuntimeException("Null object returned 
for expression: " + expression +
                                " for getting the target class of: " + object);
                }
-               return setter.getSetter();
+               return objectWithGetAndSet.getSetter();
        }
 
        /**
@@ -246,12 +256,12 @@ public final class PropertyResolver
         * @param expression
         * @param object
         * @param tryToCreateNull
-        * @return {@link ObjectAndGetSetter}
+        * @return {@link ObjectWithGetAndSet}
         */
-       private static ObjectAndGetSetter getObjectAndGetSetter(final String 
expression,
+       private static ObjectWithGetAndSet getObjectWithGetAndSet(final String 
expression,
                final Object object, int tryToCreateNull)
        {
-               return getObjectAndGetSetter(expression, object, 
tryToCreateNull, object.getClass());
+               return getObjectWithGetAndSet(expression, object, 
tryToCreateNull, object.getClass());
        }
 
        /**
@@ -262,10 +272,9 @@ public final class PropertyResolver
         * @param object
         * @param tryToCreateNull
         * @param clz
-        * @return {@link ObjectAndGetSetter}
+        * @return {@link ObjectWithGetAndSet}
         */
-       private static ObjectAndGetSetter getObjectAndGetSetter(final String 
expression,
-               final Object object, final int tryToCreateNull, Class<?> clz)
+       private static ObjectWithGetAndSet getObjectWithGetAndSet(final String 
expression, final Object object, final int tryToCreateNull, Class<?> clz)
        {
                String expressionBracketsSeperated = 
Strings.replaceAll(expression, "[", ".[").toString();
                int index = getNextDotIndex(expressionBracketsSeperated, 0);
@@ -288,52 +297,50 @@ public final class PropertyResolver
                                break;
                        }
 
-                       IGetAndSet getAndSetter = null;
+                       IGetAndSet getAndSet = null;
                        try
                        {
-                               getAndSetter = getGetAndSetter(exp, clz);
+                               getAndSet = getGetAndSet(exp, clz);
                        }
                        catch (WicketRuntimeException ex)
                        {
-                               // expression by it self can't be found. try to 
find a
-                               // setPropertyByIndex(int,value) method
-                               index = 
getNextDotIndex(expressionBracketsSeperated, index + 1);
-                               if (index != -1)
-                               {
-                                       String indexExpression = 
expressionBracketsSeperated.substring(lastIndex, index);
-                                       getAndSetter = 
getGetAndSetter(indexExpression, clz);
-                               }
-                               else
+                               // expression by itself can't be found. try 
combined with the following
+                               // expression (e.g. for a indexed property);
+                               int temp = 
getNextDotIndex(expressionBracketsSeperated, index + 1);
+                               if (temp == -1)
                                {
                                        exp = 
expressionBracketsSeperated.substring(lastIndex);
                                        break;
+                               } else {
+                                       index = temp;
+                                       continue;
                                }
                        }
-                       Object newValue = null;
+                       Object nextValue = null;
                        if (value != null)
                        {
-                               newValue = getAndSetter.getValue(value);
+                               nextValue = getAndSet.getValue(value);
                        }
-                       if (newValue == null)
+                       if (nextValue == null)
                        {
                                if (tryToCreateNull == CREATE_NEW_VALUE)
                                {
-                                       newValue = getAndSetter.newValue(value);
-                                       if (newValue == null)
+                                       nextValue = getAndSet.newValue(value);
+                                       if (nextValue == null)
                                        {
                                                return null;
                                        }
                                }
                                else if (tryToCreateNull == RESOLVE_CLASS)
                                {
-                                       clz = getAndSetter.getTargetClass();
+                                       clz = getAndSet.getTargetClass();
                                }
                                else
                                {
                                        return null;
                                }
                        }
-                       value = newValue;
+                       value = nextValue;
                        if (value != null)
                        {
                                // value can be null if we are in the 
RESOLVE_CLASS
@@ -348,8 +355,8 @@ public final class PropertyResolver
                                break;
                        }
                }
-               IGetAndSet getAndSetter = getGetAndSetter(exp, clz);
-               return new ObjectAndGetSetter(getAndSetter, value);
+               IGetAndSet getAndSet = getGetAndSet(exp, clz);
+               return new ObjectWithGetAndSet(getAndSet, value);
        }
 
        /**
@@ -380,240 +387,11 @@ public final class PropertyResolver
                return -1;
        }
 
-       private static IGetAndSet getGetAndSetter(String exp, final Class<?> 
clz)
-       {
-               IClassCache classesToGetAndSetters = 
getClassesToGetAndSetters();
-               Map<String, IGetAndSet> getAndSetters = 
classesToGetAndSetters.get(clz);
-               if (getAndSetters == null)
-               {
-                       getAndSetters = new ConcurrentHashMap<String, 
IGetAndSet>(8);
-                       classesToGetAndSetters.put(clz, getAndSetters);
-               }
-
-               IGetAndSet getAndSetter = getAndSetters.get(exp);
-               if (getAndSetter == null)
-               {
-                       Method method = null;
-                       Field field;
-                       if (exp.startsWith("["))
-                       {
-                               // if expression begins with [ skip method 
finding and use it as
-                               // a key/index lookup on a map.
-                               exp = exp.substring(1, exp.length() - 1);
-                       }
-                       else if (exp.endsWith("()"))
-                       {
-                               // if expression ends with (), don't test for 
setters just skip
-                               // directly to method finding.
-                               method = findMethod(clz, exp);
-                       }
-                       else
-                       {
-                               method = findGetter(clz, exp);
-                       }
-                       if (method == null)
-                       {
-                               if (List.class.isAssignableFrom(clz))
-                               {
-                                       try
-                                       {
-                                               int index = 
Integer.parseInt(exp);
-                                               getAndSetter = new 
ListGetSet(index);
-                                       }
-                                       catch (NumberFormatException ex)
-                                       {
-                                               // can't parse the exp as an 
index, maybe the exp was a
-                                               // method.
-                                               method = findMethod(clz, exp);
-                                               if (method != null)
-                                               {
-                                                       getAndSetter = new 
MethodGetAndSet(method, MethodGetAndSet.findSetter(
-                                                               method, clz), 
null);
-                                               }
-                                               else
-                                               {
-                                                       field = findField(clz, 
exp);
-                                                       if (field != null)
-                                                       {
-                                                               getAndSetter = 
new FieldGetAndSetter(field);
-                                                       }
-                                                       else
-                                                       {
-                                                               throw new 
WicketRuntimeException(
-                                                                       "The 
expression '" +
-                                                                               
exp +
-                                                                               
"' is neither an index nor is it a method or field for the list " +
-                                                                               
clz);
-                                                       }
-                                               }
-                                       }
-                               }
-                               else if (Map.class.isAssignableFrom(clz))
-                               {
-                                       getAndSetter = new MapGetSet(exp);
-                               }
-                               else if (clz.isArray())
-                               {
-                                       try
-                                       {
-                                               int index = 
Integer.parseInt(exp);
-                                               getAndSetter = new 
ArrayGetSet(clz.getComponentType(), index);
-                                       }
-                                       catch (NumberFormatException ex)
-                                       {
-                                               if (exp.equals("length") || 
exp.equals("size"))
-                                               {
-                                                       getAndSetter = new 
ArrayLengthGetSet();
-                                               }
-                                               else
-                                               {
-                                                       throw new 
WicketRuntimeException("Can't parse the expression '" + exp +
-                                                               "' as an index 
for an array lookup");
-                                               }
-                                       }
-                               }
-                               else
-                               {
-                                       field = findField(clz, exp);
-                                       if (field == null)
-                                       {
-                                               method = findMethod(clz, exp);
-                                               if (method == null)
-                                               {
-                                                       int index = 
exp.indexOf('.');
-                                                       if (index != -1)
-                                                       {
-                                                               String 
propertyName = exp.substring(0, index);
-                                                               String 
propertyIndex = exp.substring(index + 1);
-                                                               try
-                                                               {
-                                                                       int 
parsedIndex = Integer.parseInt(propertyIndex);
-                                                                       // if 
so then it could be a getPropertyIndex(int)
-                                                                       // and 
setPropertyIndex(int, object)
-                                                                       String 
name = Character.toUpperCase(propertyName.charAt(0)) +
-                                                                               
propertyName.substring(1);
-                                                                       method 
= clz.getMethod(GET + name, new Class[] { int.class });
-                                                                       
getAndSetter = new ArrayPropertyGetSet(method, parsedIndex);
-                                                               }
-                                                               catch 
(Exception e)
-                                                               {
-                                                                       throw 
new WicketRuntimeException(
-                                                                               
"No get method defined for class: " + clz +
-                                                                               
        " expression: " + propertyName);
-                                                               }
-                                                       }
-                                                       else
-                                                       {
-                                                               // We do not 
look for a public FIELD because
-                                                               // that is not 
good programming with beans patterns
-                                                               throw new 
WicketRuntimeException(
-                                                                       "No get 
method defined for class: " + clz + " expression: " +
-                                                                               
exp);
-                                                       }
-                                               }
-                                               else
-                                               {
-                                                       getAndSetter = new 
MethodGetAndSet(method, MethodGetAndSet.findSetter(
-                                                               method, clz), 
null);
-                                               }
-                                       }
-                                       else
-                                       {
-                                               getAndSetter = new 
FieldGetAndSetter(field);
-                                       }
-                               }
-                       }
-                       else
-                       {
-                               field = findField(clz, exp);
-                               getAndSetter = new MethodGetAndSet(method, 
MethodGetAndSet.findSetter(method, clz),
-                                       field);
-                       }
-                       getAndSetters.put(exp, getAndSetter);
-               }
-               return getAndSetter;
-       }
-
-
-       /**
-        * @param clz
-        * @param expression
-        * @return introspected field
-        */
-       private static Field findField(final Class<?> clz, final String 
expression)
-       {
-               Field field = null;
-               try
-               {
-                       field = clz.getField(expression);
-               }
-               catch (Exception e)
-               {
-                       Class<?> tmp = clz;
-                       while (tmp != null && tmp != Object.class)
-                       {
-                               Field[] fields = tmp.getDeclaredFields();
-                               for (Field aField : fields)
-                               {
-                                       if (aField.getName().equals(expression))
-                                       {
-                                               aField.setAccessible(true);
-                                               return aField;
-                                       }
-                               }
-                               tmp = tmp.getSuperclass();
-                       }
-                       log.debug("Cannot find field " + clz + "." + 
expression);
-               }
-               return field;
-       }
-
-       /**
-        * @param clz
-        * @param expression
-        * @return The method for the expression null if not found
-        */
-       private static Method findGetter(final Class<?> clz, final String 
expression)
+       private static IGetAndSet getGetAndSet(String exp, final Class<?> clz)
        {
-               String name = Character.toUpperCase(expression.charAt(0)) + 
expression.substring(1);
-               Method method = null;
-               try
-               {
-                       method = clz.getMethod(GET + name, (Class[])null);
-               }
-               catch (Exception ignored)
-               {
-               }
-               if (method == null)
-               {
-                       try
-                       {
-                               method = clz.getMethod(IS + name, 
(Class[])null);
-                       }
-                       catch (Exception e)
-                       {
-                               log.debug("Cannot find getter " + clz + "." + 
expression);
-                       }
-               }
-               return method;
-       }
-
-       private static Method findMethod(final Class<?> clz, String expression)
-       {
-               if (expression.endsWith("()"))
-               {
-                       expression = expression.substring(0, 
expression.length() - 2);
-               }
-               Method method = null;
-               try
-               {
-                       method = clz.getMethod(expression, (Class[])null);
-               }
-               catch (Exception e)
-               {
-                       log.debug("Cannot find method " + clz + "." + 
expression);
-               }
-               return method;
+               IGetAndSetLocator locator = getLocator();
+               
+               return locator.getAndSet(clz, exp);
        }
 
        /**
@@ -627,18 +405,18 @@ public final class PropertyResolver
         * @author jcompagner
         *
         */
-       private final static class ObjectAndGetSetter
+       private final static class ObjectWithGetAndSet
        {
-               private final IGetAndSet getAndSetter;
+               private final IGetAndSet getAndSet;
                private final Object value;
 
                /**
-                * @param getAndSetter
+                * @param getAndSet
                 * @param value
                 */
-               public ObjectAndGetSetter(IGetAndSet getAndSetter, Object value)
+               public ObjectWithGetAndSet(IGetAndSet getAndSet, Object value)
                {
-                       this.getAndSetter = getAndSetter;
+                       this.getAndSet = getAndSet;
                        this.value = value;
                }
 
@@ -648,7 +426,7 @@ public final class PropertyResolver
                 */
                public void setValue(Object value, PropertyResolverConverter 
converter)
                {
-                       getAndSetter.setValue(this.value, value, converter);
+                       getAndSet.setValue(this.value, value, converter);
                }
 
                /**
@@ -656,7 +434,7 @@ public final class PropertyResolver
                 */
                public Object getValue()
                {
-                       return getAndSetter.getValue(value);
+                       return getAndSet.getValue(value);
                }
 
                /**
@@ -664,7 +442,7 @@ public final class PropertyResolver
                 */
                public Class<?> getTargetClass()
                {
-                       return getAndSetter.getTargetClass();
+                       return getAndSet.getTargetClass();
                }
 
                /**
@@ -672,7 +450,7 @@ public final class PropertyResolver
                 */
                public Field getField()
                {
-                       return getAndSetter.getField();
+                       return getAndSet.getField();
                }
 
                /**
@@ -680,7 +458,7 @@ public final class PropertyResolver
                 */
                public Method getGetter()
                {
-                       return getAndSetter.getGetter();
+                       return getAndSet.getGetter();
                }
 
                /**
@@ -688,7 +466,7 @@ public final class PropertyResolver
                 */
                public Method getSetter()
                {
-                       return getAndSetter.getSetter();
+                       return getAndSet.getSetter();
                }
        }
 
@@ -781,11 +559,11 @@ public final class PropertyResolver
                }
        }
 
-       private static final class MapGetSet extends AbstractGetAndSet
+       private static final class MapGetAndSet extends AbstractGetAndSet
        {
                private final String key;
 
-               MapGetSet(String key)
+               MapGetAndSet(String key)
                {
                        this.key = key;
                }
@@ -822,11 +600,11 @@ public final class PropertyResolver
                }
        }
 
-       private static final class ListGetSet extends AbstractGetAndSet
+       private static final class ListGetAndSet extends AbstractGetAndSet
        {
                final private int index;
 
-               ListGetSet(int index)
+               ListGetAndSet(int index)
                {
                        this.index = index;
                }
@@ -884,12 +662,12 @@ public final class PropertyResolver
                }
        }
 
-       private static final class ArrayGetSet extends AbstractGetAndSet
+       private static final class ArrayGetAndSet extends AbstractGetAndSet
        {
                private final int index;
                private final Class<?> clzComponentType;
 
-               ArrayGetSet(Class<?> clzComponentType, int index)
+               ArrayGetAndSet(Class<?> clzComponentType, int index)
                {
                        this.clzComponentType = clzComponentType;
                        this.index = index;
@@ -948,9 +726,9 @@ public final class PropertyResolver
                }
        }
 
-       private static final class ArrayLengthGetSet extends AbstractGetAndSet
+       private static final class ArrayLengthGetAndSet extends 
AbstractGetAndSet
        {
-               ArrayLengthGetSet()
+               ArrayLengthGetAndSet()
                {
                }
 
@@ -993,13 +771,13 @@ public final class PropertyResolver
                }
        }
 
-       private static final class ArrayPropertyGetSet extends AbstractGetAndSet
+       private static final class IndexedPropertyGetAndSet extends 
AbstractGetAndSet
        {
                final private Integer index;
                final private Method getMethod;
                private Method setMethod;
 
-               ArrayPropertyGetSet(final Method method, final int index)
+               IndexedPropertyGetAndSet(final Method method, final int index)
                {
                        this.index = index;
                        getMethod = method;
@@ -1358,7 +1136,7 @@ public final class PropertyResolver
        /**
         * @author jcompagner
         */
-       private static class FieldGetAndSetter extends AbstractGetAndSet
+       private static class FieldGetAndSet extends AbstractGetAndSet
        {
                private final Field field;
 
@@ -1367,7 +1145,7 @@ public final class PropertyResolver
                 *
                 * @param field
                 */
-               public FieldGetAndSetter(final Field field)
+               public FieldGetAndSet(final Field field)
                {
                        super();
                        this.field = field;
@@ -1449,7 +1227,61 @@ public final class PropertyResolver
                }
        }
 
-       private static IClassCache getClassesToGetAndSetters()
+       /**
+        * Clean up cache for this app.
+        *
+        * @param application
+        */
+       public static void destroy(Application application)
+       {
+               applicationToLocators.remove(application);
+       }
+
+       /**
+        * Sets the {@link IGetAndSetLocator} for the given application.
+        *
+        * If the Application is null then it will be the default if no 
application is found. So if you
+        * want to be sure that your {@link IGetAndSetLocator} is handled in 
all situations then call this
+        * method twice with your implementations. One time for the application 
and the second time with
+        * null.
+        *
+        * @param application
+        *            to use or null if the default must be set.
+        * @param classCache
+        */
+       @Deprecated
+       public static void setClassCache(final Application application, final 
IClassCache classCache)
+       {
+               setLocator(application, new IGetAndSetLocator() {
+                       
+                       private DefaultGetAndSetLocator locator = new 
DefaultGetAndSetLocator();
+                       
+                       @Override
+                       public IGetAndSet getAndSet(Class<?> clz, String name) {
+                               Map<String, IGetAndSet> map = 
classCache.get(clz);
+                               if (map == null) {
+                                       map = new ConcurrentHashMap<String, 
IGetAndSet>(8);
+                                       classCache.put(clz, map);
+                               }
+                               
+                               IGetAndSet getAndSetter = map.get(name);
+                               if (getAndSetter == null) {
+                                       getAndSetter = locator.getAndSet(clz, 
name);
+                                       map.put(name, getAndSetter);
+                               }
+                               
+                               return getAndSetter;
+                       }
+               });
+       }
+
+       /**
+        * Get the current {@link IGetAndSetLocator}.
+        * 
+        * @return locator for the current {@link Application} or a general one 
if no current application is present
+        * @see Application#get()
+        */
+       public static IGetAndSetLocator getLocator()
        {
                Object key;
                if (Application.exists())
@@ -1460,10 +1292,10 @@ public final class PropertyResolver
                {
                        key = PropertyResolver.class;
                }
-               IClassCache result = 
applicationToClassesToGetAndSetters.get(key);
+               IGetAndSetLocator result = applicationToLocators.get(key);
                if (result == null)
                {
-                       IClassCache tmpResult = 
applicationToClassesToGetAndSetters.putIfAbsent(key, result = new 
DefaultClassCache());
+                       IGetAndSetLocator tmpResult = 
applicationToLocators.putIfAbsent(key, result = new CachingGetAndSetLocator(new 
DefaultGetAndSetLocator()));
                        if (tmpResult != null)
                        {
                                result = tmpResult;
@@ -1473,51 +1305,27 @@ public final class PropertyResolver
        }
 
        /**
-        * Clean up cache for this app.
-        *
-        * @param application
+        * Set a locator for the given application.
+        * 
+        * @param application application, may be {@code null}
+        * @param locator locator
         */
-       public static void destroy(Application application)
+       public static void setLocator(final Application application, final 
IGetAndSetLocator locator)
        {
-               applicationToClassesToGetAndSetters.remove(application);
-       }
-
-       /**
-        * Sets the {@link IClassCache} for the given application.
-        *
-        * If the Application is null then it will be the default if no 
application is found. So if you
-        * want to be sure that your {@link IClassCache} is handled in all 
situations then call this
-        * method twice with your implementations. One time for the application 
and the second time with
-        * null.
-        *
-        * @param application
-        *            to use or null if the default must be set.
-        * @param classCache
-        */
-       public static void setClassCache(final Application application, final 
IClassCache classCache)
-       {
-               if (application != null)
+               if (application == null)
                {
-                       applicationToClassesToGetAndSetters.put(application, 
classCache);
+                       applicationToLocators.put(PropertyResolver.class, 
locator);
                }
                else
                {
-                       
applicationToClassesToGetAndSetters.put(PropertyResolver.class, classCache);
+                       applicationToLocators.put(application, locator);
                }
        }
 
        /**
-        * An implementation of the class can be set on the
-        * {@link PropertyResolver#setClassCache(org.apache.wicket.Application, 
org.apache.wicket.core.util.lang.PropertyResolver.IClassCache)}
-        * method for a specific application. This class cache can then be a 
special map with
-        * an eviction policy or do nothing if nothing should be cached for the 
given class.
-        *
-        * For example if you have proxy classes that are constantly created 
you could opt for not
-        * caching those at all or have a special Map implementation that will 
evict that class at a
-        * certain point.
-        *
-        * @author jcompagner
+        * Specify an {@link IGetAndSetLocator} instead.
         */
+       @Deprecated
        public static interface IClassCache
        {
                /**
@@ -1537,20 +1345,279 @@ public final class PropertyResolver
                Map<String, IGetAndSet> get(Class<?> clz);
        }
 
-       private static class DefaultClassCache implements IClassCache
+       /**
+        * A locator of {@link IGetAndSet}s.
+        * 
+        * @param clz owning class
+        * @param exp identifying expression
+        *  
+        * @see https://issues.apache.org/jira/browse/WICKET-5623
+        */
+       public static interface IGetAndSetLocator
        {
-               private final ConcurrentHashMap<Class<?>, Map<String, 
IGetAndSet>> map = Generics.newConcurrentHashMap(16);
+               /**
+                * Get {@link IGetAndSet}.
+                * 
+                * @param clz owning class
+                * @param exp identifying expression
+                * @return get and set
+                */
+               IGetAndSet getAndSet(Class<?> clz, String exp);
+       }
+
+       public static class CachingGetAndSetLocator implements IGetAndSetLocator
+       {
+               private final ConcurrentHashMap<String, IGetAndSet> map = 
Generics.newConcurrentHashMap(16);
+               
+               private IGetAndSetLocator locator;
+
+               public CachingGetAndSetLocator(IGetAndSetLocator locator) {
+                       this.locator = locator;
+               }
 
                @Override
-               public Map<String, IGetAndSet> get(Class<?> clz)
-               {
-                       return map.get(clz);
+               public IGetAndSet getAndSet(Class<?> clz, String exp) {
+                       String key = clz.getName() + "#" + exp;
+                       
+                       IGetAndSet accessor = map.get(key);
+                       if (accessor == null) {
+                               accessor = locator.getAndSet(clz, exp);
+
+                               map.put(key, accessor);
+                       }
+                       
+                       return accessor;
                }
+       }
 
+       /**
+        * Default implementation supporting <em>Java Beans</em> properties, 
maps, lists and method invocations.
+        */
+       public static class DefaultGetAndSetLocator implements IGetAndSetLocator
+       {
                @Override
-               public void put(Class<?> clz, Map<String, IGetAndSet> values)
+               public IGetAndSet getAndSet(Class<?> clz, String exp) {
+                       IGetAndSet getAndSet;
+                       
+                       Method method = null;
+                       Field field;
+                       if (exp.startsWith("["))
+                       {
+                               // if expression begins with [ skip method 
finding and use it as
+                               // a key/index lookup on a map.
+                               exp = exp.substring(1, exp.length() - 1);
+                       }
+                       else if (exp.endsWith("()"))
+                       {
+                               // if expression ends with (), don't test for 
setters just skip
+                               // directly to method finding.
+                               method = findMethod(clz, exp);
+                       }
+                       else
+                       {
+                               method = findGetter(clz, exp);
+                       }
+                       if (method == null)
+                       {
+                               if (List.class.isAssignableFrom(clz))
+                               {
+                                       try
+                                       {
+                                               int index = 
Integer.parseInt(exp);
+                                               getAndSet = new 
ListGetAndSet(index);
+                                       }
+                                       catch (NumberFormatException ex)
+                                       {
+                                               // can't parse the exp as an 
index, maybe the exp was a
+                                               // method.
+                                               method = findMethod(clz, exp);
+                                               if (method != null)
+                                               {
+                                                       getAndSet = new 
MethodGetAndSet(method, MethodGetAndSet.findSetter(
+                                                               method, clz), 
null);
+                                               }
+                                               else
+                                               {
+                                                       field = findField(clz, 
exp);
+                                                       if (field != null)
+                                                       {
+                                                               getAndSet = new 
FieldGetAndSet(field);
+                                                       }
+                                                       else
+                                                       {
+                                                               throw new 
WicketRuntimeException(
+                                                                       "The 
expression '" +
+                                                                               
exp +
+                                                                               
"' is neither an index nor is it a method or field for the list " +
+                                                                               
clz);
+                                                       }
+                                               }
+                                       }
+                               }
+                               else if (Map.class.isAssignableFrom(clz))
+                               {
+                                       getAndSet = new MapGetAndSet(exp);
+                               }
+                               else if (clz.isArray())
+                               {
+                                       try
+                                       {
+                                               int index = 
Integer.parseInt(exp);
+                                               getAndSet = new 
ArrayGetAndSet(clz.getComponentType(), index);
+                                       }
+                                       catch (NumberFormatException ex)
+                                       {
+                                               if (exp.equals("length") || 
exp.equals("size"))
+                                               {
+                                                       getAndSet = new 
ArrayLengthGetAndSet();
+                                               }
+                                               else
+                                               {
+                                                       throw new 
WicketRuntimeException("Can't parse the expression '" + exp +
+                                                               "' as an index 
for an array lookup");
+                                               }
+                                       }
+                               }
+                               else
+                               {
+                                       field = findField(clz, exp);
+                                       if (field == null)
+                                       {
+                                               method = findMethod(clz, exp);
+                                               if (method == null)
+                                               {
+                                                       int index = 
exp.indexOf('.');
+                                                       if (index != -1)
+                                                       {
+                                                               String 
propertyName = exp.substring(0, index);
+                                                               String 
propertyIndex = exp.substring(index + 1);
+                                                               try
+                                                               {
+                                                                       int 
parsedIndex = Integer.parseInt(propertyIndex);
+                                                                       // if 
so then it could be a getPropertyIndex(int)
+                                                                       // and 
setPropertyIndex(int, object)
+                                                                       String 
name = Character.toUpperCase(propertyName.charAt(0)) +
+                                                                               
propertyName.substring(1);
+                                                                       method 
= clz.getMethod(GET + name, new Class[] { int.class });
+                                                                       
getAndSet = new IndexedPropertyGetAndSet(method, parsedIndex);
+                                                               }
+                                                               catch 
(Exception e)
+                                                               {
+                                                                       throw 
new WicketRuntimeException(
+                                                                               
"No get method defined for class: " + clz +
+                                                                               
        " expression: " + propertyName);
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               // We do not 
look for a public FIELD because
+                                                               // that is not 
good programming with beans patterns
+                                                               throw new 
WicketRuntimeException(
+                                                                       "No get 
method defined for class: " + clz + " expression: " +
+                                                                               
exp);
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       getAndSet = new 
MethodGetAndSet(method, MethodGetAndSet.findSetter(
+                                                               method, clz), 
null);
+                                               }
+                                       }
+                                       else
+                                       {
+                                               getAndSet = new 
FieldGetAndSet(field);
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               field = findField(clz, exp);
+                               getAndSet = new MethodGetAndSet(method, 
MethodGetAndSet.findSetter(method, clz),
+                                       field);
+                       }
+                       
+                       return getAndSet;
+               }
+               
+               /**
+                * @param clz
+                * @param expression
+                * @return introspected field
+                */
+               private Field findField(final Class<?> clz, final String 
expression)
+               {
+                       Field field = null;
+                       try
+                       {
+                               field = clz.getField(expression);
+                       }
+                       catch (Exception e)
+                       {
+                               Class<?> tmp = clz;
+                               while (tmp != null && tmp != Object.class)
+                               {
+                                       Field[] fields = 
tmp.getDeclaredFields();
+                                       for (Field aField : fields)
+                                       {
+                                               if 
(aField.getName().equals(expression))
+                                               {
+                                                       
aField.setAccessible(true);
+                                                       return aField;
+                                               }
+                                       }
+                                       tmp = tmp.getSuperclass();
+                               }
+                               log.debug("Cannot find field " + clz + "." + 
expression);
+                       }
+                       return field;
+               }
+
+               /**
+                * @param clz
+                * @param expression
+                * @return The method for the expression null if not found
+                */
+               private Method findGetter(final Class<?> clz, final String 
expression)
                {
-                       map.put(clz, values);
+                       String name = 
Character.toUpperCase(expression.charAt(0)) + expression.substring(1);
+                       Method method = null;
+                       try
+                       {
+                               method = clz.getMethod(GET + name, 
(Class[])null);
+                       }
+                       catch (Exception ignored)
+                       {
+                       }
+                       if (method == null)
+                       {
+                               try
+                               {
+                                       method = clz.getMethod(IS + name, 
(Class[])null);
+                               }
+                               catch (Exception e)
+                               {
+                                       log.debug("Cannot find getter " + clz + 
"." + expression);
+                               }
+                       }
+                       return method;
+               }
+
+               private Method findMethod(final Class<?> clz, String expression)
+               {
+                       if (expression.endsWith("()"))
+                       {
+                               expression = expression.substring(0, 
expression.length() - 2);
+                       }
+                       Method method = null;
+                       try
+                       {
+                               method = clz.getMethod(expression, 
(Class[])null);
+                       }
+                       catch (Exception e)
+                       {
+                               log.debug("Cannot find method " + clz + "." + 
expression);
+                       }
+                       return method;
                }
        }
-}
+}
\ No newline at end of file

Reply via email to