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