Browse Source

Consistent type variable resolution for arrays/collections (in particular at field level)

Dropping GenericCollectionTypeResolver in favor of direct ResolvableType usage.

Issue: SPR-15160
pull/1261/merge
Juergen Hoeller 8 years ago
parent
commit
5e946c2700
  1. 43
      spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
  2. 6
      spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java
  3. 9
      spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java
  4. 6
      spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java
  5. 16
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
  6. 232
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
  7. 10
      spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
  8. 6
      spring-core/src/main/java/org/springframework/core/Conventions.java
  9. 274
      spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
  10. 3
      spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
  11. 3
      spring-core/src/main/java/org/springframework/core/MethodParameter.java
  12. 188
      spring-core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java
  13. 6
      spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java

43
spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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) {

6
spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<List<Object>> { @@ -86,7 +86,7 @@ public class ListFactoryBean extends AbstractFactoryBean<List<Object>> {
}
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();

9
spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<Map<Object, Object>> { @@ -87,8 +87,9 @@ public class MapFactoryBean extends AbstractFactoryBean<Map<Object, Object>> {
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();

6
spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<Set<Object>> { @@ -86,7 +86,7 @@ public class SetFactoryBean extends AbstractFactoryBean<Set<Object>> {
}
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();

16
spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

@ -1138,6 +1138,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -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<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
@ -1154,7 +1163,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -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 @@ -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;
}

232
spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

@ -1598,12 +1598,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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<String> stringList;
@Autowired
public List<Integer> integerList;
@Autowired
public Map<String, String> stringMap;
@Autowired
public Map<String, Integer> integerMap;
@Autowired
public Repository<String> stringRepository;
@ -3010,6 +3106,30 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -3010,6 +3106,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryFieldInjectionBeanWithVariables<S, I> {
@Autowired
public S string;
@Autowired
public I integer;
@Autowired
public S[] stringArray;
@Autowired
public I[] integerArray;
@Autowired
public List<S> stringList;
@Autowired
public List<I> integerList;
@Autowired
public Map<String, S> stringMap;
@Autowired
public Map<String, I> integerMap;
@Autowired
public Repository<S> stringRepository;
@ -3106,6 +3226,22 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -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<String> stringList;
public List<Integer> integerList;
public Map<String, String> stringMap;
public Map<String, Integer> integerMap;
public Repository<String> stringRepository;
public Repository<Integer> integerRepository;
@ -3122,6 +3258,46 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -3122,6 +3258,46 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Map<String, Repository<Integer>> 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<String> stringList) {
this.stringList = stringList;
}
@Autowired
public void setIntegerList(List<Integer> integerList) {
this.integerList = integerList;
}
@Autowired
public void setStringMap(Map<String, String> stringMap) {
this.stringMap = stringMap;
}
@Autowired
public void setIntegerMap(Map<String, Integer> integerMap) {
this.integerMap = integerMap;
}
@Autowired
public void setStringRepository(Repository<String> stringRepository) {
this.stringRepository = stringRepository;
@ -3166,6 +3342,22 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -3166,6 +3342,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryMethodInjectionBeanWithVariables<S, I> {
public S string;
public I integer;
public S[] stringArray;
public I[] integerArray;
public List<S> stringList;
public List<I> integerList;
public Map<String, S> stringMap;
public Map<String, I> integerMap;
public Repository<S> stringRepository;
public Repository<I> integerRepository;
@ -3182,6 +3374,46 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -3182,6 +3374,46 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Map<String, Repository<I>> 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<S> stringList) {
this.stringList = stringList;
}
@Autowired
public void setIntegerList(List<I> integerList) {
this.integerList = integerList;
}
@Autowired
public void setStringMap(Map<String, S> stringMap) {
this.stringMap = stringMap;
}
@Autowired
public void setIntegerMap(Map<String, I> integerMap) {
this.integerMap = integerMap;
}
@Autowired
public void setStringRepository(Repository<S> stringRepository) {
this.stringRepository = stringRepository;

10
spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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);
}

6
spring-core/src/main/java/org/springframework/core/Conventions.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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(

274
spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java

@ -1,274 +0,0 @@ @@ -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.
*
* <p>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<? extends Collection> 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<? extends Map> 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<? extends Map> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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.
* <p>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);
}
}

3
spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 {

3
spring-core/src/main/java/org/springframework/core/MethodParameter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 {

188
spring-core/src/test/java/org/springframework/core/GenericCollectionTypeResolverTests.java

@ -1,188 +0,0 @@ @@ -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<T> extends AbstractSet<String> {
}
private static abstract class CustomMap<T> extends AbstractMap<String, Integer> {
}
private static abstract class OtherCustomMap<T> implements Map<String, Integer> {
}
@SuppressWarnings("rawtypes")
private static interface Foo {
Map<String, Integer> a();
Map<?, ?> b();
Map<?, ? extends Set> b2();
Map<?, ? super Set> b3();
Map c();
CustomMap<Date> d();
CustomMap<?> d2();
CustomMap d3();
OtherCustomMap<Date> e();
OtherCustomMap<?> e2();
OtherCustomMap e3();
}
}

6
spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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;
}

Loading…
Cancel
Save