You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2010/08/18 02:24:34 UTC
svn commit: r986533 - in /tapestry/tapestry5/trunk:
tapestry-core/src/main/java/org/apache/tapestry5/internal/services/
tapestry-core/src/test/java/org/apache/tapestry5/internal/services/
tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ser...
Author: hlship
Date: Wed Aug 18 00:24:33 2010
New Revision: 986533
URL: http://svn.apache.org/viewvc?rev=986533&view=rev
Log:
TAP5-818: Tapestry should properly support JDK 1.5 Generics when reading and updating properties and property expressions
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java
tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java Wed Aug 18 00:24:33 2010
@@ -14,31 +14,6 @@
package org.apache.tapestry5.internal.services;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.DECIMAL;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.DEREF;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.FALSE;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.IDENTIFIER;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INTEGER;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INVOKE;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.LIST;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NOT;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NULL;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.RANGEOP;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.SAFEDEREF;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.STRING;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.THIS;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.TRUE;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.tree.Tree;
@@ -52,14 +27,7 @@ import org.apache.tapestry5.ioc.internal
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.ioc.services.ClassFab;
-import org.apache.tapestry5.ioc.services.ClassFabUtils;
-import org.apache.tapestry5.ioc.services.ClassFactory;
-import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
-import org.apache.tapestry5.ioc.services.MethodSignature;
-import org.apache.tapestry5.ioc.services.PropertyAccess;
-import org.apache.tapestry5.ioc.services.PropertyAdapter;
-import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.ioc.services.*;
import org.apache.tapestry5.ioc.util.AvailableValues;
import org.apache.tapestry5.ioc.util.BodyBuilder;
import org.apache.tapestry5.ioc.util.UnknownValueException;
@@ -67,6 +35,20 @@ import org.apache.tapestry5.services.Com
import org.apache.tapestry5.services.InvalidationListener;
import org.apache.tapestry5.services.PropertyConduitSource;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.*;
+
public class PropertyConduitSourceImpl implements PropertyConduitSource, InvalidationListener
{
private static final MethodSignature GET_SIGNATURE = new MethodSignature(Object.class, "get", new Class[]
@@ -170,6 +152,12 @@ public class PropertyConduitSourceImpl i
* Returns true if the term is actually a public field.
*/
boolean isField();
+
+ /**
+ * Returns the Field if the term is a public field.
+ */
+ Field getField();
+
}
/**
@@ -196,7 +184,7 @@ public class PropertyConduitSourceImpl i
private class GeneratedTerm
{
- final Class type;
+ final Type type;
final String termReference;
@@ -206,7 +194,7 @@ public class PropertyConduitSourceImpl i
* @param termReference
* name of variable, or a constant value
*/
- private GeneratedTerm(Class type, String termReference)
+ private GeneratedTerm(Type type, String termReference)
{
this.type = type;
this.termReference = termReference;
@@ -391,10 +379,9 @@ public class PropertyConduitSourceImpl i
builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootType));
- builder
- .addln(
- "if (root == null) throw new NullPointerException(\"Root object of property expression '%s' is null.\");",
- expression);
+ builder.addln(
+ "if (root == null) throw new NullPointerException(\"Root object of property expression '%s' is null.\");",
+ expression);
builder.addln("return root;");
@@ -418,7 +405,7 @@ public class PropertyConduitSourceImpl i
navBuilder.begin();
String previousReference = "$1";
- Class activeType = rootType;
+ Type activeType = rootType;
Tree node = tree;
@@ -438,13 +425,14 @@ public class PropertyConduitSourceImpl i
navBuilder.addln("return %s;", previousReference);
navBuilder.end();
+ Class activeClass = GenericsUtils.asClass(activeType);
- MethodSignature sig = new MethodSignature(activeType, "navigate", new Class[]
+ MethodSignature sig = new MethodSignature(activeClass, "navigate", new Class[]
{ rootType }, null);
classFab.addMethod(Modifier.PRIVATE, sig, navBuilder.toString());
- createGetterAndSetter(activeType, sig, node);
+ createGetterAndSetter(activeClass, sig, node);
}
private void createGetterAndSetter(Class activeType, MethodSignature navigateMethod, Tree node)
@@ -646,7 +634,7 @@ public class PropertyConduitSourceImpl i
rootName);
previousReference = generated.termReference;
- activeType = generated.type;
+ activeType = GenericsUtils.asClass(generated.type);
node = node.getChild(1);
@@ -659,7 +647,7 @@ public class PropertyConduitSourceImpl i
NullHandling.IGNORE);
previousReference = generated.termReference;
- activeType = generated.type;
+ activeType = GenericsUtils.asClass(generated.type);
node = null;
@@ -739,8 +727,8 @@ public class PropertyConduitSourceImpl i
private void createNoOpSetter()
{
- createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression, rootType
- .getName());
+ createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression,
+ rootType.getName());
}
private void createGetter(MethodSignature navigateMethod, Tree node, ExpressionTermInfo info)
@@ -749,8 +737,8 @@ public class PropertyConduitSourceImpl i
if (method == null && !info.isField())
{
- createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression, rootType
- .getName());
+ createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression,
+ rootType.getName());
return;
}
@@ -839,7 +827,7 @@ public class PropertyConduitSourceImpl i
GeneratedTerm generatedTerm = subexpression(bodyBuilder, node.getChild(i + childOffset), rootName);
String currentReference = generatedTerm.termReference;
- Class actualType = generatedTerm.type;
+ Class actualType = GenericsUtils.asClass(generatedTerm.type);
Class parameterType = parameterTypes[i];
@@ -849,13 +837,13 @@ public class PropertyConduitSourceImpl i
{
String coerced = nextVariableName(parameterType);
- String call = String.format("coerce(($w) %s, %s)", currentReference, addInjection(Class.class,
- parameterType));
+ String call = String.format("coerce(($w) %s, %s)", currentReference,
+ addInjection(Class.class, parameterType));
String parameterTypeName = ClassFabUtils.toJavaClassName(parameterType);
- bodyBuilder.addln("%s %s = %s;", parameterTypeName, coerced, ClassFabUtils.castReference(call,
- parameterTypeName));
+ bodyBuilder.addln("%s %s = %s;", parameterTypeName, coerced,
+ ClassFabUtils.castReference(call, parameterTypeName));
currentReference = coerced;
}
@@ -882,7 +870,7 @@ public class PropertyConduitSourceImpl i
* Extends the navigate method for a node, which will be a DEREF or
* SAFEDERF.
*/
- private GeneratedTerm processDerefNode(BodyBuilder builder, Class activeType, Tree node,
+ private GeneratedTerm processDerefNode(BodyBuilder builder, Type activeType, Tree node,
String previousVariableName, String rootName)
{
// The first child is the term.
@@ -922,26 +910,39 @@ public class PropertyConduitSourceImpl i
return InternalUtils.lastTerm(type.getName());
}
- private GeneratedTerm addAccessForMember(BodyBuilder builder, Class activeType, Tree term,
+ private GeneratedTerm addAccessForMember(BodyBuilder builder, Type activeType, Tree term,
String previousVariableName, String rootName, NullHandling nullHandling)
{
assertNodeType(term, IDENTIFIER, INVOKE);
-
+ Class activeClass = GenericsUtils.asClass(activeType);
// Get info about this property or method.
- ExpressionTermInfo info = infoForMember(activeType, term);
+ ExpressionTermInfo info = infoForMember(activeClass, term);
Method method = info.getReadMethod();
if (method == null && !info.isField())
throw new RuntimeException(String.format(
- "Property '%s' of class %s is not readable (it has no read accessor method).", info
- .getDescription(), activeType.getName()));
+ "Property '%s' of class %s is not readable (it has no read accessor method).",
+ info.getDescription(), activeClass.getName()));
- // If a primitive type, convert to wrapper type
+ Type termType;
+ /*
+ * It's not possible for the ClassPropertyAdapter to know about the generic info for all the properties of
+ * a class. For instance; if the type arguments of a field are provided by a subclass.
+ */
+ if (info.isField())
+ {
+ termType = GenericsUtils.extractActualType(activeType, info.getField());
+ }
+ else
+ {
+ termType = GenericsUtils.extractActualType(activeType, method);
+ }
+
+ Class termClass = GenericsUtils.asClass(termType);
- Class termType = info.getType();
- final Class wrappedType = ClassFabUtils.getWrapperType(termType);
+ final Class wrappedType = ClassFabUtils.getWrapperType(termClass);
String wrapperTypeName = ClassFabUtils.toJavaClassName(wrappedType);
@@ -955,11 +956,11 @@ public class PropertyConduitSourceImpl i
// Casts are needed for primitives, and for the case where
// generics are involved.
- if (termType.isPrimitive())
+ if (termClass.isPrimitive())
{
builder.add(" ($w) ");
}
- else if (info.isCastRequired())
+ else if (info.isCastRequired() || info.getType() != termClass)
{
builder.add(" (%s) ", wrapperTypeName);
}
@@ -982,7 +983,7 @@ public class PropertyConduitSourceImpl i
break;
}
- return new GeneratedTerm(wrappedType, variableName);
+ return new GeneratedTerm(wrappedType == termClass ? termType : wrappedType, variableName);
}
private void assertNodeType(Tree node, int... expected)
@@ -1005,9 +1006,9 @@ public class PropertyConduitSourceImpl i
for (int i = 0; i < expected.length; i++)
tokenNames.add(PropertyExpressionParser.tokenNames[expected[i]]);
- String message = String.format("Node %s was type %s, but was expected to be (one of) %s.", node
- .toStringTree(), PropertyExpressionParser.tokenNames[node.getType()], InternalUtils
- .joinSorted(tokenNames));
+ String message = String.format("Node %s was type %s, but was expected to be (one of) %s.",
+ node.toStringTree(), PropertyExpressionParser.tokenNames[node.getType()],
+ InternalUtils.joinSorted(tokenNames));
return new RuntimeException(message);
}
@@ -1082,6 +1083,11 @@ public class PropertyConduitSourceImpl i
{
return adapter.isField();
}
+
+ public Field getField()
+ {
+ return adapter.getField();
+ }
};
}
@@ -1142,12 +1148,17 @@ public class PropertyConduitSourceImpl i
{
return false;
}
+
+ public Field getField()
+ {
+ return null;
+ }
};
}
catch (NoSuchMethodException ex)
{
- throw new RuntimeException(String.format("No public method '%s()' in class %s.", methodName, activeType
- .getName()));
+ throw new RuntimeException(String.format("No public method '%s()' in class %s.", methodName,
+ activeType.getName()));
}
}
@@ -1375,8 +1386,8 @@ public class PropertyConduitSourceImpl i
}
catch (Exception ex)
{
- throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression, ex
- .getMessage()), ex);
+ throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression,
+ ex.getMessage()), ex);
}
}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java Wed Aug 18 00:24:33 2010
@@ -22,6 +22,7 @@ import org.apache.tapestry5.integration.
import org.apache.tapestry5.internal.InternalPropertyConduit;
import org.apache.tapestry5.internal.bindings.PropBindingFactoryTest;
import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.internal.util.Holder;
import org.apache.tapestry5.internal.util.IntegerRange;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
import org.apache.tapestry5.ioc.services.ClassFab;
@@ -231,6 +232,228 @@ public class PropertyConduitSourceImplTe
assertSame(conduit.getPropertyType(), String.class);
}
+ public static class One<A, B>
+ {
+ A a;
+ B b;
+
+ public A getA()
+ {
+ return a;
+ }
+
+ public void setA(A a)
+ {
+ this.a = a;
+ }
+
+ public B getB()
+ {
+ return b;
+ }
+
+ public void setB(B b)
+ {
+ this.b = b;
+ }
+ }
+
+ public static class Two<B> extends One<String, B>
+ {
+ String s;
+ B b2;
+
+ public String getS()
+ {
+ return s;
+ }
+
+ public void setS(String s)
+ {
+ this.s = s;
+ }
+
+ public B getB2()
+ {
+ return b2;
+ }
+
+ public void setB2(B b2)
+ {
+ this.b2 = b2;
+ }
+ }
+
+ public static class Three extends Two<Long>
+ {
+ Long x;
+
+ public Long getX()
+ {
+ return x;
+ }
+
+ public void setX(Long x)
+ {
+ this.x = x;
+ }
+ }
+
+ public static class WithParameters<C, T>
+ {
+ private C type1Property; // method access
+ public C type1Field; // field access
+ private T type2Property; // method access
+ public T type2Field; // field access
+
+ private T[] type2ArrayProperty;
+ public T[] type2ArrayField;
+
+ public C getType1Property()
+ {
+ return type1Property;
+ }
+
+ public void setType1Property(C type1Property)
+ {
+ this.type1Property = type1Property;
+ }
+
+ public T getType2Property()
+ {
+ return type2Property;
+ }
+
+ public void setType2Property(T type2Property)
+ {
+ this.type2Property = type2Property;
+ }
+
+ public T[] getType2ArrayProperty()
+ {
+ return type2ArrayProperty;
+ }
+
+ public void setType2ArrayProperty(T[] type2ArrayProperty)
+ {
+ this.type2ArrayProperty = type2ArrayProperty;
+ }
+ }
+
+ public static class RealizedParameters extends WithParameters<Holder<SimpleBean>, Long>
+ {
+ }
+
+ public static class WithGenericProperties
+ {
+ public Holder<SimpleBean> holder = new Holder<SimpleBean>();
+ }
+
+ public static interface GenericInterface<A, B>
+ {
+ A genericA();
+
+ B genericB();
+ }
+
+ public static class WithRealizedGenericInterface implements GenericInterface<String, Long>
+ {
+ String a;
+ Long b;
+
+ public String genericA()
+ {
+ return a;
+ }
+
+ public Long genericB()
+ {
+ return b;
+ }
+ }
+
+ @Test
+ public void generic_properties()
+ {
+ final WithGenericProperties bean = new WithGenericProperties();
+ final String first = "John";
+ final String last = "Doe";
+ final SimpleBean simple = new SimpleBean();
+ simple.setLastName(last);
+ simple.setAge(2);
+ simple.setFirstName(first);
+ bean.holder.put(simple);
+
+ PropertyConduit conduit = source.create(WithGenericProperties.class, "holder.get().firstName");
+ assertSame(conduit.get(bean), first);
+ }
+
+ @Test
+ public void generic_parameterized_base_with_properties()
+ {
+ final String first = "John";
+ final String last = "Doe";
+ final SimpleBean simple = new SimpleBean();
+ simple.setAge(2);
+ simple.setFirstName(first);
+ simple.setLastName(last);
+
+ final RealizedParameters bean = new RealizedParameters();
+ final Holder<SimpleBean> holder = new Holder<SimpleBean>();
+ holder.put(simple);
+ bean.setType1Property(holder);
+ bean.setType2Property(1234L);
+ bean.type1Field = holder;
+ bean.type2Field = 5678L;
+ bean.type2ArrayField = new Long[]
+ { 123L, 456L };
+
+ PropertyConduit conduit = source.create(RealizedParameters.class, "type1property.get().firstName");
+ assertSame(conduit.get(bean), first);
+ conduit.set(bean, "Change");
+ assertSame(conduit.get(bean), "Change");
+ conduit.set(bean, first);
+
+ conduit = source.create(RealizedParameters.class, "type1field.get().firstName");
+ assertSame(conduit.get(bean), first);
+
+ conduit = source.create(RealizedParameters.class, "type2field");
+ assertEquals(conduit.get(bean), bean.type2Field);
+
+ conduit = source.create(RealizedParameters.class, "type2property");
+ assertEquals(conduit.get(bean), bean.getType2Property());
+
+ conduit = source.create(RealizedParameters.class, "type2ArrayField");
+ assertEquals(conduit.get(bean), bean.type2ArrayField);
+
+ }
+
+ @Test
+ public void generic_interface()
+ {
+ final WithRealizedGenericInterface bean = new WithRealizedGenericInterface();
+ bean.a = "Hello";
+ bean.b = 12345L;
+
+ PropertyConduit conduit = source.create(WithRealizedGenericInterface.class, "genericA()");
+ assertSame(conduit.get(bean), "Hello");
+ conduit = source.create(WithRealizedGenericInterface.class, "genericB()");
+ assertEquals(conduit.get(bean), 12345L);
+ }
+
+ @Test
+ public void generic_nested()
+ {
+ Three bean = new Three();
+ bean.setA("hello");
+ bean.setB(123L);
+ bean.setB2(1235L);
+ bean.setX(54321L);
+
+ PropertyConduit conduit = source.create(Three.class, "a");
+ assertSame(conduit.get(bean), "hello");
+ }
+
@Test
public void null_root_object()
{
@@ -380,21 +603,23 @@ public class PropertyConduitSourceImplTe
assertListsEquals(l, new Long(1), new Double(2.0), "Bart");
}
-
+
@Test
public void arrays_as_method_argument()
{
PropertyConduit conduit = source.create(EchoBean.class, "echoArray(storedArray)");
EchoBean bean = new EchoBean();
- bean.setStoredArray(new Number[][]{ new Integer[] {1, 2}, new Double[] {3.0, 4.0}});
+ bean.setStoredArray(new Number[][]
+ { new Integer[]
+ { 1, 2 }, new Double[]
+ { 3.0, 4.0 } });
Number[][] array = (Number[][]) conduit.get(bean);
assertArraysEqual(array[0], 1, 2);
assertArraysEqual(array[1], 3.0, 4.0);
}
-
@Test
public void not_operator()
@@ -485,7 +710,9 @@ public class PropertyConduitSourceImplTe
assertEquals(falseConduit.get(bedrock), "Barney");
}
- /** TAP5-747 */
+ /**
+ * TAP5-747
+ */
@Test
public void dereference_result_of_method_invocation()
{
Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java Wed Aug 18 00:24:33 2010
@@ -230,6 +230,11 @@ public class PropertyAdapterImpl impleme
return field != null;
}
+ public Field getField()
+ {
+ return field;
+ }
+
public Class getDeclaringClass()
{
return declaringClass;
Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java Wed Aug 18 00:24:33 2010
@@ -14,11 +14,8 @@
package org.apache.tapestry5.ioc.internal.util;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
+import java.lang.reflect.*;
+import java.util.LinkedList;
/**
* Static methods related to the use of JDK 1.5 generics.
@@ -40,7 +37,9 @@ public class GenericsUtils
*/
public static Class extractGenericReturnType(Class containingClassType, Method method)
{
- return extractGenericType(containingClassType, method.getReturnType(), method.getGenericReturnType());
+ return extractActualTypeAsClass(containingClassType, method.getDeclaringClass(), method.getGenericReturnType(),
+ method.getReturnType());
+
}
/**
@@ -57,57 +56,164 @@ public class GenericsUtils
*/
public static Class extractGenericFieldType(Class containingClassType, Field field)
{
- return extractGenericType(containingClassType, field.getType(), field.getGenericType());
+ return extractActualTypeAsClass(containingClassType, field.getDeclaringClass(), field.getGenericType(),
+ field.getType());
}
- private static Class extractGenericType(Class containingClassType, Type defaultType, Type genericType)
+ /**
+ * @param owner
+ * - type that owns the field
+ * @param field
+ * - field that is generic
+ * @return Type
+ */
+ public static Type extractActualType(Type owner, Field field)
{
- // We can only handle the case where you "lock down" a generic type to a specific type.
+ return extractActualType(owner, field.getDeclaringClass(), field.getGenericType(), field.getType());
+ }
- if (genericType instanceof TypeVariable)
- {
+ /**
+ * @param owner
+ * - type that owns the field
+ * @param method
+ * - method with generic return type
+ * @return Type
+ */
+ public static Type extractActualType(Type owner, Method method)
+ {
+ return extractActualType(owner, method.getDeclaringClass(), method.getGenericReturnType(),
+ method.getReturnType());
+ }
- // An odd name for the method that gives you access to the type parameters
- // used when implementing this class. When you say Bean<String>, the first
- // type variable of the generic superclass is class String.
+ /**
+ * Extracts the Class used as a type argument when declaring a
+ *
+ * @param containingType
+ * - the type which the method is being/will be called on
+ * @param declaringClass
+ * - the class that the method is actually declared in (base class)
+ * @param type
+ * - the generic type from the field/method being inspected
+ * @param defaultType
+ * - the default type to return if no parameterized type can be found
+ * @return a Class or ParameterizedType that the field/method can reliably be cast to.
+ * @since 5.2.?
+ */
+ private static Type extractActualType(final Type containingType, final Class declaringClass, final Type type,
+ final Class defaultType)
+ {
- Type superType = containingClassType.getGenericSuperclass();
+ if (type instanceof ParameterizedType) { return type; }
+ if (!(type instanceof TypeVariable))
+ return defaultType;
+
+ TypeVariable typeVariable = (TypeVariable) type;
+
+ if (!declaringClass.isAssignableFrom(asClass(containingType))) { throw new RuntimeException(String.format(
+ "%s must be a subclass of %s", declaringClass.getName(), asClass(containingType).getName())); }
+
+ // First, check to see if we are operating on a parameterized type already.
+ Type extractedType = type;
+ if (containingType instanceof ParameterizedType)
+ {
+ final int i = getTypeVariableIndex(asClass(containingType), typeVariable);
+ extractedType = ((ParameterizedType) containingType).getActualTypeArguments()[i];
+ if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+ }
- if (superType instanceof ParameterizedType)
- {
- ParameterizedType superPType = (ParameterizedType) superType;
+ // Somewhere between declaringClass and containingClass are the parameter type arguments
+ // We are going to drop down the containingClassType until we find the declaring class.
+ // The class that extends declaringClass will define the ParameterizedType or a new TypeVariable
+
+ final LinkedList<Type> classStack = new LinkedList<Type>();
+ Type cur = containingType;
+ while (cur != null && !asClass(cur).equals(declaringClass))
+ {
+ classStack.add(0, cur);
+ cur = asClass(cur).getSuperclass();
+ }
- TypeVariable tv = (TypeVariable) genericType;
+ int typeArgumentIndex = getTypeVariableIndex(declaringClass, (TypeVariable) extractedType);
- String name = tv.getName();
+ for (Type descendant : classStack)
+ {
+ final Class descendantClass = asClass(descendant);
+ final ParameterizedType parameterizedType = (ParameterizedType) descendantClass.getGenericSuperclass();
- TypeVariable[] typeVariables = tv.getGenericDeclaration().getTypeParameters();
+ extractedType = parameterizedType.getActualTypeArguments()[typeArgumentIndex];
- for (int i = 0; i < typeVariables.length; i++)
- {
- TypeVariable stv = typeVariables[i];
+ if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
- // We're trying to match the name of the type variable that is used as the return type
- // of the method. With that name, we find the corresponding index in the
- // type declarations. With the index, we check superPType for the Class instance
- // that defines it. Generics has lots of other options that we simply can't handle.
+ if (extractedType instanceof TypeVariable)
+ {
+ typeArgumentIndex = getTypeVariableIndex(descendantClass, (TypeVariable) extractedType);
+ }
+ else
+ {
+ // I don't know what else this could be?
+ break;
+ }
+ }
- if (stv.getName().equals(name))
- {
- Type actualType = superPType.getActualTypeArguments()[i];
+ return defaultType;
+ }
- if (actualType instanceof Class)
- return (Class) actualType;
+ /**
+ * Convenience method to get actual type as raw class.
+ *
+ * @param containingClassType
+ * @param declaringClass
+ * @param type
+ * @param defaultType
+ * @return
+ * @see #extractActualType(Type, Class, Type, Class)
+ */
+ private static Class extractActualTypeAsClass(Class containingClassType, Class<?> declaringClass, Type type,
+ Class<?> defaultType)
+ {
+ final Type actualType = extractActualType(containingClassType, declaringClass, type, defaultType);
- break;
- }
- }
+ return asClass(actualType);
+ }
+ public static Class asClass(Type actualType)
+ {
+ if (actualType instanceof ParameterizedType)
+ {
+ final Type rawType = ((ParameterizedType) actualType).getRawType();
+ if (rawType instanceof Class)
+ {
+ // The sun implementation returns Class<?>, but there is room in the interface for it to be
+ // something else so to be safe ignore whatever "something else" might be.
+ // TODO: consider logging for that day when "something else" causes some confusion
+ return (Class) rawType;
}
}
- return (Class) defaultType;
+ return (Class) actualType;
+ }
- // P.S. I wrote this and I barely understand it. Fortunately, I have tests ...
+ /**
+ * Find the index of the TypeVariable in the classes parameters. The offset can be used on a subclass to find
+ * the actual type.
+ *
+ * @param clazz
+ * - the parameterized class
+ * @param typeVar
+ * - the type variable in question.
+ * @return the index of the type variable in the classes type parameters.
+ */
+ private static int getTypeVariableIndex(Class clazz, TypeVariable typeVar)
+ {
+ // the label from the class (the T in List<T>, the K and V in Map<K,V>, etc)
+ String typeVarName = typeVar.getName();
+ int typeArgumentIndex = 0;
+ final TypeVariable[] typeParameters = clazz.getTypeParameters();
+ for (; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
+ {
+ if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
+ break;
+ }
+ return typeArgumentIndex;
}
}
Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java Wed Aug 18 00:24:33 2010
@@ -16,13 +16,14 @@ package org.apache.tapestry5.ioc.service
import org.apache.tapestry5.ioc.AnnotationProvider;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Provides access to a single property within a class. Acts as an {@link org.apache.tapestry5.ioc.AnnotationProvider};
* when searching for annotations, the read method (if present) is checked first, followed by the write method, followed
* by the underlying field (when the property name matches the field name).
- * <p>
+ * <p/>
* Starting in release 5.2, this property may actually be a public field.
*
* @see org.apache.tapestry5.ioc.services.ClassPropertyAdapter
@@ -110,6 +111,13 @@ public interface PropertyAdapter extends
boolean isField();
/**
+ * Returns the field if the property is a public field or null if the property is accessed via the read method.
+ *
+ * @since 5.2
+ */
+ Field getField();
+
+ /**
* The class in which the property (or public field) is defined.
*
* @since 5.2