You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2016/07/19 22:26:10 UTC

[3/3] wicket git commit: WICKET-5623 extensible PropertyResolver

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 <sv...@apache.org>
Authored: Thu Jul 14 00:16:48 2016 +0200
Committer: Sven Meier <sv...@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