You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jo...@apache.org on 2010/12/26 20:41:50 UTC
svn commit: r1052930 - in /tapestry/tapestry5/trunk:
tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/
tapestry-core/src/main/java/org/apache/tapestry5/internal/services/
tapestry-core/src/main/java/org/apache/tapestry5/internal/tran...
Author: joshcanfield
Date: Sun Dec 26 19:41:50 2010
New Revision: 1052930
URL: http://svn.apache.org/viewvc?rev=1052930&view=rev
Log:
Added support for generic component properties.
Modified the Loop component to support generic source and value parameters.
Public interface changes:
Added constructor to TransformMethodSignature to accept the methods generic signature.
Added method to TransformField to get the fields generic signature
Added:
tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java Sun Dec 26 19:41:50 2010
@@ -41,7 +41,7 @@ import java.util.List;
*/
@SupportsInformalParameters
@Events(EventConstants.SYNCHRONIZE_VALUES)
-public class Loop
+public class Loop<T>
{
/**
* Setup command for non-volatile rendering.
@@ -209,7 +209,7 @@ public class Loop
* container whose name matches the Loop cmponent's id.
*/
@Parameter(required = true, principal = true, autoconnect = true)
- private Iterable<?> source;
+ private Iterable<T> source;
/**
* Optional value converter; if provided (or defaulted) and inside a form and not volatile, then each iterated value
@@ -217,7 +217,7 @@ public class Loop
* the value parameter.
*/
@Parameter
- private ValueEncoder<Object> encoder;
+ private ValueEncoder<T> encoder;
/**
* If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off. Defaults to false,
@@ -253,7 +253,7 @@ public class Loop
* The current value, set before the component renders its body.
*/
@Parameter(principal = true)
- private Object value;
+ private T value;
/**
* The index into the source items.
@@ -267,7 +267,7 @@ public class Loop
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private Block empty;
- private Iterator<?> iterator;
+ private Iterator<T> iterator;
@Environmental
private Heartbeat heartbeat;
@@ -286,7 +286,7 @@ public class Loop
* Objects that have been recovered via {@link org.apache.tapestry5.ValueEncoder#toValue(String)} during the
* processing of the loop. These are sent to the container via an event.
*/
- private List<Object> synchonizedValues;
+ private List<T> synchonizedValues;
LoopFormState defaultFormState()
@@ -439,7 +439,7 @@ public class Loop
/**
* Restores state previously stored by the Loop into a Form.
*/
- private void restoreState(Object storedValue)
+ private void restoreState(T storedValue)
{
value = storedValue;
@@ -454,7 +454,7 @@ public class Loop
// We assume that if an encoder is available when we rendered, that one will be available
// when the form is submitted.
- Object restoredValue = encoder.toValue(clientValue);
+ T restoredValue = encoder.toValue(clientValue);
restoreState(restoredValue);
@@ -475,17 +475,17 @@ public class Loop
// For testing:
- int getIndex()
+ public int getIndex()
{
return index;
}
- Object getValue()
+ public T getValue()
{
return value;
}
- void setSource(Iterable<?> source)
+ void setSource(Iterable<T> source)
{
this.source = source;
}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java Sun Dec 26 19:41:50 2010
@@ -24,6 +24,9 @@ import java.util.Map;
import java.util.Set;
import javassist.*;
+import javassist.bytecode.FieldInfo;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.SignatureAttribute;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
@@ -325,7 +328,7 @@ public final class InternalClassTransfor
* The static method takes the same parameters as the main method, but takes
* an instance object first. Invoking the static method turns into an invocation
* of the proper method of the instance object.
- *
+ *
* @return the name of the created static access method
*/
private String createStaticAccessMethodForNonPublicMethod()
@@ -423,7 +426,7 @@ public final class InternalClassTransfor
private final CtClass fieldType;
- private final String name, type;
+ private final String name, type, signature;
private boolean added;
@@ -446,6 +449,12 @@ public final class InternalClassTransfor
try
{
fieldType = field.getType();
+
+ final FieldInfo fieldInfo = field.getFieldInfo2();
+ SignatureAttribute sig = (SignatureAttribute) fieldInfo.getAttribute(SignatureAttribute.tag);
+ // This contains the generic type information
+ signature = sig != null ? sig.getSignature() : null;
+
type = fieldType.getName();
}
catch (NotFoundException ex)
@@ -486,6 +495,11 @@ public final class InternalClassTransfor
return type;
}
+ public String getSignature()
+ {
+ return signature;
+ }
+
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
{
failIfFrozen();
@@ -954,7 +968,7 @@ public final class InternalClassTransfor
* Searches an array of objects (that are really annotations instances) to find one that is of
* the correct type,
* which is returned.
- *
+ *
* @param <T>
* @param annotationClass
* the annotation to search for
@@ -1080,7 +1094,7 @@ public final class InternalClassTransfor
* degenerate cases that are not covered properly: these are related to base interfaces that may
* be implemented by
* base classes.
- *
+ *
* @param ctInterface
* @throws NotFoundException
*/
@@ -1243,7 +1257,12 @@ public final class InternalClassTransfor
method.setBody(methodBody);
method.setExceptionTypes(exceptions);
-
+ String sig = signature.getSignature();
+ if (sig != null)
+ {
+ final MethodInfo methodInfo = method.getMethodInfo();
+ methodInfo.addAttribute(new SignatureAttribute(methodInfo.getConstPool(), sig));
+ }
ctClass.addMethod(method);
result = recordMethod(method, addAsNew);
@@ -1594,7 +1613,10 @@ public final class InternalClassTransfor
String[] parameters = toTypeNames(method.getParameterTypes());
String[] exceptions = toTypeNames(method.getExceptionTypes());
- return new TransformMethodSignature(method.getModifiers(), type, method.getName(), parameters, exceptions);
+ final SignatureAttribute attribute = (SignatureAttribute)method.getMethodInfo().getAttribute(SignatureAttribute.tag);
+ String sig = attribute != null ? attribute.getSignature() : null;
+
+ return new TransformMethodSignature(method.getModifiers(), type, sig, method.getName(), parameters, exceptions);
}
catch (NotFoundException ex)
{
@@ -1809,7 +1831,7 @@ public final class InternalClassTransfor
/**
* Adds a parameter to the constructor for the class; the parameter is used to initialize the
* value for a field.
- *
+ *
* @param fieldName
* name of field to inject
* @param fieldType
@@ -2261,7 +2283,7 @@ public final class InternalClassTransfor
/**
* Adds a new constructor argument to the transformed constructor.
- *
+ *
* @param parameterType
* type of parameter
* @param value
@@ -2372,9 +2394,9 @@ public final class InternalClassTransfor
public void advise(ComponentMethodInvocation invocation)
{
// Invoke the super-class implementation first.
-
+
invocation.proceed();
-
+
ComponentEvent event = (ComponentEvent) invocation.getParameter(0);
if (!event.isAborted() && event.matches(eventType, "", minContextValues))
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=1052930&r1=1052929&r2=1052930&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 Sun Dec 26 19:41:50 2010
@@ -129,7 +129,7 @@ public class PropertyConduitSourceImpl i
/**
* The return type of the method, or the type of the property.
*/
- Class getType();
+ Type getType();
/**
* True if an explicit cast to the return type is needed (typically
@@ -453,7 +453,7 @@ public class PropertyConduitSourceImpl i
createSetter(navigateMethod, info);
createGetter(navigateMethod, node, info);
- conduitPropertyType = info.getType();
+ conduitPropertyType = GenericsUtils.asClass(info.getType());
conduitPropertyName = info.getPropertyName();
annotationProvider = info;
@@ -564,7 +564,7 @@ public class PropertyConduitSourceImpl i
/**
* Evalutates the node as a sub expression, storing the result into a
* new variable, whose name is returned.
- *
+ *
* @param builder
* to receive generated code
* @param node
@@ -648,7 +648,8 @@ public class PropertyConduitSourceImpl i
previousReference = generated.termReference;
activeType = GenericsUtils.asClass(generated.type);
-
+ if ( activeType.isPrimitive() )
+ activeType = ClassFabUtils.getWrapperType(activeType);
node = null;
break;
@@ -707,7 +708,7 @@ public class PropertyConduitSourceImpl i
builder.addln("if (target == null) return;");
- String propertyTypeName = ClassFabUtils.toJavaClassName(info.getType());
+ String propertyTypeName = ClassFabUtils.toJavaClassName(GenericsUtils.asClass(info.getType()));
String reference = ClassFabUtils.castReference("$2", propertyTypeName);
@@ -766,7 +767,7 @@ public class PropertyConduitSourceImpl i
/**
* Creates a method invocation call for the given node (an INVOKE node).
- *
+ *
* @param bodyBuilder
* may receive new code to define variables for some
* sub-expressions
@@ -790,7 +791,7 @@ public class PropertyConduitSourceImpl i
/**
* Creates a method invocation call for the given node
- *
+ *
* @param bodyBuilder
* may receive new code to define variables for some
* sub-expressions
@@ -914,31 +915,19 @@ public class PropertyConduitSourceImpl i
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(activeClass, term);
+ // Get info about this property or method.
- Method method = info.getReadMethod();
+ final ExpressionTermInfo info = infoForMember(activeType, term);
+ final Method method = info.getReadMethod();
+ final Class activeClass = GenericsUtils.asClass(activeType);
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(), activeClass.getName()));
- 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);
- }
+ Type termType = info.getType();
Class termClass = GenericsUtils.asClass(termType);
@@ -960,7 +949,7 @@ public class PropertyConduitSourceImpl i
{
builder.add(" ($w) ");
}
- else if (info.isCastRequired() || info.getType() != termClass)
+ else if (info.isCastRequired() )
{
builder.add(" (%s) ", wrapperTypeName);
}
@@ -983,7 +972,7 @@ public class PropertyConduitSourceImpl i
break;
}
- return new GeneratedTerm(wrappedType == termClass ? termType : wrappedType, variableName);
+ return new GeneratedTerm(termType, variableName);
}
private void assertNodeType(Tree node, int... expected)
@@ -1013,7 +1002,7 @@ public class PropertyConduitSourceImpl i
return new RuntimeException(message);
}
- private ExpressionTermInfo infoForMember(Class activeType, Tree node)
+ private ExpressionTermInfo infoForMember(Type activeType, Tree node)
{
if (node.getType() == INVOKE)
return infoForInvokeNode(activeType, node);
@@ -1021,27 +1010,41 @@ public class PropertyConduitSourceImpl i
return infoForPropertyOrPublicField(activeType, node);
}
- private ExpressionTermInfo infoForPropertyOrPublicField(Class activeType, Tree node)
+ private ExpressionTermInfo infoForPropertyOrPublicField(Type activeType, Tree node)
{
String propertyName = node.getText();
- ClassPropertyAdapter classAdapter = access.getAdapter(activeType);
+ final Class activeClass = GenericsUtils.asClass(activeType);
+ ClassPropertyAdapter classAdapter = access.getAdapter(activeClass);
final PropertyAdapter adapter = classAdapter.getPropertyAdapter(propertyName);
if (adapter == null)
{
- List<String> names = classAdapter.getPropertyNames();
-
+ final List<String> names = classAdapter.getPropertyNames();
+ final String className = activeClass.getName();
throw new UnknownValueException(String.format(
- "Class %s does not contain a property (or public field) named '%s'.", activeType.getName(),
- propertyName), new AvailableValues("Properties (and public fields)", names));
+ "Class %s does not contain a property (or public field) named '%s'.", className, propertyName),
+ new AvailableValues("Properties (and public fields)", names));
}
- return createExpressionTermInfoForProperty(adapter);
- }
+ final Type type;
+ final boolean isCastRequired;
+ if (adapter.getField() != null)
+ {
+ type = GenericsUtils.extractActualType(activeType, adapter.getField());
+ isCastRequired = !type.equals(adapter.getField().getType());
+ }
+ else if (adapter.getReadMethod() != null)
+ {
+ type = GenericsUtils.extractActualType(activeType, adapter.getReadMethod());
+ isCastRequired = !type.equals(adapter.getReadMethod().getReturnType());
+ }
+ else
+ {
+ type = adapter.getType();
+ isCastRequired = adapter.isCastRequired();
+ }
- private ExpressionTermInfo createExpressionTermInfoForProperty(final PropertyAdapter adapter)
- {
return new ExpressionTermInfo()
{
public Method getReadMethod()
@@ -1054,14 +1057,14 @@ public class PropertyConduitSourceImpl i
return adapter.getWriteMethod();
}
- public Class getType()
+ public Type getType()
{
- return adapter.getType();
+ return type;
}
public boolean isCastRequired()
{
- return adapter.isCastRequired();
+ return isCastRequired;
}
public String getDescription()
@@ -1091,21 +1094,22 @@ public class PropertyConduitSourceImpl i
};
}
- private ExpressionTermInfo infoForInvokeNode(Class activeType, Tree node)
+ private ExpressionTermInfo infoForInvokeNode(Type activeType, Tree node)
{
String methodName = node.getChild(0).getText();
int parameterCount = node.getChildCount() - 1;
+ final Class activeClass = GenericsUtils.asClass(activeType);
try
{
- final Method method = findMethod(activeType, methodName, parameterCount);
+ final Method method = findMethod(activeClass, methodName, parameterCount);
if (method.getReturnType().equals(void.class))
- throw new RuntimeException(String.format("Method %s.%s() returns void.", activeType.getName(),
+ throw new RuntimeException(String.format("Method %s.%s() returns void.", activeClass.getName(),
methodName));
- final Class genericType = GenericsUtils.extractGenericReturnType(activeType, method);
+ final Type genericType = GenericsUtils.extractActualType(activeType, method);
return new ExpressionTermInfo()
{
@@ -1119,7 +1123,7 @@ public class PropertyConduitSourceImpl i
return null;
}
- public Class getType()
+ public Type getType()
{
return genericType;
}
@@ -1157,8 +1161,8 @@ public class PropertyConduitSourceImpl i
}
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, activeClass.getName()));
}
}
@@ -1246,7 +1250,7 @@ public class PropertyConduitSourceImpl i
* conduits for the same
* rootClass/expression, and it will get sorted out when the conduit is
* stored into the cache.
- *
+ *
* @param rootClass
* class of root object for expression evaluation
* @param expression
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java Sun Dec 26 19:41:50 2010
@@ -31,7 +31,7 @@ import org.apache.tapestry5.services.Tra
* Provides the getter and setter methods. The methods are added as "existing", meaning that field access to them will
* be transformed as necessary by other annotations. This worker needs to be scheduled before any worker that might
* delete a field.
- *
+ *
* @see org.apache.tapestry5.annotations.Property
*/
public class PropertyWorker implements ComponentClassTransformWorker
@@ -86,8 +86,10 @@ public class PropertyWorker implements C
private void addGetter(ClassTransformation transformation, TransformField field, String propertyName)
{
- TransformMethodSignature getter = new TransformMethodSignature(Modifier.PUBLIC, field.getType(), "get"
- + propertyName, null, null);
+ final String methodSignature = field.getSignature() != null ? "()" + field.getSignature() : null;
+ TransformMethodSignature getter =
+ new TransformMethodSignature(Modifier.PUBLIC, field.getType(), methodSignature,
+ "get" + propertyName, null, null);
ensureNotOverride(transformation, getter);
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java Sun Dec 26 19:41:50 2010
@@ -23,7 +23,7 @@ import org.apache.tapestry5.ioc.services
* A field defined by (or created within) a {@link ClassTransformation},
* allowing the details of the field to be
* accessed or modified.
- *
+ *
* @since 5.2.0
*/
public interface TransformField extends AnnotationProvider, Comparable<TransformField>
@@ -41,11 +41,19 @@ public interface TransformField extends
String getType();
/**
+ * Returns the fields fully qualified generic type, or null if not defined.
+ * (in Java source syntax, i.e., "()Ljava/util/List<Ljava/lang/String;>;"
+ *
+ * @since 5.3.0
+ */
+ String getSignature();
+
+ /**
* Claims the field so as to ensure that only a single annotation is applied to any single field.
* When a transformation occurs (driven by a field annotation), the field is claimed (using the
* annotation object as the tag). If a field has multiple conflicting annotations, this will be discovered when
* the code attempts to claim the field a second time.
- *
+ *
* @param tag
* a non-null object that represents why the field is being tagged (this is typically
* a specific annotation on the field)
@@ -56,7 +64,7 @@ public interface TransformField extends
/**
* Replaces read and write field access with a conduit. The field will be deleted.
- *
+ *
* @param conduitProvider
* provides the actual conduit at class instantiation time
*/
@@ -64,7 +72,7 @@ public interface TransformField extends
/**
* Replaces read and write field access with a conduit. The field itself will be deleted.
- *
+ *
* @param conduitField
* identifies the field containing (via injection) an instance of {@link FieldValueConduit}
*/
@@ -73,7 +81,7 @@ public interface TransformField extends
/**
* Replaces read and write field access with a conduit. A new field is created for the conduit instance,
* and the original field is deleted.
- *
+ *
* @param conduit
* used to replace read and write access to the field
*/
@@ -81,7 +89,7 @@ public interface TransformField extends
/**
* Returns the modifiers for the field.
- *
+ *
* @see Field#getModifiers()
*/
int getModifiers();
@@ -89,7 +97,7 @@ public interface TransformField extends
/**
* Converts this field into a read only field whose value is the provided
* value. This is used when converting an existing field into a read-only injected value.
- *
+ *
* @param value
* the value provided by the field
*/
@@ -99,7 +107,7 @@ public interface TransformField extends
* Like {@link #inject(Object)}, except that the value to be injected is obtained
* from a {@link ComponentValueProvider}. It is assumed that the provider will return an object
* assignable to the field.
- *
+ *
* @param <T>
* type of field
* @param provider
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java Sun Dec 26 19:41:50 2010
@@ -33,7 +33,7 @@ public class TransformMethodSignature im
private final int modifiers;
- private final String returnType, methodName;
+ private final String returnType, methodName, signature;
private final String[] parameterTypes, exceptionTypes;
@@ -47,12 +47,22 @@ public class TransformMethodSignature im
}
public TransformMethodSignature(int modifiers, String type, String name, String[] parameterTypes,
+ String[] exceptionTypes)
+ {
+ this(modifiers, type, null, name, parameterTypes, exceptionTypes);
+ }
+
+ /**
+ * @since 5.3.0
+ */
+ public TransformMethodSignature(int modifiers, String type, String signature, String name, String[] parameterTypes,
String[] exceptionTypes)
{
assert InternalUtils.isNonBlank(name);
assert InternalUtils.isNonBlank(type);
this.modifiers = modifiers;
+ this.signature = signature;
returnType = type;
methodName = name;
@@ -89,7 +99,7 @@ public class TransformMethodSignature im
/**
* Returns the set of modifier flags for this method.
- *
+ *
* @see java.lang.reflect.Modifier
*/
public int getModifiers()
@@ -97,6 +107,10 @@ public class TransformMethodSignature im
return modifiers;
}
+ public String getSignature() {
+ return signature;
+ }
+
/**
* Returns an array of the type name for each parameter. Calling code should not modify the
* array.
@@ -237,7 +251,7 @@ public class TransformMethodSignature im
* Returns a shortened form of the string representation of the method. It lists just the name
* of the method and the
* types of any parameters, omitting return type, exceptions and modifiers.
- *
+ *
* @return
*/
public String getMediumDescription()
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml Sun Dec 26 19:41:50 2010
@@ -0,0 +1,20 @@
+<html t:type="border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
+
+<h1>Generic Loop Demo</h1>
+
+<h2>Integer Loop</h2>
+<t:loop t:id="integerLoop" source="integerSource">
+ <div id="int_${integerLoop.index}">${integerLoop.value}</div>
+</t:loop>
+
+<h2>Person Loop</h2>
+<t:loop t:id="personLoop" source="personSource">
+ <div id="person_${personLoop.index}">${personLoop.value.name}</div>
+</t:loop>
+
+<h2>BaseClass parameterized type loop</h2>
+<t:loop t:id="inheritedLoop" source="inheritedLoopSource">
+ <div id="inherited_${inheritedLoop.index}"
+ >${inheritedLoop.value.type}:${inheritedLoop.value.age}:${inheritedLoop.value.name}</div>
+</t:loop>
+</html>
\ No newline at end of file
Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java Sun Dec 26 19:41:50 2010
@@ -44,6 +44,29 @@ public class LoopTests extends TapestryC
test_loop_inside_form("ToDo List (Volatile)");
}
+ @Test
+ public void generic_loop()
+ {
+ clickThru("Generic Loop Demo");
+ String[] strings = {"1", "3", "5", "7", "11"};
+ for ( int i = 0; i< strings.length; ++i)
+ {
+ assertText("int_" + i, strings[i]);
+ }
+
+ strings = new String[] {"John Doe", "Jane Dover", "James Jackson"};
+ for ( int i = 0; i< strings.length; ++i)
+ {
+ assertText("person_" + i, strings[i]);
+ }
+
+ strings = new String[] {"DOG:6:Dakota", "CAT:3:Jill", "CAT:3:Jack"};
+ for (int i = 0; i< strings.length; ++i)
+ {
+ assertText("inherited_" + i, strings[i]);
+ }
+ }
+
private void test_loop_inside_form(String linkLabel)
{
clickThru(linkLabel);
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java Sun Dec 26 19:41:50 2010
@@ -0,0 +1,25 @@
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.BeginRender;
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.corelib.components.Loop;
+
+import java.util.List;
+
+public abstract class BaseGenericLoopDemo<T> {
+
+ @Property
+ @Component
+ private Loop<T> inheritedLoop;
+
+ @Property
+ private List<T> inheritedLoopSource;
+
+ @BeginRender
+ void setupLoop() {
+ inheritedLoopSource = initInheritedLoop();
+ }
+
+ abstract List<T> initInheritedLoop();
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java Sun Dec 26 19:41:50 2010
@@ -0,0 +1,73 @@
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.corelib.components.Loop;
+import org.apache.tapestry5.integration.app1.data.Person;
+import org.apache.tapestry5.integration.app1.data.Pet;
+import org.apache.tapestry5.integration.app1.data.PetType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GenericLoopDemo extends BaseGenericLoopDemo<Pet> {
+
+ @Property
+ @Component(parameters = {"source=integerSource"})
+ private Loop<Integer> integerLoop;
+
+ @Property
+ private List<Integer> integerSource;
+
+ @Property
+ @Component(parameters = {"source=personSource"})
+ private Loop<Person> personLoop;
+
+ @Property
+ private List<Person> personSource;
+
+ void beginRender() {
+ integerSource = Arrays.asList(1, 3, 5, 7, 11);
+ personSource = new ArrayList<Person>();
+
+ Person person = new Person();
+ person.setAge(25);
+ person.setName("John Doe");
+ personSource.add(person);
+
+ person = new Person();
+ person.setAge(53);
+ person.setName("Jane Dover");
+ personSource.add(person);
+
+ person = new Person();
+ person.setAge(13);
+ person.setName("James Jackson");
+ personSource.add(person);
+ }
+
+ @Override
+ List<Pet> initInheritedLoop() {
+ final ArrayList<Pet> list = new ArrayList<Pet>();
+ Pet pet = new Pet();
+ pet.setAge(6);
+ pet.setName("Dakota");
+ pet.setType(PetType.DOG);
+ list.add(pet);
+
+ pet = new Pet();
+ pet.setAge(3);
+ pet.setName("Jill");
+ pet.setType(PetType.CAT);
+ list.add(pet);
+
+ pet = new Pet();
+ pet.setAge(3);
+ pet.setName("Jack");
+ pet.setType(PetType.CAT);
+ list.add(pet);
+ return list;
+ }
+
+}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Sun Dec 26 19:41:50 2010
@@ -151,6 +151,8 @@ public class Index
new Item("EmptyLoopDemo", "Empty Loop Demo", "Use of empty parameter with the Loop component."),
+ new Item("GenericLoopDemo", "Generic Loop Demo", "Use of generic parameters with the Loop component."),
+
new Item("BlankPasswordDemo", "Blank Password Demo",
"Show that a blank value in a PasswordField does not update the server side value."),
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=1052930&r1=1052929&r2=1052930&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 Sun Dec 26 19:41:50 2010
@@ -24,196 +24,586 @@ import java.util.LinkedList;
public class GenericsUtils
{
/**
- * Analyzes the method (often defined in a base class) in the context of a particular concrete implementation of the
- * class to establish the generic type of a property. This works when the property type is defined as a class
- * generic parameter.
- *
- * @param containingClassType
- * class containing the method, used to reason about generics
- * @param method
- * method (possibly from a base class of type) to extract
- * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
- * Object)
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost. If you want
+ * to preserve the type parameters of the return type consider using
+ * {@link #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)}.
+ *
+ * @param containingClass class which either contains or inherited the method
+ * @param method method from which to extract the return type
+ * @return the class represented by the methods generic return type, resolved based on the context .
+ * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ * @see #asClass(java.lang.reflect.Type)
*/
- public static Class extractGenericReturnType(Class containingClassType, Method method)
+ public static Class<?> extractGenericReturnType(Class<?> containingClass, Method method)
{
- return extractActualTypeAsClass(containingClassType, method.getDeclaringClass(), method.getGenericReturnType(),
- method.getReturnType());
-
+ return asClass(resolve(method.getGenericReturnType(), containingClass));
}
+
/**
- * Analyzes the field in the context of a particular concrete implementation of the class to establish
- * the generic type of a (public) field. This works when the field type is defined as a class
- * generic parameter.
- *
- * @param containingClassType
- * class containing the method, used to reason about generics
- * @param field
- * public field to extract type from
- * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
- * @since 5.2.0
+ * Analyzes the field in the context of containingClass and returns the Class that is represented by
+ * the field's generic type. Any parameter information in the generic type is lost, if you want
+ * to preserve the type parameters of the return type consider using
+ * {@link #getTypeVariableIndex(java.lang.reflect.TypeVariable)}.
+ *
+ * @param containingClass class which either contains or inherited the field
+ * @param field field from which to extract the type
+ * @return the class represented by the field's generic type, resolved based on the containingClass.
+ * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Field)
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ * @see #asClass(java.lang.reflect.Type)
*/
- public static Class extractGenericFieldType(Class containingClassType, Field field)
+ public static Class extractGenericFieldType(Class containingClass, Field field)
{
- return extractActualTypeAsClass(containingClassType, field.getDeclaringClass(), field.getGenericType(),
- field.getType());
+ return asClass(resolve(field.getGenericType(), containingClass));
}
/**
- * @param owner
- * - type that owns the field
- * @param field
- * - field that is generic
- * @return Type
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost.
+ *
+ * @param containingType Type which is/represents the class that either contains or inherited the method
+ * @param method method from which to extract the generic return type
+ * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
*/
- public static Type extractActualType(Type owner, Field field)
+ public static Type extractActualType(Type containingType, Method method)
{
- return extractActualType(owner, field.getDeclaringClass(), field.getGenericType(), field.getType());
+ return resolve(method.getGenericReturnType(), containingType);
}
/**
- * @param owner
- * - type that owns the field
- * @param method
- * - method with generic return type
- * @return Type
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost.
+ *
+ * @param containingType Type which is/represents the class that either contains or inherited the field
+ * @param field field from which to extract the generic return type
+ * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
*/
- public static Type extractActualType(Type owner, Method method)
+ public static Type extractActualType(Type containingType, Field field)
{
- return extractActualType(owner, method.getDeclaringClass(), method.getGenericReturnType(),
- method.getReturnType());
+ return resolve(field.getGenericType(), containingType);
}
/**
- * 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)
+ * Resolves the type parameter based on the context of the containingType.
+ * <p/>
+ * {@link java.lang.reflect.TypeVariable} will be unwrapped to the type argument resolved form the class
+ * hierarchy. This may be something other than a simple Class if the type argument is a ParameterizedType for
+ * instance (e.g. List<E>; List<Map<Long, String>>, E would be returned as a ParameterizedType with the raw
+ * type Map and type arguments Long and String.
+ * <p/>
+ *
* @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.
+ * the generic type (ParameterizedType, GenericArrayType, WildcardType, TypeVariable) to be resolved
+ * @param containingType
+ * the type which his
+ * @return
+ * the type resolved to the best of our ability.
* @since 5.2.?
*/
- private static Type extractActualType(final Type containingType, final Class declaringClass, final Type type,
- final Class defaultType)
+ public static Type resolve(final Type type, final Type containingType)
{
+ // The type isn't generic. (String, Long, etc)
+ if (type instanceof Class)
+ return type;
+
+ // List<T>, List<String>, List<T extends Number>
+ if (type instanceof ParameterizedType)
+ return resolve((ParameterizedType) type, containingType);
+
+ // T[], List<String>[], List<T>[]
+ if (type instanceof GenericArrayType)
+ return resolve((GenericArrayType) type, containingType);
+
+ // List<? extends T>, List<? extends Object & Comparable & Serializable>
+ if (type instanceof WildcardType)
+ return resolve((WildcardType) type, containingType);
+
+ // T
+ if (type instanceof TypeVariable)
+ return resolve((TypeVariable) type, containingType);
- if (type instanceof ParameterizedType) { return type; }
- if (!(type instanceof TypeVariable))
- return defaultType;
+ // I'm leaning towards an exception here.
+ return type;
+ }
- 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())); }
+ /**
+ * Determines if the suspected super type is assignable from the suspected sub type.
+ *
+ * @param suspectedSuperType
+ * e.g. GenericDAO<Pet, String>
+ * @param suspectedSubType
+ * e.g. PetDAO extends GenericDAO<Pet,String>
+ * @return
+ * true if (sourceType)targetClass is a valid cast
+ */
+ public static boolean isAssignableFrom(Type suspectedSuperType, Type suspectedSubType)
+ {
+ final Class suspectedSuperClass = asClass(suspectedSuperType);
+ final Class suspectedSubClass = asClass(suspectedSubType);
- // First, check to see if we are operating on a parameterized type already.
- Type extractedType = type;
- if (containingType instanceof ParameterizedType)
+ // The raw types need to be compatible.
+ if (!suspectedSuperClass.isAssignableFrom(suspectedSubClass))
{
- final int i = getTypeVariableIndex(asClass(containingType), typeVariable);
- extractedType = ((ParameterizedType) containingType).getActualTypeArguments()[i];
- if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+ return false;
}
- // 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
+ // From this point we know that the raw types are assignable.
+ // We need to figure out what the generic parameters in the targetClass are
+ // as they pertain to the sourceType.
- final LinkedList<Type> classStack = new LinkedList<Type>();
- Type cur = containingType;
- while (cur != null && !asClass(cur).equals(declaringClass))
+ if (suspectedSuperType instanceof WildcardType)
{
- classStack.add(0, cur);
- cur = asClass(cur).getSuperclass();
+ // ? extends Number
+ // needs to match all the bounds (there will only be upper bounds or lower bounds
+ for (Type t : ((WildcardType) suspectedSuperType).getUpperBounds())
+ {
+ if (!isAssignableFrom(t, suspectedSubType)) return false;
+ }
+ for (Type t : ((WildcardType) suspectedSuperType).getLowerBounds())
+ {
+ if (!isAssignableFrom(suspectedSubType, t)) return false;
+ }
+ return true;
}
- int typeArgumentIndex = getTypeVariableIndex(declaringClass, (TypeVariable) extractedType);
+ Type curType = suspectedSubType;
+ Class curClass;
- for (Type descendant : classStack)
+ while (curType != null && !curType.equals(Object.class))
{
- final Class descendantClass = asClass(descendant);
- final ParameterizedType parameterizedType = (ParameterizedType) descendantClass.getGenericSuperclass();
+ curClass = asClass(curType);
- extractedType = parameterizedType.getActualTypeArguments()[typeArgumentIndex];
+ if (curClass.equals(suspectedSuperClass))
+ {
+ final Type resolved = resolve(curType, suspectedSubType);
+
+ if (suspectedSuperType instanceof Class)
+ {
+ if ( resolved instanceof Class )
+ return suspectedSuperType.equals(resolved);
+
+ // They may represent the same class, but the suspectedSuperType is not parameterized. The parameter
+ // types default to Object so they must be a match.
+ // e.g. Pair p = new StringLongPair();
+ // Pair p = new Pair<? extends Number, String>
+
+ return true;
+ }
+
+ if (suspectedSuperType instanceof ParameterizedType)
+ {
+ if (resolved instanceof ParameterizedType)
+ {
+ final Type[] type1Arguments = ((ParameterizedType) suspectedSuperType).getActualTypeArguments();
+ final Type[] type2Arguments = ((ParameterizedType) resolved).getActualTypeArguments();
+ if (type1Arguments.length != type2Arguments.length) return false;
+
+ for (int i = 0; i < type1Arguments.length; ++i)
+ {
+ if (!isAssignableFrom(type1Arguments[i], type2Arguments[i])) return false;
+ }
+ return true;
+ }
+ }
+ else if (suspectedSuperType instanceof GenericArrayType)
+ {
+ if (resolved instanceof GenericArrayType)
+ {
+ return isAssignableFrom(
+ ((GenericArrayType) suspectedSuperType).getGenericComponentType(),
+ ((GenericArrayType) resolved).getGenericComponentType()
+ );
+ }
+ }
- if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+ return false;
+ }
- if (extractedType instanceof TypeVariable)
+ final Type[] types = curClass.getGenericInterfaces();
+ for (Type t : types)
{
- typeArgumentIndex = getTypeVariableIndex(descendantClass, (TypeVariable) extractedType);
+ final Type resolved = resolve(t, suspectedSubType);
+ if (isAssignableFrom(suspectedSuperType, resolved))
+ return true;
}
- else
+
+ curType = curClass.getGenericSuperclass();
+ }
+ return false;
+ }
+
+ /**
+ * Get the class represented by the reflected type.
+ * This method is lossy; You cannot recover the type information from the class that is returned.
+ * <p/>
+ * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information
+ * is lost.
+ * <p/>
+ * {@code WildcardType} the first lower bound is returned. If the wildcard is defined with upper bounds
+ * then {@code Object} is returned.
+ *
+ * @param actualType
+ * a Class, ParameterizedType, GenericArrayType
+ * @return the un-parameterized class associated with the type.
+ */
+ public static Class asClass(Type actualType)
+ {
+ if (actualType instanceof Class) return (Class) actualType;
+
+ if (actualType instanceof ParameterizedType)
+ {
+ final Type rawType = ((ParameterizedType) actualType).getRawType();
+ // The sun implementation returns getRawType as Class<?>, but there is room in the interface for it to be
+ // some other Type. We'll assume it's a Class.
+ // TODO: consider logging or throwing our own exception for that day when "something else" causes some confusion
+ return (Class) rawType;
+ }
+
+ if (actualType instanceof GenericArrayType)
+ {
+ final Type type = ((GenericArrayType) actualType).getGenericComponentType();
+ return Array.newInstance(asClass(type), 0).getClass();
+ }
+
+ if (actualType instanceof TypeVariable)
+ {
+ // Support for List<T extends Number>
+ // There is always at least one bound. If no bound is specified in the source then it will be Object.class
+ return asClass(((TypeVariable) actualType).getBounds()[0]);
+ }
+
+ if (actualType instanceof WildcardType)
+ {
+ final WildcardType wildcardType = (WildcardType) actualType;
+ final Type[] bounds = wildcardType.getLowerBounds();
+ if (bounds != null && bounds.length > 0)
{
- // I don't know what else this could be?
- break;
+ return asClass(bounds[0]);
}
+ // If there is no lower bounds then the only thing that makes sense is Object.
+ return Object.class;
}
- return defaultType;
+ throw new RuntimeException(String.format("Unable to convert %s to 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)
+ * Convert the type into a string. The string representation approximates the code that would be used to define the
+ * type.
+ *
+ * @param type - the type.
+ * @return a string representation of the type, similar to how it was declared.
+ */
+ public static String toString(Type type)
+ {
+ if ( type instanceof ParameterizedType ) return toString((ParameterizedType)type);
+ if ( type instanceof WildcardType ) return toString((WildcardType)type);
+ if ( type instanceof GenericArrayType) return toString((GenericArrayType)type);
+ if ( type instanceof Class )
+ {
+ final Class theClass = (Class) type;
+ return (theClass.isArray() ? theClass.getName() + "[]" : theClass.getName());
+ }
+ return type.toString();
+ }
+
+ /**
+ * Method to resolve a TypeVariable to it's most
+ * <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#112582">reifiable</a> form.
+ * <p/>
+ * <p/>
+ * How to resolve a TypeVariable:<br/>
+ * All of the TypeVariables defined by a generic class will be given a Type by any class that extends it. The Type
+ * given may or may not be reifiable; it may be another TypeVariable for instance.
+ * <p/>
+ * Consider <br/>
+ * <i>class Pair>A,B> { A getA(){...}; ...}</i><br/>
+ * <i>class StringLongPair extends Pair>String, Long> { }</i><br/>
+ * <p/>
+ * To resolve the actual return type of Pair.getA() you must first resolve the TypeVariable "A".
+ * We can do that by first finding the index of "A" in the Pair.class.getTypeParameters() array of TypeVariables.
+ * <p/>
+ * To get to the Type provided by StringLongPair you access the generics information by calling
+ * StringLongPair.class.getGenericSuperclass; this will be a ParameterizedType. ParameterizedType gives you access
+ * to the actual type arguments provided to Pair by StringLongPair. The array is in the same order as the array in
+ * Pair.class.getTypeParameters so you can use the index we discovered earlier to extract the Type; String.class.
+ * <p/>
+ * When extracting Types we only have to consider the superclass hierarchy and not the interfaces implemented by
+ * the class. When a class implements a generic interface it must provide types for the interface and any generic
+ * methods implemented from the interface will be re-defined by the class with it's generic type variables.
+ *
+ * @param typeVariable - the type variable to resolve.
+ * @param containingType - the shallowest class in the class hierarchy (furthest from Object) where typeVariable is defined.
+ * @return a Type that has had all possible TypeVariables resolved that have been defined between the type variable
+ * declaration and the containingType.
*/
- private static Class extractActualTypeAsClass(Class containingClassType, Class<?> declaringClass, Type type,
- Class<?> defaultType)
+ private static Type resolve(TypeVariable typeVariable, Type containingType)
{
- final Type actualType = extractActualType(containingClassType, declaringClass, type, defaultType);
+ // The generic declaration is either a Class, Method or Constructor
+ final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+
+ if (!(genericDeclaration instanceof Class))
+ {
+ // It's a method or constructor. The best we can do here is try to resolve the bounds
+ // e.g. <T extends E> T getT(T param){} where E is defined by the class.
+ final Type bounds0 = typeVariable.getBounds()[0];
+ return resolve(bounds0, containingType);
+ }
+
+ final Class typeVariableOwner = (Class) genericDeclaration;
+
+ // find the typeOwner in the containingType's hierarchy
+ final LinkedList<Type> stack = new LinkedList<Type>();
+
+ // If you pass a List<Long> as the containingType then the TypeVariable is going to be resolved by the
+ // containingType and not the super class.
+ if (containingType instanceof ParameterizedType)
+ {
+ stack.add(containingType);
+ }
- return asClass(actualType);
+ Class theClass = asClass(containingType);
+ Type genericSuperclass = theClass.getGenericSuperclass();
+ while (genericSuperclass != null && // true for interfaces with no superclass
+ !theClass.equals(Object.class) &&
+ !theClass.equals(typeVariableOwner))
+ {
+ stack.addFirst(genericSuperclass);
+ theClass = asClass(genericSuperclass);
+ genericSuperclass = theClass.getGenericSuperclass();
+ }
+
+ int i = getTypeVariableIndex(typeVariable);
+ Type resolved = typeVariable;
+ for (Type t : stack)
+ {
+ if (t instanceof ParameterizedType)
+ {
+ resolved = ((ParameterizedType) t).getActualTypeArguments()[i];
+ if (resolved instanceof Class) return resolved;
+ if (resolved instanceof TypeVariable)
+ {
+ // Need to look at the next class in the hierarchy
+ i = getTypeVariableIndex((TypeVariable) resolved);
+ continue;
+ }
+ return resolve(resolved, containingType);
+ }
+ }
+
+ // the only way we get here is if resolved is still a TypeVariable, otherwise an
+ // exception is thrown or a value is returned.
+ return ((TypeVariable) resolved).getBounds()[0];
}
- public static Class asClass(Type actualType)
+ /**
+ * @param type - something like List<T>[] or List<? extends T>[] or T[]
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return either the passed type if no changes required or a copy with a best effort resolve of the component type.
+ */
+ private static GenericArrayType resolve(GenericArrayType type, Type containingType)
{
- if (actualType instanceof ParameterizedType)
+ final Type componentType = type.getGenericComponentType();
+
+ if (!(componentType instanceof Class))
{
- final Type rawType = ((ParameterizedType) actualType).getRawType();
- if (rawType instanceof Class)
+ final Type resolved = resolve(componentType, containingType);
+ return create(resolved);
+ }
+
+ return type;
+ }
+
+ /**
+ * @param type - something like List<T>, List<T extends Number>
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return the passed type if nothing to resolve or a copy of the type with the type arguments resolved.
+ */
+ private static ParameterizedType resolve(ParameterizedType type, Type containingType)
+ {
+ // Use a copy because we're going to modify it.
+ final Type[] types = type.getActualTypeArguments().clone();
+
+ boolean modified = resolve(types, containingType);
+ return modified ? create(type.getRawType(), type.getOwnerType(), types) : type;
+ }
+
+ /**
+ * @param type - something like List<? super T>, List<<? extends T>, List<? extends T & Comparable<? super T>>
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return the passed type if nothing to resolve or a copy of the type with the upper and lower bounds resolved.
+ */
+ private static WildcardType resolve(WildcardType type, Type containingType)
+ {
+ // Use a copy because we're going to modify them.
+ final Type[] upper = type.getUpperBounds().clone();
+ final Type[] lower = type.getLowerBounds().clone();
+
+ boolean modified = resolve(upper, containingType);
+ modified = modified || resolve(lower, containingType);
+
+ return modified ? create(upper, lower) : type;
+ }
+
+ /**
+ * @param types - Array of types to resolve. The unresolved type is replaced in the array with the resolved type.
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return true if any of the types were resolved.
+ */
+ private static boolean resolve(Type[] types, Type containingType)
+ {
+ boolean modified = false;
+ for (int i = 0; i < types.length; ++i)
+ {
+ Type t = types[i];
+ if (!(t 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;
+ modified = true;
+ final Type resolved = resolve(t, containingType);
+ if (!resolved.equals(t))
+ {
+ types[i] = resolved;
+ modified = true;
+ }
}
}
+ return modified;
+ }
+
+ /**
+ * @param rawType - the un-parameterized type.
+ * @param ownerType - the outer class or null if the class is not defined within another class.
+ * @param typeArguments - type arguments.
+ * @return a copy of the type with the typeArguments replaced.
+ */
+ static ParameterizedType create(final Type rawType, final Type ownerType, final Type[] typeArguments)
+ {
+ return new ParameterizedType()
+ {
+ public Type[] getActualTypeArguments()
+ {
+ return typeArguments;
+ }
+
+ public Type getRawType()
+ {
+ return rawType;
+ }
+
+ public Type getOwnerType()
+ {
+ return ownerType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ static GenericArrayType create(final Type componentType)
+ {
+ return new GenericArrayType()
+ {
+ public Type getGenericComponentType()
+ {
+ return componentType;
+ }
- return (Class) actualType;
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ /**
+ * @param upperBounds - e.g. ? extends Number
+ * @param lowerBounds - e.g. ? super Long
+ * @return An new copy of the type with the upper and lower bounds replaced.
+ */
+ static WildcardType create(final Type[] upperBounds, final Type[] lowerBounds)
+ {
+
+ return new WildcardType()
+ {
+ public Type[] getUpperBounds()
+ {
+ return upperBounds;
+ }
+
+ public Type[] getLowerBounds()
+ {
+ return lowerBounds;
+ }
+
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ static String toString(ParameterizedType pt)
+ {
+ String s = toString(pt.getActualTypeArguments());
+ return String.format("%s<%s>", toString(pt.getRawType()), s);
+ }
+
+ static String toString(GenericArrayType gat)
+ {
+ return String.format("%s[]", toString(gat.getGenericComponentType()));
+ }
+
+ static String toString(WildcardType wt)
+ {
+ final boolean isSuper = wt.getLowerBounds().length > 0;
+ return String.format("? %s %s",
+ isSuper ? "super" : "extends",
+ isSuper ? toString(wt.getLowerBounds()) : toString(wt.getLowerBounds()));
+ }
+
+ static String toString(Type[] types)
+ {
+ StringBuilder sb = new StringBuilder();
+ for ( Type t : types )
+ {
+ sb.append(toString(t)).append(", ");
+ }
+ return sb.substring(0, sb.length() - 2);// drop last ,
}
/**
* 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++)
+ *
+ * @param typeVariable - the type variable in question.
+ * @return the index of the type variable in it's declaring class/method/constructor's type parameters.
+ */
+ private static int getTypeVariableIndex(final TypeVariable typeVariable)
+ {
+ // the label from the class (the T in List<T>, the K or V in Map<K,V>, etc)
+ final String typeVarName = typeVariable.getName();
+ final TypeVariable[] typeParameters = typeVariable.getGenericDeclaration().getTypeParameters();
+ for (int typeArgumentIndex = 0; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
{
+ // The .equals for TypeVariable may not be compatible, a name check should be sufficient.
if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
- break;
+ return typeArgumentIndex;
}
- return typeArgumentIndex;
+
+ // The only way this could happen is if the TypeVariable is hand built incorrectly, or it's corrupted.
+ throw new RuntimeException(
+ String.format("%s does not have a TypeVariable matching %s", typeVariable.getGenericDeclaration(), typeVariable));
}
}