simplified TypeDescriptor usage and updated use of the API across BeanWrapper and SpEL; collapsed PropertyTypeDescriptor into TypeDescriptor for simplicity and ease of use; improved docs

This commit is contained in:
Keith Donald
2011-06-02 23:37:19 +00:00
parent 9d2ee7061c
commit 6f146737f4
12 changed files with 930 additions and 449 deletions

View File

@@ -19,17 +19,33 @@ package org.springframework.core.convert;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* @author Andy Clement
@@ -163,7 +179,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType());
assertEquals(String.class, typeDescriptor.getElementType());
// TODO caught shorten these names but it is OK that they are fully qualified for now
assertEquals("java.util.List<java.lang.String>", typeDescriptor.asString());
assertEquals("java.util.List<java.lang.String>", typeDescriptor.toString());
}
@Test
@@ -173,7 +189,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.asString());
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.toString());
}
@Test
@@ -183,7 +199,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(Object.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.asString());
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.toString());
}
@Test
@@ -191,14 +207,14 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray"));
assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE,typeDescriptor.getElementType());
assertEquals("int[]",typeDescriptor.asString());
assertEquals("int[]",typeDescriptor.toString());
}
@Test
public void buildingArrayTypeDescriptor() throws Exception {
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class);
assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE ,typeDescriptor.getElementType());
assertEquals(Integer.TYPE, typeDescriptor.getElementType());
}
@Test
@@ -208,7 +224,7 @@ public class TypeDescriptorTests {
assertTrue(typeDescriptor.isArray());
assertEquals(List.class,typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List[]",typeDescriptor.asString());
assertEquals("java.util.List[]",typeDescriptor.toString());
}
@Test
@@ -218,7 +234,7 @@ public class TypeDescriptorTests {
assertEquals(String.class,typeDescriptor.getMapKeyType());
assertEquals(List.class, typeDescriptor.getMapValueType());
assertEquals(Integer.class, typeDescriptor.getMapValueTypeDescriptor().getElementType());
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.asString());
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.toString());
}
@Test
@@ -244,5 +260,373 @@ public class TypeDescriptorTests {
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
assertEquals(t11, t12);
}
@Test
public void annotatedMethod() throws Exception {
TypeDescriptor t1 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
assertEquals(String.class, t1.getType());
assertNotNull(t1.getAnnotation(ParameterAnnotation.class));
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParameterAnnotation {
}
public void testAnnotatedMethod(@ParameterAnnotation String parameter) {
}
@Test
public void nestedMethodParameterType() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test1", List.class), 0), 1);
assertEquals(String.class, t1.getType());
}
@Test
public void nestedMethodParameterType2Levels() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test2", List.class), 0), 2);
assertEquals(String.class, t1.getType());
}
@Test
public void nestedMethodParameterTypeMap() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test3", Map.class), 0), 1);
assertEquals(String.class, t1.getType());
}
@Test
public void nestedMethodParameterTypeMapTwoLevels() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test4", List.class), 0), 2);
assertEquals(String.class, t1.getType());
}
@Test(expected=IllegalStateException.class)
public void nestedMethodParameterTypeNotNestable() throws Exception {
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0), 2);
}
@Test(expected=IllegalArgumentException.class)
public void nestedMethodParameterTypeInvalidNestingLevel() throws Exception {
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0, 2), 2);
}
public void test1(List<String> param1) {
}
public void test2(List<List<String>> param1) {
}
public void test3(Map<Integer, String> param1) {
}
public void test4(List<Map<Integer, String>> param1) {
}
public void test5(String param1) {
}
@Test
public void nestedFieldTypeMapTwoLevels() throws Exception {
TypeDescriptor t1 = TypeDescriptor.nested(getClass().getField("test4"), 2);
assertEquals(String.class, t1.getType());
}
public List<Map<Integer, String>> test4;
@Test
public void nestedPropertyTypeMapTwoLevels() throws Exception {
PropertyDescriptor property = new PropertyDescriptor("test4", getClass().getMethod("getTest4", null), getClass().getMethod("setTest4", List.class));
TypeDescriptor t1 = TypeDescriptor.nested(getClass(), property, 2);
assertEquals(String.class, t1.getType());
}
public List<Map<Integer, String>> getTest4() {
return null;
}
public void setTest4(List<Map<Integer, String>> test4) {
}
@Test
public void property() throws Exception {
PropertyDescriptor property = new PropertyDescriptor("property", getClass().getMethod("getProperty", null), getClass().getMethod("setProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
assertNotNull(desc.getAnnotation(MethodAnnotation1.class));
assertNotNull(desc.getAnnotation(MethodAnnotation2.class));
assertNotNull(desc.getAnnotation(MethodAnnotation3.class));
}
@MethodAnnotation1
public Map<List<Integer>, List<Long>> getProperty() {
return property;
}
@MethodAnnotation2
public void setProperty(Map<List<Integer>, List<Long>> property) {
this.property = property;
}
@MethodAnnotation3
private Map<List<Integer>, List<Long>> property;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation1 {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation2 {
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation3 {
}
@Test
@Ignore
public void field() throws Exception {
// typeIndex handling not currently supported by fields
TypeDescriptor desc = new TypeDescriptor(getClass().getField("field"));
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
}
public Map<List<Integer>, List<Long>> field;
@Test
public void methodParameter() throws Exception {
TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("setProperty", Map.class), 0));
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
}
@Test
public void complexProperty() throws Exception {
PropertyDescriptor property = new PropertyDescriptor("complexProperty", getClass().getMethod("getComplexProperty", null), getClass().getMethod("setComplexProperty", Map.class));
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
//assertEquals(String.class, desc.getMapKeyType());
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getElementType());
}
public Map<String, List<List<Integer>>> getComplexProperty() {
return null;
}
public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) {
}
@Test
public void genericType() throws Exception {
GenericType<Integer> genericBean = new IntegerType();
PropertyDescriptor property = new PropertyDescriptor("property", genericBean.getClass().getMethod("getProperty", null), genericBean.getClass().getMethod("setProperty", Integer.class));
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
assertEquals(Integer.class, desc.getType());
}
@Test
public void genericTypeList() throws Exception {
GenericType<Integer> genericBean = new IntegerType();
PropertyDescriptor property = new PropertyDescriptor("listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class));
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementType());
}
public interface GenericType<T> {
T getProperty();
void setProperty(T t);
List<T> getListProperty();
void setListProperty(List<T> t);
}
public class IntegerType implements GenericType<Integer> {
public Integer getProperty() {
// TODO Auto-generated method stub
return null;
}
public void setProperty(Integer t) {
// TODO Auto-generated method stub
}
public List<Integer> getListProperty() {
// TODO Auto-generated method stub
return null;
}
public void setListProperty(List<Integer> t) {
// TODO Auto-generated method stub
}
}
@Test
public void genericClassList() throws Exception {
IntegerClass genericBean = new IntegerClass();
PropertyDescriptor property = new GenericTypeAwarePropertyDescriptor(genericBean.getClass(), "listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class), null);
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
assertEquals(List.class, desc.getType());
assertEquals(Integer.class, desc.getElementType());
}
public static class GenericClass<T> {
public T getProperty() {
return null;
}
public void setProperty(T t) {
}
public List<T> getListProperty() {
return null;
}
public void setListProperty(List<T> t) {
}
}
public static class IntegerClass extends GenericClass<Integer> {
}
private static class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
private final Class beanClass;
private final Method readMethod;
private final Method writeMethod;
private final Class propertyEditorClass;
private volatile Set<Method> ambiguousWriteMethods;
private Class propertyType;
private MethodParameter writeMethodParameter;
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
Method readMethod, Method writeMethod, Class propertyEditorClass)
throws IntrospectionException {
super(propertyName, null, null);
this.beanClass = beanClass;
this.propertyEditorClass = propertyEditorClass;
Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod);
Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod);
if (writeMethodToUse == null && readMethodToUse != null) {
// Fallback: Original JavaBeans introspection might not have found matching setter
// method due to lack of bridge method resolution, in case of the getter using a
// covariant return type whereas the setter is defined for the concrete property type.
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());
}
this.readMethod = readMethodToUse;
this.writeMethod = writeMethodToUse;
if (this.writeMethod != null && this.readMethod == null) {
// Write method not matched against read method: potentially ambiguous through
// several overloaded variants, in which case an arbitrary winner has been chosen
// by the JDK's JavaBeans Introspector...
Set<Method> ambiguousCandidates = new HashSet<Method>();
for (Method method : beanClass.getMethods()) {
if (method.getName().equals(writeMethodToUse.getName()) &&
!method.equals(writeMethodToUse) && !method.isBridge()) {
ambiguousCandidates.add(method);
}
}
if (!ambiguousCandidates.isEmpty()) {
this.ambiguousWriteMethods = ambiguousCandidates;
}
}
}
@Override
public Method getReadMethod() {
return this.readMethod;
}
@Override
public Method getWriteMethod() {
return this.writeMethod;
}
public Method getWriteMethodForActualAccess() {
Set<Method> ambiguousCandidates = this.ambiguousWriteMethods;
if (ambiguousCandidates != null) {
this.ambiguousWriteMethods = null;
LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).warn("Invalid JavaBean property '" +
getName() + "' being accessed! Ambiguous write methods found next to actually used [" +
this.writeMethod + "]: " + ambiguousCandidates);
}
return this.writeMethod;
}
@Override
public Class getPropertyEditorClass() {
return this.propertyEditorClass;
}
@Override
public synchronized Class getPropertyType() {
if (this.propertyType == null) {
if (this.readMethod != null) {
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
}
else {
MethodParameter writeMethodParam = getWriteMethodParameter();
if (writeMethodParam != null) {
this.propertyType = writeMethodParam.getParameterType();
}
else {
this.propertyType = super.getPropertyType();
}
}
}
return this.propertyType;
}
public synchronized MethodParameter getWriteMethodParameter() {
if (this.writeMethod == null) {
return null;
}
if (this.writeMethodParameter == null) {
this.writeMethodParameter = new MethodParameter(this.writeMethod, 0);
GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass);
}
return this.writeMethodParameter;
}
}
}