diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index c7321ad40f..030d748099 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -34,7 +34,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -74,6 +73,8 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable private Class containingClass; + private volatile ResolvableType resolvableType; + /** * Create a new descriptor for a method or constructor parameter. @@ -279,8 +280,12 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable * @since 4.0 */ public ResolvableType getResolvableType() { - return (this.field != null ? ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) : - ResolvableType.forMethodParameter(this.methodParameter)); + if (this.resolvableType == null) { + this.resolvableType = (this.field != null ? + ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) : + ResolvableType.forMethodParameter(this.methodParameter)); + } + return this.resolvableType; } /** @@ -363,36 +368,6 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable } } - /** - * Determine the generic element type of the wrapped Collection parameter/field, if any. - * @return the generic type, or {@code null} if none - */ - public Class getCollectionType() { - return (this.field != null ? - GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); - } - - /** - * Determine the generic key type of the wrapped Map parameter/field, if any. - * @return the generic type, or {@code null} if none - */ - public Class getMapKeyType() { - return (this.field != null ? - GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); - } - - /** - * Determine the generic value type of the wrapped Map parameter/field, if any. - * @return the generic type, or {@code null} if none - */ - public Class getMapValueType() { - return (this.field != null ? - GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); - } - @Override public boolean equals(Object other) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java index c2cb65d297..2462506bc8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -21,7 +21,7 @@ import java.util.List; import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared List instances. Allows for central setup @@ -86,7 +86,7 @@ public class ListFactoryBean extends AbstractFactoryBean> { } Class valueType = null; if (this.targetListClass != null) { - valueType = GenericCollectionTypeResolver.getCollectionType(this.targetListClass); + valueType = ResolvableType.forClass(this.targetListClass).asCollection().resolveGeneric(); } if (valueType != null) { TypeConverter converter = getBeanTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java index 8e37987c37..542487fa3b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -21,7 +21,7 @@ import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared Map instances. Allows for central setup @@ -87,8 +87,9 @@ public class MapFactoryBean extends AbstractFactoryBean> { Class keyType = null; Class valueType = null; if (this.targetMapClass != null) { - keyType = GenericCollectionTypeResolver.getMapKeyType(this.targetMapClass); - valueType = GenericCollectionTypeResolver.getMapValueType(this.targetMapClass); + ResolvableType mapType = ResolvableType.forClass(this.targetMapClass).asMap(); + keyType = mapType.resolveGeneric(0); + valueType = mapType.resolveGeneric(1); } if (keyType != null || valueType != null) { TypeConverter converter = getBeanTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java index 9ab8703f7e..906d639774 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -21,7 +21,7 @@ import java.util.Set; import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared Set instances. Allows for central setup @@ -86,7 +86,7 @@ public class SetFactoryBean extends AbstractFactoryBean> { } Class valueType = null; if (this.targetSetClass != null) { - valueType = GenericCollectionTypeResolver.getCollectionType(this.targetSetClass); + valueType = ResolvableType.forClass(this.targetSetClass).asCollection().resolveGeneric(); } if (valueType != null) { TypeConverter converter = getBeanTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index b2c52d775f..7d80043aeb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1138,6 +1138,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Class type = descriptor.getDependencyType(); if (type.isArray()) { Class componentType = type.getComponentType(); + ResolvableType resolvableType = descriptor.getResolvableType(); + Class resolvedArrayType = resolvableType.resolve(); + if (resolvedArrayType != null && resolvedArrayType != type) { + type = resolvedArrayType; + componentType = resolvableType.getComponentType().resolve(); + } + if (componentType == null) { + return null; + } Map matchingBeans = findAutowireCandidates(beanName, componentType, new MultiElementDescriptor(descriptor)); if (matchingBeans.isEmpty()) { @@ -1154,7 +1163,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return result; } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { - Class elementType = descriptor.getCollectionType(); + Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); if (elementType == null) { return null; } @@ -1174,11 +1183,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return result; } else if (Map.class == type) { - Class keyType = descriptor.getMapKeyType(); + ResolvableType mapType = descriptor.getResolvableType().asMap(); + Class keyType = mapType.resolveGeneric(0); if (String.class != keyType) { return null; } - Class valueType = descriptor.getMapValueType(); + Class valueType = mapType.resolveGeneric(1); if (valueType == null) { return null; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 373eec7964..8ff50bf0a8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1598,12 +1598,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBean.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryFieldInjectionBean bean = (RepositoryFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1630,12 +1648,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithSubstitutedVariables.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryFieldInjectionBeanWithSubstitutedVariables bean = (RepositoryFieldInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1878,12 +1914,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBean.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryMethodInjectionBean bean = (RepositoryMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1910,12 +1964,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBeanWithSubstitutedVariables.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryMethodInjectionBeanWithSubstitutedVariables bean = (RepositoryMethodInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -2982,6 +3054,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class RepositoryFieldInjectionBean { + @Autowired + public String string; + + @Autowired + public Integer integer; + + @Autowired + public String[] stringArray; + + @Autowired + public Integer[] integerArray; + + @Autowired + public List stringList; + + @Autowired + public List integerList; + + @Autowired + public Map stringMap; + + @Autowired + public Map integerMap; + @Autowired public Repository stringRepository; @@ -3010,6 +3106,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class RepositoryFieldInjectionBeanWithVariables { + @Autowired + public S string; + + @Autowired + public I integer; + + @Autowired + public S[] stringArray; + + @Autowired + public I[] integerArray; + + @Autowired + public List stringList; + + @Autowired + public List integerList; + + @Autowired + public Map stringMap; + + @Autowired + public Map integerMap; + @Autowired public Repository stringRepository; @@ -3106,6 +3226,22 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class RepositoryMethodInjectionBean { + public String string; + + public Integer integer; + + public String[] stringArray; + + public Integer[] integerArray; + + public List stringList; + + public List integerList; + + public Map stringMap; + + public Map integerMap; + public Repository stringRepository; public Repository integerRepository; @@ -3122,6 +3258,46 @@ public class AutowiredAnnotationBeanPostProcessorTests { public Map> integerRepositoryMap; + @Autowired + public void setString(String string) { + this.string = string; + } + + @Autowired + public void setInteger(Integer integer) { + this.integer = integer; + } + + @Autowired + public void setStringArray(String[] stringArray) { + this.stringArray = stringArray; + } + + @Autowired + public void setIntegerArray(Integer[] integerArray) { + this.integerArray = integerArray; + } + + @Autowired + public void setStringList(List stringList) { + this.stringList = stringList; + } + + @Autowired + public void setIntegerList(List integerList) { + this.integerList = integerList; + } + + @Autowired + public void setStringMap(Map stringMap) { + this.stringMap = stringMap; + } + + @Autowired + public void setIntegerMap(Map integerMap) { + this.integerMap = integerMap; + } + @Autowired public void setStringRepository(Repository stringRepository) { this.stringRepository = stringRepository; @@ -3166,6 +3342,22 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class RepositoryMethodInjectionBeanWithVariables { + public S string; + + public I integer; + + public S[] stringArray; + + public I[] integerArray; + + public List stringList; + + public List integerList; + + public Map stringMap; + + public Map integerMap; + public Repository stringRepository; public Repository integerRepository; @@ -3182,6 +3374,46 @@ public class AutowiredAnnotationBeanPostProcessorTests { public Map> integerRepositoryMap; + @Autowired + public void setString(S string) { + this.string = string; + } + + @Autowired + public void setInteger(I integer) { + this.integer = integer; + } + + @Autowired + public void setStringArray(S[] stringArray) { + this.stringArray = stringArray; + } + + @Autowired + public void setIntegerArray(I[] integerArray) { + this.integerArray = integerArray; + } + + @Autowired + public void setStringList(List stringList) { + this.stringList = stringList; + } + + @Autowired + public void setIntegerList(List integerList) { + this.integerList = integerList; + } + + @Autowired + public void setStringMap(Map stringMap) { + this.stringMap = stringMap; + } + + @Autowired + public void setIntegerMap(Map integerMap) { + this.integerMap = integerMap; + } + @Autowired public void setStringRepository(Repository stringRepository) { this.stringRepository = stringRepository; diff --git a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java index 1aebcf4495..6efbb2209e 100644 --- a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java +++ b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -57,8 +57,8 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.CollectionFactory; -import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.jmx.support.JmxUtils; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ClassUtils; @@ -546,7 +546,8 @@ public class MBeanClientInterceptor return convertDataArrayToTargetArray(array, targetClass); } else if (Collection.class.isAssignableFrom(targetClass)) { - Class elementType = GenericCollectionTypeResolver.getCollectionParameterType(parameter); + Class elementType = + ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); if (elementType != null) { return convertDataArrayToTargetCollection(array, targetClass, elementType); } @@ -562,7 +563,8 @@ public class MBeanClientInterceptor return convertDataArrayToTargetArray(array, targetClass); } else if (Collection.class.isAssignableFrom(targetClass)) { - Class elementType = GenericCollectionTypeResolver.getCollectionParameterType(parameter); + Class elementType = + ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); if (elementType != null) { return convertDataArrayToTargetCollection(array, targetClass, elementType); } diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java index edd64ebf2a..6a9fc6ca73 100644 --- a/spring-core/src/main/java/org/springframework/core/Conventions.java +++ b/spring-core/src/main/java/org/springframework/core/Conventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -114,7 +114,7 @@ public abstract class Conventions { pluralize = true; } else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { - valueClass = GenericCollectionTypeResolver.getCollectionParameterType(parameter); + valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); if (valueClass == null) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection parameter type"); @@ -180,7 +180,7 @@ public abstract class Conventions { pluralize = true; } else if (Collection.class.isAssignableFrom(resolvedType)) { - valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method); + valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); if (valueClass == null) { if (!(value instanceof Collection)) { throw new IllegalArgumentException( diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java deleted file mode 100644 index 22d2a873fd..0000000000 --- a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2002-2013 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; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; - -/** - * Helper class for determining element types of collections and maps. - * - *

Mainly intended for usage within the framework, determining the - * target type of values to be added to a collection or map - * (to be able to attempt type conversion if appropriate). - * - * @author Juergen Hoeller - * @author Phillip Webb - * @since 2.0 - * @see ResolvableType - */ -public abstract class GenericCollectionTypeResolver { - - /** - * Determine the generic element type of the given Collection class - * (if it declares one through a generic superclass or generic interface). - * @param collectionClass the collection class to introspect - * @return the generic type, or {@code null} if none - */ - @SuppressWarnings("rawtypes") - public static Class getCollectionType(Class collectionClass) { - return ResolvableType.forClass(collectionClass).asCollection().resolveGeneric(); - } - - /** - * Determine the generic key type of the given Map class - * (if it declares one through a generic superclass or generic interface). - * @param mapClass the map class to introspect - * @return the generic type, or {@code null} if none - */ - @SuppressWarnings("rawtypes") - public static Class getMapKeyType(Class mapClass) { - return ResolvableType.forClass(mapClass).asMap().resolveGeneric(0); - } - - /** - * Determine the generic value type of the given Map class - * (if it declares one through a generic superclass or generic interface). - * @param mapClass the map class to introspect - * @return the generic type, or {@code null} if none - */ - @SuppressWarnings("rawtypes") - public static Class getMapValueType(Class mapClass) { - return ResolvableType.forClass(mapClass).asMap().resolveGeneric(1); - } - - /** - * Determine the generic element type of the given Collection field. - * @param collectionField the collection field to introspect - * @return the generic type, or {@code null} if none - */ - public static Class getCollectionFieldType(Field collectionField) { - return ResolvableType.forField(collectionField).asCollection().resolveGeneric(); - } - - /** - * Determine the generic element type of the given Collection field. - * @param collectionField the collection field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getCollectionFieldType(Field collectionField, int nestingLevel) { - return ResolvableType.forField(collectionField).getNested(nestingLevel).asCollection().resolveGeneric(); - } - - /** - * Determine the generic element type of the given Collection field. - * @param collectionField the collection field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @param typeIndexesPerLevel Map keyed by nesting level, with each value - * expressing the type index for traversal at that level - * @return the generic type, or {@code null} if none - * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels - */ - @Deprecated - public static Class getCollectionFieldType(Field collectionField, int nestingLevel, Map typeIndexesPerLevel) { - return ResolvableType.forField(collectionField).getNested(nestingLevel, typeIndexesPerLevel).asCollection().resolveGeneric(); - } - - /** - * Determine the generic key type of the given Map field. - * @param mapField the map field to introspect - * @return the generic type, or {@code null} if none - */ - public static Class getMapKeyFieldType(Field mapField) { - return ResolvableType.forField(mapField).asMap().resolveGeneric(0); - } - - /** - * Determine the generic key type of the given Map field. - * @param mapField the map field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getMapKeyFieldType(Field mapField, int nestingLevel) { - return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(0); - } - - /** - * Determine the generic key type of the given Map field. - * @param mapField the map field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @param typeIndexesPerLevel Map keyed by nesting level, with each value - * expressing the type index for traversal at that level - * @return the generic type, or {@code null} if none - * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels - */ - @Deprecated - public static Class getMapKeyFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { - return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(0); - } - - /** - * Determine the generic value type of the given Map field. - * @param mapField the map field to introspect - * @return the generic type, or {@code null} if none - */ - public static Class getMapValueFieldType(Field mapField) { - return ResolvableType.forField(mapField).asMap().resolveGeneric(1); - } - - /** - * Determine the generic value type of the given Map field. - * @param mapField the map field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getMapValueFieldType(Field mapField, int nestingLevel) { - return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(1); - } - - /** - * Determine the generic value type of the given Map field. - * @param mapField the map field to introspect - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @param typeIndexesPerLevel Map keyed by nesting level, with each value - * expressing the type index for traversal at that level - * @return the generic type, or {@code null} if none - * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels - */ - @Deprecated - public static Class getMapValueFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { - return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(1); - } - - /** - * Determine the generic element type of the given Collection parameter. - * @param methodParam the method parameter specification - * @return the generic type, or {@code null} if none - */ - public static Class getCollectionParameterType(MethodParameter methodParam) { - return ResolvableType.forMethodParameter(methodParam).asCollection().resolveGeneric(); - } - - /** - * Determine the generic key type of the given Map parameter. - * @param methodParam the method parameter specification - * @return the generic type, or {@code null} if none - */ - public static Class getMapKeyParameterType(MethodParameter methodParam) { - return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(0); - } - - /** - * Determine the generic value type of the given Map parameter. - * @param methodParam the method parameter specification - * @return the generic type, or {@code null} if none - */ - public static Class getMapValueParameterType(MethodParameter methodParam) { - return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(1); - } - - /** - * Determine the generic element type of the given Collection return type. - * @param method the method to check the return type for - * @return the generic type, or {@code null} if none - */ - public static Class getCollectionReturnType(Method method) { - return ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); - } - - /** - * Determine the generic element type of the given Collection return type. - *

If the specified nesting level is higher than 1, the element type of - * a nested Collection/Map will be analyzed. - * @param method the method to check the return type for - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getCollectionReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asCollection().resolveGeneric(); - } - - /** - * Determine the generic key type of the given Map return type. - * @param method the method to check the return type for - * @return the generic type, or {@code null} if none - */ - public static Class getMapKeyReturnType(Method method) { - return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(0); - } - - /** - * Determine the generic key type of the given Map return type. - * @param method the method to check the return type for - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getMapKeyReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(0); - } - - /** - * Determine the generic value type of the given Map return type. - * @param method the method to check the return type for - * @return the generic type, or {@code null} if none - */ - public static Class getMapValueReturnType(Method method) { - return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(1); - } - - /** - * Determine the generic value type of the given Map return type. - * @param method the method to check the return type for - * @param nestingLevel the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) - * @return the generic type, or {@code null} if none - */ - public static Class getMapValueReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(1); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index c76c911094..a66d732c0f 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -39,7 +39,6 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Sam Brannen * @author Phillip Webb * @since 2.5.2 - * @see GenericCollectionTypeResolver */ public abstract class GenericTypeResolver { diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 57bee0cb23..14d18544f1 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -54,7 +54,6 @@ import org.springframework.util.ClassUtils; * @author Sam Brannen * @author Sebastien Deleuze * @since 2.0 - * @see GenericCollectionTypeResolver * @see org.springframework.core.annotation.SynthesizingMethodParameter */ public class MethodParameter { diff --git a/spring-core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java deleted file mode 100644 index 59fb22ea11..0000000000 --- a/spring-core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2002-2014 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; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.core.io.Resource; -import org.springframework.tests.sample.objects.GenericObject; - -import static org.junit.Assert.*; - -/** - * @author Serge Bogatyrjov - * @author Juergen Hoeller - * @author Sam Brannen - */ -public class GenericCollectionTypeResolverTests { - - protected Class targetClass; - - protected String[] methods; - - protected Type[] expectedResults; - - @Before - public void setUp() throws Exception { - this.targetClass = Foo.class; - this.methods = new String[] { "a", "b", "b2", "b3", "c", "d", "d2", "d3", "e", - "e2", "e3" }; - this.expectedResults = new Class[] { Integer.class, null, Set.class, Set.class, - null, Integer.class, Integer.class, Integer.class, Integer.class, - Integer.class, Integer.class }; - } - - protected void executeTest(String methodName) throws NoSuchMethodException { - for (int i = 0; i < this.methods.length; i++) { - if (methodName.equals(this.methods[i])) { - Method method = this.targetClass.getMethod(methodName); - Type type = getType(method); - assertEquals(this.expectedResults[i], type); - return; - } - } - throw new IllegalStateException("Bad test data"); - } - - protected Type getType(Method method) { - return GenericCollectionTypeResolver.getMapValueReturnType(method); - } - - @Test - public void a() throws Exception { - executeTest("a"); - } - - @Test - public void b() throws Exception { - executeTest("b"); - } - - @Test - public void b2() throws Exception { - executeTest("b2"); - } - - @Test - public void b3() throws Exception { - executeTest("b3"); - } - - @Test - public void c() throws Exception { - executeTest("c"); - } - - @Test - public void d() throws Exception { - executeTest("d"); - } - - @Test - public void d2() throws Exception { - executeTest("d2"); - } - - @Test - public void d3() throws Exception { - executeTest("d3"); - } - - @Test - public void e() throws Exception { - executeTest("e"); - } - - @Test - public void e2() throws Exception { - executeTest("e2"); - } - - @Test - public void e3() throws Exception { - executeTest("e3"); - } - - @Test - public void programmaticListIntrospection() throws Exception { - Method setter = GenericObject.class.getMethod("setResourceList", List.class); - assertEquals( - Resource.class, - GenericCollectionTypeResolver.getCollectionParameterType(new MethodParameter( - setter, 0))); - - Method getter = GenericObject.class.getMethod("getResourceList"); - assertEquals(Resource.class, - GenericCollectionTypeResolver.getCollectionReturnType(getter)); - } - - @Test - public void classResolution() { - assertEquals(String.class, - GenericCollectionTypeResolver.getCollectionType(CustomSet.class)); - assertEquals(String.class, - GenericCollectionTypeResolver.getMapKeyType(CustomMap.class)); - assertEquals(Integer.class, - GenericCollectionTypeResolver.getMapValueType(CustomMap.class)); - } - - private static abstract class CustomSet extends AbstractSet { - } - - private static abstract class CustomMap extends AbstractMap { - } - - private static abstract class OtherCustomMap implements Map { - } - - @SuppressWarnings("rawtypes") - private static interface Foo { - - Map a(); - - Map b(); - - Map b2(); - - Map b3(); - - Map c(); - - CustomMap d(); - - CustomMap d2(); - - CustomMap d3(); - - OtherCustomMap e(); - - OtherCustomMap e2(); - - OtherCustomMap e3(); - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java index ef3770c0b2..d409496bd7 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -22,8 +22,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; -import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.util.WebUtils; @@ -130,7 +130,7 @@ public abstract class MultipartResolutionDelegate { private static Class getCollectionParameterType(MethodParameter methodParam) { Class paramType = methodParam.getNestedParameterType(); if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){ - Class valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam); + Class valueType = ResolvableType.forMethodParameter(methodParam).asCollection().resolveGeneric(); if (valueType != null) { return valueType; }