From a1b7af5c9c230274770880748ebc10e1e21a83f5 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Fri, 3 Jun 2011 03:09:05 +0000 Subject: [PATCH] broke out pkg private classes from TypeDescriptor to improve manageability and testability --- .../core/convert/AbstractDescriptor.java | 106 ++++ .../core/convert/BeanPropertyDescriptor.java | 152 ++++++ .../core/convert/ClassDescriptor.java | 51 ++ .../core/convert/CommonElement.java | 116 ++++ .../core/convert/FieldDescriptor.java | 66 +++ .../core/convert/ParameterDescriptor.java | 75 +++ .../core/convert/TypeDescriptor.java | 498 +----------------- .../core/convert/TypeDescriptorTests.java | 4 +- 8 files changed, 593 insertions(+), 475 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java new file mode 100644 index 00000000000..1faf39f1947 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Map; + +abstract class AbstractDescriptor { + + private final Class type; + + public AbstractDescriptor(Class type) { + this.type = type; + } + + public Class getType() { + return type; + } + + public TypeDescriptor getElementType() { + if (isCollection()) { + Class elementType = wildcard(getCollectionElementClass()); + return new TypeDescriptor(nested(elementType, 0)); + } else if (isArray()) { + Class elementType = getType().getComponentType(); + return new TypeDescriptor(nested(elementType, 0)); + } else { + return TypeDescriptor.NULL; + } + } + + public TypeDescriptor getMapKeyType() { + if (isMap()) { + Class keyType = wildcard(getMapKeyClass()); + return new TypeDescriptor(nested(keyType, 0)); + } else { + return TypeDescriptor.NULL; + } + } + + public TypeDescriptor getMapValueType() { + if (isMap()) { + Class valueType = wildcard(getMapValueClass()); + return new TypeDescriptor(nested(valueType, 1)); + } else { + return TypeDescriptor.NULL; + } + } + + public abstract Annotation[] getAnnotations(); + + public AbstractDescriptor nested() { + if (isCollection()) { + return nested(wildcard(getCollectionElementClass()), 0); + } else if (isArray()) { + return nested(getType().getComponentType(), 0); + } else if (isMap()) { + return nested(wildcard(getMapValueClass()), 1); + } else { + throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types"); + } + } + + // subclassing hooks + + protected abstract Class getCollectionElementClass(); + + protected abstract Class getMapKeyClass(); + + protected abstract Class getMapValueClass(); + + protected abstract AbstractDescriptor nested(Class type, int typeIndex); + + // internal helpers + + private boolean isCollection() { + return Collection.class.isAssignableFrom(getType()); + } + + private boolean isArray() { + return getType().isArray(); + } + + private boolean isMap() { + return Map.class.isAssignableFrom(getType()); + } + + private Class wildcard(Class type) { + return type != null ? type : Object.class; + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java new file mode 100644 index 00000000000..ebc6d12c147 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.MethodParameter; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +class BeanPropertyDescriptor extends AbstractDescriptor { + + private final Class beanClass; + + private final PropertyDescriptor property; + + private final MethodParameter methodParameter; + + private final Annotation[] annotations; + + public BeanPropertyDescriptor(Class beanClass, PropertyDescriptor property) { + super(property.getPropertyType()); + this.beanClass = beanClass; + this.property = property; + this.methodParameter = resolveMethodParameter(); + this.annotations = resolveAnnotations(); + } + + @Override + public Annotation[] getAnnotations() { + return annotations; + } + + @Override + protected Class getCollectionElementClass() { + return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); + } + + @Override + protected Class getMapKeyClass() { + return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); + } + + @Override + protected Class getMapValueClass() { + return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); + } + + @Override + protected AbstractDescriptor nested(Class type, int typeIndex) { + MethodParameter methodParameter = new MethodParameter(this.methodParameter); + methodParameter.increaseNestingLevel(); + methodParameter.setTypeIndexForCurrentLevel(typeIndex); + return new BeanPropertyDescriptor(type, beanClass, property, methodParameter, annotations); + } + + // internal + + private MethodParameter resolveMethodParameter() { + MethodParameter parameter = parameterForPropertyMethod(); + // needed to resolve generic property types that parameterized by sub-classes e.g. T getFoo(); + GenericTypeResolver.resolveParameterType(parameter, beanClass); + return parameter; + } + + private MethodParameter parameterForPropertyMethod() { + if (property.getReadMethod() != null) { + return new MethodParameter(property.getReadMethod(), -1); + } else if (property.getWriteMethod() != null) { + return new MethodParameter(property.getWriteMethod(), 0); + } else { + throw new IllegalArgumentException("Property is neither readable or writeable"); + } + } + + private Annotation[] resolveAnnotations() { + Map, Annotation> annMap = new LinkedHashMap, Annotation>(); + Method readMethod = this.property.getReadMethod(); + if (readMethod != null) { + for (Annotation ann : readMethod.getAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + } + Method writeMethod = this.property.getWriteMethod(); + if (writeMethod != null) { + for (Annotation ann : writeMethod.getAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + } + Field field = getField(); + if (field != null) { + for (Annotation ann : field.getAnnotations()) { + annMap.put(ann.annotationType(), ann); + } + } + return annMap.values().toArray(new Annotation[annMap.size()]); + } + + private Field getField() { + String name = this.property.getName(); + if (!StringUtils.hasLength(name)) { + return null; + } + Class declaringClass = declaringClass(); + Field field = ReflectionUtils.findField(declaringClass, name); + if (field == null) { + // Same lenient fallback checking as in CachedIntrospectionResults... + field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toLowerCase() + name.substring(1)); + if (field == null) { + field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toUpperCase() + name.substring(1)); + } + } + return field; + } + + private Class declaringClass() { + if (this.property.getReadMethod() != null) { + return this.property.getReadMethod().getDeclaringClass(); + } else { + return this.property.getWriteMethod().getDeclaringClass(); + } + } + + private BeanPropertyDescriptor(Class type, Class beanClass, java.beans.PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) { + super(type); + this.beanClass = beanClass; + this.property = propertyDescriptor; + this.methodParameter = methodParameter; + this.annotations = annotations; + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java new file mode 100644 index 00000000000..afa7a913d25 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.lang.annotation.Annotation; + +class ClassDescriptor extends AbstractDescriptor { + + ClassDescriptor(Class type) { + super(type); + } + + @Override + public Annotation[] getAnnotations() { + return TypeDescriptor.EMPTY_ANNOTATION_ARRAY; + } + + @Override + protected Class getCollectionElementClass() { + return Object.class; + } + + @Override + protected Class getMapKeyClass() { + return Object.class; + } + + @Override + protected Class getMapValueClass() { + return Object.class; + } + + @Override + protected AbstractDescriptor nested(Class type, int typeIndex) { + return new ClassDescriptor(type); + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java b/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java new file mode 100644 index 00000000000..51ca44a2f24 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/CommonElement.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +class CommonElement { + + private final Class type; + + private final Object value; + + public CommonElement(Class type, Object value) { + this.type = type; + this.value = value; + } + + public Class getType() { + return type; + } + + public Object getValue() { + return value; + } + + public TypeDescriptor toTypeDescriptor() { + if (type == null) { + return TypeDescriptor.NULL; + } else if (value instanceof Collection) { + Collection collection = (Collection) value; + return new TypeDescriptor(type, typeDescriptor(collection)); + } + else if (value instanceof Map) { + Map map = (Map) value; + return new TypeDescriptor(type, typeDescriptor(map.keySet()), typeDescriptor(map.values())); + } + else { + return TypeDescriptor.valueOf(type); + } + } + + public static TypeDescriptor typeDescriptor(Collection collection) { + return findCommonElement(collection).toTypeDescriptor(); + } + + // internal helpers + + private static CommonElement findCommonElement(Collection values) { + Class commonType = null; + Object candidate = null; + for (Object value : values) { + if (value != null) { + if (candidate == null) { + commonType = value.getClass(); + candidate = value; + } else { + commonType = commonType(commonType, value.getClass()); + if (commonType == Object.class) { + return new CommonElement(Object.class, null); + } + } + } + } + return new CommonElement(commonType, candidate); + } + + private static Class commonType(Class commonType, Class valueClass) { + Set> interfaces = new LinkedHashSet>(); + LinkedList> classQueue = new LinkedList>(); + classQueue.addFirst(commonType); + while (!classQueue.isEmpty()) { + Class currentClass = classQueue.removeLast(); + if (currentClass.isAssignableFrom(valueClass)) { + return currentClass; + } + Class superClass = currentClass.getSuperclass(); + if (superClass != null && superClass != Object.class) { + classQueue.addFirst(currentClass.getSuperclass()); + } + for (Class interfaceType : currentClass.getInterfaces()) { + addInterfaceHierarchy(interfaceType, interfaces); + } + } + for (Class interfaceType : interfaces) { + if (interfaceType.isAssignableFrom(valueClass)) { + return interfaceType; + } + } + return Object.class; + } + + private static void addInterfaceHierarchy(Class interfaceType, Set> interfaces) { + interfaces.add(interfaceType); + for (Class inheritedInterface : interfaceType.getInterfaces()) { + addInterfaceHierarchy(inheritedInterface, interfaces); + } + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java new file mode 100644 index 00000000000..3f7a7d26e92 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +import org.springframework.core.GenericCollectionTypeResolver; + +class FieldDescriptor extends AbstractDescriptor { + + private final Field field; + + private final int nestingLevel; + + public FieldDescriptor(Field field) { + this(field.getType(), field, 1, 0); + } + + @Override + public Annotation[] getAnnotations() { + return TypeDescriptor.nullSafeAnnotations(field.getAnnotations()); + } + + @Override + protected Class getCollectionElementClass() { + return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel); + } + + @Override + protected Class getMapKeyClass() { + return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel); + } + + @Override + protected Class getMapValueClass() { + return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel); + } + + @Override + protected AbstractDescriptor nested(Class type, int typeIndex) { + return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex); + } + + // internal + + private FieldDescriptor(Class type, Field field, int nestingLevel, int typeIndex) { + super(type); + this.field = field; + this.nestingLevel = nestingLevel; + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java new file mode 100644 index 00000000000..465348c0663 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert; + +import java.lang.annotation.Annotation; + +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.MethodParameter; + +class ParameterDescriptor extends AbstractDescriptor { + + private final MethodParameter methodParameter; + + public ParameterDescriptor(MethodParameter methodParameter) { + super(methodParameter.getParameterType()); + if (methodParameter.getNestingLevel() != 1) { + throw new IllegalArgumentException("The MethodParameter argument must have its nestingLevel set to 1"); + } + this.methodParameter = methodParameter; + } + + @Override + public Annotation[] getAnnotations() { + if (methodParameter.getParameterIndex() == -1) { + return TypeDescriptor.nullSafeAnnotations(methodParameter.getMethodAnnotations()); + } + else { + return TypeDescriptor.nullSafeAnnotations(methodParameter.getParameterAnnotations()); + } + } + + @Override + protected Class getCollectionElementClass() { + return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); + } + + @Override + protected Class getMapKeyClass() { + return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); + } + + @Override + protected Class getMapValueClass() { + return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); + } + + @Override + protected AbstractDescriptor nested(Class type, int typeIndex) { + MethodParameter methodParameter = new MethodParameter(this.methodParameter); + methodParameter.increaseNestingLevel(); + methodParameter.setTypeIndexForCurrentLevel(typeIndex); + return new ParameterDescriptor(type, methodParameter); + } + + // internal + + private ParameterDescriptor(Class type, MethodParameter methodParameter) { + super(type); + this.methodParameter = methodParameter; + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index da5cb00d9da..1867f3f773d 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -19,22 +19,13 @@ package org.springframework.core.convert; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.Map; -import java.util.Set; -import org.springframework.core.GenericCollectionTypeResolver; -import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; /** * Context about a type to convert from or to. @@ -50,7 +41,7 @@ public class TypeDescriptor { private static final Map, TypeDescriptor> typeDescriptorCache = new HashMap, TypeDescriptor>(); - private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; static { typeDescriptorCache.put(boolean.class, new TypeDescriptor(boolean.class)); @@ -177,11 +168,11 @@ public class TypeDescriptor { return NULL; } if (object instanceof Collection) { - return new TypeDescriptor(object.getClass(), findCommonElement((Collection) object)); + return new TypeDescriptor(object.getClass(), CommonElement.typeDescriptor((Collection) object)); } else if (object instanceof Map) { Map map = (Map) object; - return new TypeDescriptor(map.getClass(), findCommonElement(map.keySet()), findCommonElement(map.values())); + return new TypeDescriptor(map.getClass(), CommonElement.typeDescriptor(map.keySet()), CommonElement.typeDescriptor(map.values())); } else { return valueOf(object.getClass()); @@ -438,13 +429,9 @@ public class TypeDescriptor { } } - // internal constructors + // package private - private TypeDescriptor(Class type) { - this(new ClassDescriptor(type)); - } - - private TypeDescriptor(AbstractDescriptor descriptor) { + TypeDescriptor(AbstractDescriptor descriptor) { this.type = descriptor.getType(); this.elementType = descriptor.getElementType(); this.mapKeyType = descriptor.getMapKeyType(); @@ -452,26 +439,28 @@ public class TypeDescriptor { this.annotations = descriptor.getAnnotations(); } + TypeDescriptor(Class collectionType, TypeDescriptor elementType) { + this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL); + } + + TypeDescriptor(Class mapType, TypeDescriptor keyType, TypeDescriptor valueType) { + this(mapType, TypeDescriptor.NULL, keyType, valueType); + } + + static Annotation[] nullSafeAnnotations(Annotation[] annotations) { + return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY; + } + + // internal constructors + + private TypeDescriptor(Class type) { + this(new ClassDescriptor(type)); + } + private TypeDescriptor() { this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL); } - private TypeDescriptor(Class collectionType, TypeDescriptor elementType) { - this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL); - } - - private TypeDescriptor(Class mapType, TypeDescriptor keyType, TypeDescriptor valueType) { - this(mapType, TypeDescriptor.NULL, keyType, valueType); - } - - private TypeDescriptor(Class collectionType, CommonElement commonElement) { - this(collectionType, fromCommonElement(commonElement), TypeDescriptor.NULL, TypeDescriptor.NULL); - } - - private TypeDescriptor(Class mapType, CommonElement commonKey, CommonElement commonValue) { - this(mapType, TypeDescriptor.NULL, fromCommonElement(commonKey), fromCommonElement(commonValue)); - } - private TypeDescriptor(Class type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType) { this.type = type; this.elementType = elementType; @@ -480,86 +469,8 @@ public class TypeDescriptor { this.annotations = EMPTY_ANNOTATION_ARRAY; } - private static Annotation[] nullSafeAnnotations(Annotation[] annotations) { - return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY; - } + // internal helpers - // forObject-related internal helpers - - private static CommonElement findCommonElement(Collection values) { - Class commonType = null; - Object candidate = null; - for (Object value : values) { - if (value != null) { - if (candidate == null) { - commonType = value.getClass(); - candidate = value; - } else { - commonType = commonType(commonType, value.getClass()); - if (commonType == Object.class) { - return null; - } - } - } - } - return new CommonElement(commonType, candidate); - } - - private static Class commonType(Class commonType, Class valueClass) { - Set> interfaces = new LinkedHashSet>(); - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(commonType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - if (currentClass.isAssignableFrom(valueClass)) { - return currentClass; - } - Class superClass = currentClass.getSuperclass(); - if (superClass != null && superClass != Object.class) { - classQueue.addFirst(currentClass.getSuperclass()); - } - for (Class interfaceType : currentClass.getInterfaces()) { - addInterfaceHierarchy(interfaceType, interfaces); - } - } - for (Class interfaceType : interfaces) { - if (interfaceType.isAssignableFrom(valueClass)) { - return interfaceType; - } - } - return Object.class; - } - - private static void addInterfaceHierarchy(Class interfaceType, Set> interfaces) { - interfaces.add(interfaceType); - for (Class inheritedInterface : interfaceType.getInterfaces()) { - addInterfaceHierarchy(inheritedInterface, interfaces); - } - } - - private static TypeDescriptor fromCommonElement(CommonElement commonElement) { - if (commonElement == null) { - return TypeDescriptor.valueOf(Object.class); - } - if (commonElement.getValue() instanceof Collection) { - Collection collection = (Collection) commonElement.getValue(); - if (collection.size() == 0) { - return TypeDescriptor.valueOf(Object.class); - } - return new TypeDescriptor(commonElement.getType(), findCommonElement(collection)); - } - else if (commonElement.getValue() instanceof Map) { - Map map = (Map) commonElement.getValue(); - if (map.size() == 0) { - return TypeDescriptor.valueOf(Object.class); - } - return new TypeDescriptor(commonElement.getType(), findCommonElement(map.keySet()), findCommonElement(map.values())); - } - else { - return TypeDescriptor.valueOf(commonElement.getType()); - } - } - private static TypeDescriptor nested(AbstractDescriptor descriptor, int nestingLevel) { for (int i = 0; i < nestingLevel; i++) { descriptor = descriptor.nested(); @@ -567,365 +478,4 @@ public class TypeDescriptor { return new TypeDescriptor(descriptor); } - // inner classes - - private abstract static class AbstractDescriptor { - - private final Class type; - - public AbstractDescriptor(Class type) { - this.type = type; - } - - public Class getType() { - return type; - } - - public TypeDescriptor getElementType() { - if (isCollection()) { - Class elementType = wildcard(getCollectionElementClass()); - return new TypeDescriptor(nested(elementType, 0)); - } else if (isArray()) { - Class elementType = getType().getComponentType(); - return new TypeDescriptor(nested(elementType, 0)); - } else { - return TypeDescriptor.NULL; - } - } - - public TypeDescriptor getMapKeyType() { - if (isMap()) { - Class keyType = wildcard(getMapKeyClass()); - return new TypeDescriptor(nested(keyType, 0)); - } else { - return TypeDescriptor.NULL; - } - } - - public TypeDescriptor getMapValueType() { - if (isMap()) { - Class valueType = wildcard(getMapValueClass()); - return new TypeDescriptor(nested(valueType, 1)); - } else { - return TypeDescriptor.NULL; - } - } - - public abstract Annotation[] getAnnotations(); - - public AbstractDescriptor nested() { - if (isCollection()) { - return nested(wildcard(getCollectionElementClass()), 0); - } else if (isArray()) { - return nested(getType().getComponentType(), 0); - } else if (isMap()) { - return nested(wildcard(getMapValueClass()), 1); - } else { - throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types"); - } - } - - // subclassing hooks - - protected abstract Class getCollectionElementClass(); - - protected abstract Class getMapKeyClass(); - - protected abstract Class getMapValueClass(); - - protected abstract AbstractDescriptor nested(Class type, int typeIndex); - - // internal helpers - - private boolean isCollection() { - return Collection.class.isAssignableFrom(getType()); - } - - private boolean isArray() { - return getType().isArray(); - } - - private boolean isMap() { - return Map.class.isAssignableFrom(getType()); - } - - private Class wildcard(Class type) { - return type != null ? type : Object.class; - } - - } - - private static class FieldDescriptor extends AbstractDescriptor { - - private final Field field; - - private final int nestingLevel; - - public FieldDescriptor(Field field) { - this(field.getType(), field, 1, 0); - } - - @Override - public Annotation[] getAnnotations() { - return nullSafeAnnotations(field.getAnnotations()); - } - - @Override - protected Class getCollectionElementClass() { - return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel); - } - - @Override - protected Class getMapKeyClass() { - return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel); - } - - @Override - protected Class getMapValueClass() { - return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel); - } - - @Override - protected AbstractDescriptor nested(Class type, int typeIndex) { - return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex); - } - - // internal - - private FieldDescriptor(Class type, Field field, int nestingLevel, int typeIndex) { - super(type); - this.field = field; - this.nestingLevel = nestingLevel; - } - - } - - private static class ParameterDescriptor extends AbstractDescriptor { - - private final MethodParameter methodParameter; - - public ParameterDescriptor(MethodParameter methodParameter) { - super(methodParameter.getParameterType()); - if (methodParameter.getNestingLevel() != 1) { - throw new IllegalArgumentException("The MethodParameter argument must have its nestingLevel set to 1"); - } - this.methodParameter = methodParameter; - } - - @Override - public Annotation[] getAnnotations() { - if (methodParameter.getParameterIndex() == -1) { - return nullSafeAnnotations(methodParameter.getMethodAnnotations()); - } - else { - return nullSafeAnnotations(methodParameter.getParameterAnnotations()); - } - } - - @Override - protected Class getCollectionElementClass() { - return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); - } - - @Override - protected Class getMapKeyClass() { - return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); - } - - @Override - protected Class getMapValueClass() { - return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); - } - - @Override - protected AbstractDescriptor nested(Class type, int typeIndex) { - MethodParameter methodParameter = new MethodParameter(this.methodParameter); - methodParameter.increaseNestingLevel(); - methodParameter.setTypeIndexForCurrentLevel(typeIndex); - return new ParameterDescriptor(type, methodParameter); - } - - // internal - - private ParameterDescriptor(Class type, MethodParameter methodParameter) { - super(type); - this.methodParameter = methodParameter; - } - - } - - private static class BeanPropertyDescriptor extends AbstractDescriptor { - - private final Class beanClass; - - private final PropertyDescriptor property; - - private final MethodParameter methodParameter; - - private final Annotation[] annotations; - - public BeanPropertyDescriptor(Class beanClass, PropertyDescriptor property) { - super(property.getPropertyType()); - this.beanClass = beanClass; - this.property = property; - this.methodParameter = resolveMethodParameter(); - this.annotations = resolveAnnotations(); - } - - @Override - public Annotation[] getAnnotations() { - return annotations; - } - - @Override - protected Class getCollectionElementClass() { - return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); - } - - @Override - protected Class getMapKeyClass() { - return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); - } - - @Override - protected Class getMapValueClass() { - return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); - } - - @Override - protected AbstractDescriptor nested(Class type, int typeIndex) { - MethodParameter methodParameter = new MethodParameter(this.methodParameter); - methodParameter.increaseNestingLevel(); - methodParameter.setTypeIndexForCurrentLevel(typeIndex); - return new BeanPropertyDescriptor(type, beanClass, property, methodParameter, annotations); - } - - // internal - - private MethodParameter resolveMethodParameter() { - if (property.getReadMethod() != null) { - MethodParameter parameter = new MethodParameter(property.getReadMethod(), -1); - GenericTypeResolver.resolveParameterType(parameter, beanClass); - return parameter; - } else if (property.getWriteMethod() != null) { - MethodParameter parameter = new MethodParameter(property.getWriteMethod(), 0); - GenericTypeResolver.resolveParameterType(parameter, beanClass); - return parameter; - } else { - throw new IllegalArgumentException("Property is neither readable or writeable"); - } - } - - private Annotation[] resolveAnnotations() { - Map, Annotation> annMap = new LinkedHashMap, Annotation>(); - Method readMethod = this.property.getReadMethod(); - if (readMethod != null) { - for (Annotation ann : readMethod.getAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - } - Method writeMethod = this.property.getWriteMethod(); - if (writeMethod != null) { - for (Annotation ann : writeMethod.getAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - } - Field field = getField(); - if (field != null) { - for (Annotation ann : field.getAnnotations()) { - annMap.put(ann.annotationType(), ann); - } - } - return annMap.values().toArray(new Annotation[annMap.size()]); - } - - private Field getField() { - String name = this.property.getName(); - if (!StringUtils.hasLength(name)) { - return null; - } - Class declaringClass = declaringClass(); - Field field = ReflectionUtils.findField(declaringClass, name); - if (field == null) { - // Same lenient fallback checking as in CachedIntrospectionResults... - field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toLowerCase() + name.substring(1)); - if (field == null) { - field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toUpperCase() + name.substring(1)); - } - } - return field; - } - - private Class declaringClass() { - if (this.property.getReadMethod() != null) { - return this.property.getReadMethod().getDeclaringClass(); - } else { - return this.property.getWriteMethod().getDeclaringClass(); - } - } - - private BeanPropertyDescriptor(Class type, Class beanClass, java.beans.PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) { - super(type); - this.beanClass = beanClass; - this.property = propertyDescriptor; - this.methodParameter = methodParameter; - this.annotations = annotations; - } - - } - - private static class ClassDescriptor extends AbstractDescriptor { - - private ClassDescriptor(Class type) { - super(type); - } - - @Override - public Annotation[] getAnnotations() { - return EMPTY_ANNOTATION_ARRAY; - } - - @Override - protected Class getCollectionElementClass() { - return Object.class; - } - - @Override - protected Class getMapKeyClass() { - return Object.class; - } - - @Override - protected Class getMapValueClass() { - return Object.class; - } - - @Override - protected AbstractDescriptor nested(Class type, int typeIndex) { - return new ClassDescriptor(type); - } - - } - - private static class CommonElement { - - private Class type; - - private Object value; - - public CommonElement(Class type, Object value) { - this.type = type; - this.value = value; - } - - public Class getType() { - return type; - } - - public Object getValue() { - return value; - } - - } - } \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index ccffbba4cd7..5b659d48cb1 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -306,6 +306,7 @@ public class TypeDescriptorTests { TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property); assertEquals(List.class, desc.getType()); assertEquals(Integer.class, desc.getElementType()); + assertNotNull(desc.getAnnotation(MethodAnnotation1.class)); } public static class GenericClass { @@ -316,7 +317,8 @@ public class TypeDescriptorTests { public void setProperty(T t) { } - + + @MethodAnnotation1 public List getListProperty() { return null; }