WICKET-5623 let IPropertyLocator return null; added test for custom properties
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 c3e3025..28023e1 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
@@ -35,9 +35,6 @@
import org.slf4j.LoggerFactory;
/**
- * NOTE: THIS CLASS IS NOT PART OF THE WICKET PUBLIC API, DO NOT USE IT UNLESS YOU KNOW WHAT YOU ARE
- * DOING.
- * <p>
* This class parses expressions to lookup or set a value on the object that is given. <br/>
* The supported expressions are:
* <dl>
@@ -88,7 +85,7 @@
private final static int CREATE_NEW_VALUE = 1;
private final static int RESOLVE_CLASS = 2;
- private final static ConcurrentHashMap<Object, IGetAndSetLocator> applicationToLocators = Generics.newConcurrentHashMap(2);
+ private final static ConcurrentHashMap<Object, IPropertyLocator> applicationToLocators = Generics.newConcurrentHashMap(2);
private static final String GET = "get";
private static final String IS = "is";
@@ -389,9 +386,15 @@
private static IGetAndSet getGetAndSet(String exp, final Class<?> clz)
{
- IGetAndSetLocator locator = getLocator();
+ IPropertyLocator locator = getLocator();
- return locator.getAndSet(clz, exp);
+ IGetAndSet getAndSet = locator.get(clz, exp);
+ if (getAndSet == null) {
+ throw new WicketRuntimeException(
+ "Property could not be resolved for class: " + clz + " expression: " + exp);
+ }
+
+ return getAndSet;
}
/**
@@ -473,7 +476,7 @@
/**
* @author jcompagner
*/
- public static interface IGetAndSet
+ public interface IGetAndSet
{
/**
* @param object
@@ -520,7 +523,7 @@
public Method getSetter();
}
- private static abstract class AbstractGetAndSet implements IGetAndSet
+ public static abstract class AbstractGetAndSet implements IGetAndSet
{
/**
* {@inheritDoc}
@@ -1238,10 +1241,10 @@
}
/**
- * Sets the {@link IGetAndSetLocator} for the given application.
+ * Sets the {@link IPropertyLocator} 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
+ * want to be sure that your {@link IPropertyLocator} is handled in all situations then call this
* method twice with your implementations. One time for the application and the second time with
* null.
*
@@ -1252,12 +1255,12 @@
@Deprecated
public static void setClassCache(final Application application, final IClassCache classCache)
{
- setLocator(application, new IGetAndSetLocator() {
+ setLocator(application, new IPropertyLocator() {
private DefaultGetAndSetLocator locator = new DefaultGetAndSetLocator();
@Override
- public IGetAndSet getAndSet(Class<?> clz, String name) {
+ public IGetAndSet get(Class<?> clz, String name) {
Map<String, IGetAndSet> map = classCache.get(clz);
if (map == null) {
map = new ConcurrentHashMap<String, IGetAndSet>(8);
@@ -1266,7 +1269,7 @@
IGetAndSet getAndSetter = map.get(name);
if (getAndSetter == null) {
- getAndSetter = locator.getAndSet(clz, name);
+ getAndSetter = locator.get(clz, name);
map.put(name, getAndSetter);
}
@@ -1276,12 +1279,12 @@
}
/**
- * Get the current {@link IGetAndSetLocator}.
+ * Get the current {@link IPropertyLocator}.
*
* @return locator for the current {@link Application} or a general one if no current application is present
* @see Application#get()
*/
- public static IGetAndSetLocator getLocator()
+ public static IPropertyLocator getLocator()
{
Object key;
if (Application.exists())
@@ -1292,10 +1295,10 @@
{
key = PropertyResolver.class;
}
- IGetAndSetLocator result = applicationToLocators.get(key);
+ IPropertyLocator result = applicationToLocators.get(key);
if (result == null)
{
- IGetAndSetLocator tmpResult = applicationToLocators.putIfAbsent(key, result = new CachingGetAndSetLocator(new DefaultGetAndSetLocator()));
+ IPropertyLocator tmpResult = applicationToLocators.putIfAbsent(key, result = new CachingGetAndSetLocator(new DefaultGetAndSetLocator()));
if (tmpResult != null)
{
result = tmpResult;
@@ -1310,7 +1313,7 @@
* @param application application, may be {@code null}
* @param locator locator
*/
- public static void setLocator(final Application application, final IGetAndSetLocator locator)
+ public static void setLocator(final Application application, final IPropertyLocator locator)
{
if (application == null)
{
@@ -1323,7 +1326,7 @@
}
/**
- * Specify an {@link IGetAndSetLocator} instead.
+ * Specify an {@link IPropertyLocator} instead.
*/
@Deprecated
public static interface IClassCache
@@ -1346,58 +1349,81 @@
}
/**
- * A locator of {@link IGetAndSet}s.
+ * A locator of properties.
*
- * @param clz owning class
- * @param exp identifying expression
- *
* @see https://issues.apache.org/jira/browse/WICKET-5623
*/
- public static interface IGetAndSetLocator
+ public static interface IPropertyLocator
{
/**
- * Get {@link IGetAndSet}.
+ * Get {@link IGetAndSet} for a property.
*
* @param clz owning class
- * @param exp identifying expression
- * @return get and set
+ * @param exp identifying the property
+ * @return getAndSet or {@code null} if non located
*/
- IGetAndSet getAndSet(Class<?> clz, String exp);
+ IGetAndSet get(Class<?> clz, String exp);
}
- public static class CachingGetAndSetLocator implements IGetAndSetLocator
+ public static class CachingGetAndSetLocator implements IPropertyLocator
{
private final ConcurrentHashMap<String, IGetAndSet> map = Generics.newConcurrentHashMap(16);
- private IGetAndSetLocator locator;
+ /**
+ * Special token to put into the cache representing no located {@link IGetAndSet}.
+ */
+ private IGetAndSet NONE = new AbstractGetAndSet() {
- public CachingGetAndSetLocator(IGetAndSetLocator locator) {
+ @Override
+ public Object getValue(Object object) {
+ return null;
+ }
+
+ @Override
+ public Object newValue(Object object) {
+ return null;
+ }
+
+ @Override
+ public void setValue(Object object, Object value, PropertyResolverConverter converter) {
+ }
+ };
+
+ private IPropertyLocator locator;
+
+ public CachingGetAndSetLocator(IPropertyLocator locator) {
this.locator = locator;
}
@Override
- public IGetAndSet getAndSet(Class<?> clz, String exp) {
+ public IGetAndSet get(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);
+ IGetAndSet located = map.get(key);
+ if (located == null) {
+ located = locator.get(clz, exp);
+ if (located == null) {
+ located = NONE;
+ }
+ map.put(key, located);
}
- return accessor;
+ if (located == NONE) {
+ located = null;
+ }
+
+ return located;
}
}
/**
* Default implementation supporting <em>Java Beans</em> properties, maps, lists and method invocations.
*/
- public static class DefaultGetAndSetLocator implements IGetAndSetLocator
+ public static class DefaultGetAndSetLocator implements IPropertyLocator
{
@Override
- public IGetAndSet getAndSet(Class<?> clz, String exp) {
- IGetAndSet getAndSet;
+ public IGetAndSet get(Class<?> clz, String exp) {
+ IGetAndSet getAndSet = null;
Method method = null;
Field field;
@@ -1508,14 +1534,6 @@
" 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
{
diff --git a/wicket-core/src/test/java/org/apache/wicket/util/lang/Document.java b/wicket-core/src/test/java/org/apache/wicket/util/lang/Document.java
new file mode 100644
index 0000000..11c05fd
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/util/lang/Document.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.lang;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Document {
+
+ private Map<String, Object> values = new HashMap<>();
+
+ private String type;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getProperty(String name) {
+ return (T) values.get(name);
+ }
+
+ public <T> void setProperty(String name, T t) {
+ values.put(name, t);
+ }
+}
\ No newline at end of file
diff --git a/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java b/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
index 5265354..e76e1d6 100644
--- a/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
@@ -31,6 +31,11 @@
import org.apache.wicket.IConverterLocator;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.core.util.lang.PropertyResolver;
+import org.apache.wicket.core.util.lang.PropertyResolver.AbstractGetAndSet;
+import org.apache.wicket.core.util.lang.PropertyResolver.CachingGetAndSetLocator;
+import org.apache.wicket.core.util.lang.PropertyResolver.DefaultGetAndSetLocator;
+import org.apache.wicket.core.util.lang.PropertyResolver.IGetAndSet;
+import org.apache.wicket.core.util.lang.PropertyResolver.IPropertyLocator;
import org.apache.wicket.core.util.lang.PropertyResolverConverter;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
@@ -46,6 +51,7 @@
*/
public class PropertyResolverTest extends WicketTestCase
{
+
private static final PropertyResolverConverter CONVERTER = new PropertyResolverConverter(
new ConverterLocator(), Locale.US);
@@ -599,8 +605,8 @@
private static final long serialVersionUID = 1L;
/**
- *
*/
+ @SuppressWarnings("unused")
public String testValue = "vector";
}
@@ -629,6 +635,7 @@
{
private int value;
+ @SuppressWarnings("unused")
public String getValue()
{
return String.valueOf(value);
@@ -739,4 +746,64 @@
Object actual = converter.convert(date, Long.class);
assertEquals(date.getTime(), actual);
}
-}
+
+ /**
+ * WICKET-5623 custom properties
+ */
+ @Test
+ public void custom() {
+ Document document = new Document();
+ document.setType("type");
+ document.setProperty("string", "string");
+
+ Document nestedCustom = new Document();
+ nestedCustom.setProperty("string", "string2");
+ document.setProperty("nested", nestedCustom);
+
+ PropertyResolver.setLocator(tester.getApplication(), new CachingGetAndSetLocator(new CustomGetAndSetLocator()));
+
+ assertEquals("type", PropertyResolver.getValue("type", document));
+ assertEquals("string", PropertyResolver.getValue("string", document));
+ assertEquals("string2", PropertyResolver.getValue("nested.string", document));
+ }
+
+ class CustomGetAndSetLocator implements IPropertyLocator {
+
+ private IPropertyLocator locator = new DefaultGetAndSetLocator();
+
+ @Override
+ public IGetAndSet get(Class<?> clz, String exp) {
+ // first try default properties
+ IGetAndSet getAndSet = locator.get(clz, exp);
+ if (getAndSet == null && Document.class.isAssignableFrom(clz)) {
+ // fall back to document properties
+ getAndSet = new DocumentPropertyGetAndSet(exp);
+ }
+ return getAndSet;
+ }
+
+ public class DocumentPropertyGetAndSet extends AbstractGetAndSet {
+
+ private String name;
+
+ public DocumentPropertyGetAndSet(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Object getValue(Object object) {
+ return ((Document) object).getProperty(name);
+ }
+
+ @Override
+ public Object newValue(Object object) {
+ return new Document();
+ }
+
+ @Override
+ public void setValue(Object object, Object value, PropertyResolverConverter converter) {
+ ((Document) object).setProperty(name, value);
+ }
+ }
+ }
+}
\ No newline at end of file