Revision: 353
Author: tfenne
Date: 2006-07-31 20:08:42 -0700 (Mon, 31 Jul 2006)
ViewCVS: http://svn.sourceforge.net/stripes/?rev=353&view=rev
Log Message:
-----------
Change to the PropertyExpressionEvaluation to support the use of object
instances when inferring type information if no luck is had inferring it from
the declarations.
Modified Paths:
--------------
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
trunk/tests/src/net/sourceforge/stripes/test/TestBean.java
Modified:
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
2006-07-31 22:49:45 UTC (rev 352)
+++
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
2006-08-01 03:08:42 UTC (rev 353)
@@ -42,7 +42,6 @@
private PropertyExpression expression;
private Object bean;
private NodeEvaluation root, leaf;
- private boolean typeInformationValid;
/**
* Constructs a new PropertyExpressionEvaluation for the expression and
bean supplied.
@@ -145,33 +144,144 @@
if (type instanceof Class) {
Class clazz = (Class) type;
String property = current.getNode().getStringValue();
- PropertyDescriptor pd =
ReflectUtil.getPropertyDescriptor(clazz, property);
+ type = getBeanPropertyType(clazz, property);
- if (pd != null) {
- if (pd.getReadMethod() != null) {
- type = pd.getReadMethod().getGenericReturnType();
- }
- else {
- type =
pd.getWriteMethod().getGenericParameterTypes()[0];
- }
+ if (type != null) {
current.setValueType(type);
current.setType(NodeType.BeanProperty);
}
else {
- Field field = ReflectUtil.getField(clazz, property);
- if (field == null) { break; }
- else { type = field.getGenericType(); }
- current.setValueType(type);
- current.setType(NodeType.BeanProperty);
+ type = getTypeViaInstances(current);
+ if (type == null) {
+
+ }
+
}
}
}
+ }
- // Finally figure out what to store on the leaf node!
- this.typeInformationValid = (this.leaf.getValueType() != null);
+ /**
+ * Fetches the type of a property with the given name on the Class of the
specified type.
+ * Uses the methods first to fetch the generic type if a
PropertyDescriptor can be found,
+ * otherwise looks for a public field and returns its generic type.
+ *
+ * @param beanClass the class of the JavaBean containing the property
+ * @param property the name of the property
+ * @return the Type if it can be determined, or null otherwise
+ */
+ protected Type getBeanPropertyType(Class beanClass, String property) {
+ PropertyDescriptor pd = ReflectUtil.getPropertyDescriptor(beanClass,
property);
+ if (pd != null) {
+ if (pd.getReadMethod() != null) {
+ return pd.getReadMethod().getGenericReturnType();
+ }
+ else {
+ return pd.getWriteMethod().getGenericParameterTypes()[0];
+ }
+ }
+ else {
+ Field field = ReflectUtil.getField(beanClass, property);
+ if (field == null) {
+ return null;
+ }
+ else {
+ return field.getGenericType();
+ }
+ }
}
/**
+ * <p>Determines the type of the supplied node and sets appropriate
information on the node.
+ * The type is discovered by fetching (and instantiating if necessary) all
prior values
+ * in the expression to determine the actual type of the prior node. The
prior node is
+ * then examined to determine the type of the node provided.</p>
+ *
+ * <p>After this method executes either 1) all necessary type information
will be set on the
+ * node and the appropriate type object returned or 2) an exception will
be thrown.</p>
+ *
+ * @param end the node to instantate up to and determine the type of
+ * @return the Type of the node if possible
+ * @throws NoSuchPropertyException if the previous node is a JavaBean
(i.e. non-collection)
+ * node and does not contain a property with the corresponding name
+ * @throws EvaluationException if the previous node is a List or Map and
does not contain
+ * enough information to determine the type
+ */
+ protected Type getTypeViaInstances(NodeEvaluation end)
+ throws EvaluationException, NoSuchPropertyException {
+ Object previous;
+ Object value = this.bean;
+
+ // First loop through and get to the pre-cursor node using the type
info we have
+ for (NodeEvaluation node = this.root; node != end; node =
node.getNext()) {
+ PropertyAccessor accessor = node.getType().getPropertyAccessor();
+ previous = value;
+ value = accessor.getValue(node, previous);
+
+ if (value == null) {
+ value = getDefaultValue(node);
+ }
+ }
+
+ // Then determine how to fish for the next property in line
+ previous = value;
+ if (value instanceof Map) {
+ value = ((Map) value).get(end.getNode().getTypedValue());
+ if (value != null) {
+ end.setType(NodeType.MapEntry);
+ end.setValueType(value.getClass());
+ end.setKeyType(end.getNode().getTypedValue().getClass());
+ return value.getClass();
+ }
+ else {
+ throw new EvaluationException("Not enough type information
available to " +
+ "evaluate expression. Expression: '" + expression + "'.
Type information ran " +
+ "out at node '" + end.getNode().getStringValue() + "',
which represents a Map " +
+ "entry. Please ensure that either the getter for the Map
contains appropriate " +
+ "generic type information or that it contains a value with
the key type " +
+ end.getNode().getTypedValue().getClass().getName() + " and
value " +
+ end.getNode().getStringValue());
+ }
+ }
+ else if (value instanceof List) {
+ List list = (List) value;
+ if (end.getNode().getTypedValue() instanceof Integer) {
+ Integer index = (Integer) end.getNode().getTypedValue();
+ if (index < list.size()) {
+ value = list.get(index);
+ if (value != null) {
+ end.setType(NodeType.ListEntry);
+ end.setValueType(value.getClass());
+ end.setKeyType(Integer.class);
+ return value.getClass();
+ }
+ }
+ }
+
+ throw new EvaluationException("Not enough type information
available to " +
+ "evaluate expression. Expression: '" + expression + "'. Type
information ran " +
+ "out at node '" + end.getNode().getStringValue() + "', which
represents a List " +
+ "entry. Please ensure that either the getter for the List
contains appropriate " +
+ "generic type information or that the index is numeric and a
value exists at " +
+ "the supplied index (" + end.getNode().getStringValue() +
").");
+ }
+ else {
+ Type type = getBeanPropertyType(value.getClass(),
end.getNode().getStringValue());
+ if (type != null) {
+ end.setType(NodeType.BeanProperty);
+ end.setValueType(type);
+ return type;
+ }
+ else {
+ throw new NoSuchPropertyException("Bean class " +
previous.getClass().getName() +
+ " does not contain a property called '" +
end.getNode().getStringValue() +
+ "'. As a result the following expression could not be
evaluated: " +
+ this.expression);
+ }
+ }
+ }
+
+ /**
* Attempts to convert the [EMAIL PROTECTED] Type} object into a Class
object. Currently will extract the
* raw type from a [EMAIL PROTECTED] ParameterizedType} and the
appropriate bound from a
* [EMAIL PROTECTED] WildcardType}. If the result after these operations
is a Class object it will
@@ -250,12 +360,7 @@
* @return the Class of object that can be set/get with this evaluation or
null
*/
public Class getType() {
- if (this.typeInformationValid) {
- return convertToClass(this.leaf.getValueType(), this.leaf);
- }
- else {
- return null;
- }
+ return convertToClass(this.leaf.getValueType(), this.leaf);
}
/**
@@ -272,35 +377,30 @@
* point at a non scalar property
*/
public Class getScalarType() {
- if (this.typeInformationValid) {
- Type type = this.leaf.getValueType();
- Class clazz = convertToClass(type, this.leaf);
+ Type type = this.leaf.getValueType();
+ Class clazz = convertToClass(type, this.leaf);
- if (clazz.isArray()) {
- return clazz.getComponentType();
+ if (clazz.isArray()) {
+ return clazz.getComponentType();
+ }
+ else if (Collection.class.isAssignableFrom(clazz)) {
+ if (type instanceof ParameterizedType) {
+ return convertToClass(((ParameterizedType)
type).getActualTypeArguments()[0], this.leaf);
}
- else if (Collection.class.isAssignableFrom(clazz)) {
- if (type instanceof ParameterizedType) {
- return convertToClass(((ParameterizedType)
type).getActualTypeArguments()[0], this.leaf);
- }
- else {
- return String.class;
- }
+ else {
+ return String.class;
}
- else if (Map.class.isAssignableFrom(clazz)) {
- if (type instanceof ParameterizedType) {
- return convertToClass(((ParameterizedType)
type).getActualTypeArguments()[1], this.leaf);
- }
- else {
- return String.class;
- }
+ }
+ else if (Map.class.isAssignableFrom(clazz)) {
+ if (type instanceof ParameterizedType) {
+ return convertToClass(((ParameterizedType)
type).getActualTypeArguments()[1], this.leaf);
}
else {
- return clazz;
+ return String.class;
}
}
else {
- return null;
+ return clazz;
}
}
Modified:
trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
2006-08-01 03:08:42 UTC (rev 353)
@@ -243,4 +243,16 @@
Assert.assertEquals(colors[1], TestActionBean.Color.Green);
Assert.assertEquals(colors[2], TestActionBean.Color.Blue);
}
+
+ @Test(groups="fast")
+ public void testBindingToSubclassOfDeclaredType() throws Exception {
+ MockRoundtrip trip = getRoundtrip();
+ trip.addParameter("item.id", "1000000");
+ trip.execute();
+
+ TestActionBean bean = trip.getActionBean(TestActionBean.class);
+ TestActionBean.PropertyLess item = bean.getItem();
+ Assert.assertEquals(item.getClass(), TestActionBean.Item.class);
+ Assert.assertEquals( ((TestActionBean.Item) item).getId(), new
Long(1000000l));
+ }
}
Modified:
trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
2006-08-01 03:08:42 UTC (rev 353)
@@ -1,18 +1,21 @@
package net.sourceforge.stripes.controller;
import net.sourceforge.stripes.StripesTestFixture;
-import net.sourceforge.stripes.test.TestBean;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.action.Before;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.mock.MockRoundtrip;
import net.sourceforge.stripes.mock.MockServletContext;
+import net.sourceforge.stripes.test.TestBean;
+import net.sourceforge.stripes.test.TestEnum;
import org.testng.Assert;
import org.testng.annotations.Test;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
import java.util.Map;
-import java.util.Date;
-import java.util.Calendar;
/**
* Tests all reasonable variations of binding involving Maps. String keys,
numeric keys,
@@ -60,6 +63,11 @@
public Map<Date, Date> getMapDateDate() { return mapDateDate; }
public void setMapDateDate(Map<Date, Date> mapDateDate) { this.mapDateDate
= mapDateDate; }
+ /** A map completely lacking in type information!!. */
+ private Map typelessMap;
+ public Map getTypelessMap() { return typelessMap; }
+ public void setTypelessMap(Map typelessMap) { this.typelessMap =
typelessMap; }
+
/** Helper method to create a roundtrip with the TestActionBean class. */
protected MockRoundtrip getRoundtrip() {
MockServletContext context = StripesTestFixture.getServletContext();
@@ -230,4 +238,26 @@
MapBindingTests bean = trip.getActionBean(MapBindingTests.class);
Assert.assertNotNull(bean.getMapDateDate().get(key));
}
+
+ @Before(LifecycleStage.BindingAndValidation)
+ public void populateTypelessMap() {
+ this.typelessMap = new HashMap();
+ this.typelessMap.put(1, new TestBean());
+ this.typelessMap.put(2l, new TestBean());
+ this.typelessMap.put("foo", new TestBean());
+ }
+
+ @Test(groups="fast")
+ public void bindThroughTypelessMap() throws Exception {
+ MockRoundtrip trip = getRoundtrip();
+ trip.addParameter("typelessMap[1].longProperty", "1234");
+ trip.addParameter("typelessMap[2l].nestedBean.longProperty", "4321");
+ trip.addParameter("typelessMap['foo'].enumProperty", "Sixth");
+ trip.execute();
+
+ MapBindingTests bean = trip.getActionBean(MapBindingTests.class);
+ Assert.assertEquals( ((TestBean)
bean.getTypelessMap().get(1)).getLongProperty(), new Long(1234));
+ Assert.assertEquals( ((TestBean)
bean.getTypelessMap().get(2l)).getNestedBean().getLongProperty(), new
Long(4321));
+ Assert.assertEquals( ((TestBean)
bean.getTypelessMap().get("foo")).getEnumProperty(), TestEnum.Sixth);
+ }
}
Modified: trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
2006-08-01 03:08:42 UTC (rev 353)
@@ -21,6 +21,14 @@
public class TestActionBean implements ActionBean {
public enum Color {Red, Green, Blue, Yellow, Orange, Black, White }
+ /** Pair of static classes used to check type finding via instances intead
of type inference. */
+ public static class PropertyLess { }
+ public static class Item extends PropertyLess {
+ private Long id;
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ }
+
private ActionBeanContext context;
private List<Long> listOfLongs;
private Set<String> setOfStrings;
@@ -35,6 +43,7 @@
private String setOnlyString;
public Long publicLong;
public Color[] colors;
+ private PropertyLess item = new Item();
/** A pretty ordinary list of longs, to test lists of primitive/simply
objects. */
public List<Long> getListOfLongs() { return listOfLongs; }
@@ -85,6 +94,10 @@
public Color[] getColors() { return colors; }
public void setColors(Color[] colors) { this.colors = colors; }
+ /** Return type is a property-less class, but returns an instance of a
subclass with an 'id' property. */
+ public PropertyLess getItem() { return item; }
+ public void setItem(PropertyLess item) { this.item = item; }
+
///////////////////////////////////////////////////////////////////////////
// Dummied up ActionBean methods that aren't really used for much.
///////////////////////////////////////////////////////////////////////////
Modified: trunk/tests/src/net/sourceforge/stripes/test/TestBean.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/test/TestBean.java 2006-07-31
22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/test/TestBean.java 2006-08-01
03:08:42 UTC (rev 353)
@@ -10,7 +10,8 @@
*
* @author Tim Fennell
*/
-public class TestBean {
+public class
+ TestBean {
private String stringProperty;
private int intProperty;
private Long longProperty;
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development