Browse Source

Apply array editor to collection of same element type as well

Closes gh-24845
pull/30993/head
Juergen Hoeller 2 years ago
parent
commit
84b3335e71
  1. 41
      spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
  2. 6
      spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java
  3. 2
      spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java
  4. 12
      spring-context/src/test/java/org/springframework/context/support/Service.java
  5. 1
      spring-context/src/test/resources/org/springframework/context/support/test/contextA.xml

41
spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java

@ -35,6 +35,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils; import org.springframework.util.NumberUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -139,13 +140,17 @@ class TypeConverterDelegate {
// Value not of required type? // Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType)) {
convertedValue instanceof String text) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor(); TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) { if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType(); Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) { if (convertedValue instanceof String text) {
convertedValue = StringUtils.commaDelimitedListToStringArray(text); if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray(text);
}
if (editor == null && String.class != elementType) {
editor = findDefaultEditor(Array.newInstance(elementType, 0).getClass());
}
} }
} }
} }
@ -166,11 +171,23 @@ class TypeConverterDelegate {
} }
else if (requiredType.isArray()) { else if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements. // Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String text && Enum.class.isAssignableFrom(requiredType.getComponentType())) { if (convertedValue instanceof String text &&
Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray(text); convertedValue = StringUtils.commaDelimitedListToStringArray(text);
} }
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType()); return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
} }
else if (convertedValue.getClass().isArray()) {
if (Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
else if (Collection.class.isAssignableFrom(requiredType)) {
convertedValue = convertToTypedCollection(CollectionUtils.arrayToList(convertedValue),
propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
}
else if (convertedValue instanceof Collection<?> coll) { else if (convertedValue instanceof Collection<?> coll) {
// Convert elements to target type, if determined. // Convert elements to target type, if determined.
convertedValue = convertToTypedCollection(coll, propertyName, requiredType, typeDescriptor); convertedValue = convertToTypedCollection(coll, propertyName, requiredType, typeDescriptor);
@ -181,10 +198,6 @@ class TypeConverterDelegate {
convertedValue = convertToTypedMap(map, propertyName, requiredType, typeDescriptor); convertedValue = convertToTypedMap(map, propertyName, requiredType, typeDescriptor);
standardConversion = true; standardConversion = true;
} }
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
// We can stringify any primitive value... // We can stringify any primitive value...
return (T) convertedValue.toString(); return (T) convertedValue.toString();
@ -501,12 +514,11 @@ class TypeConverterDelegate {
Collection<Object> convertedCopy; Collection<Object> convertedCopy;
try { try {
if (approximable) { if (approximable && requiredType.isInstance(original)) {
convertedCopy = CollectionFactory.createApproximateCollection(original, original.size()); convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
} }
else { else {
convertedCopy = (Collection<Object>) convertedCopy = CollectionFactory.createCollection(requiredType, original.size());
ReflectionUtils.accessibleConstructor(requiredType).newInstance();
} }
} }
catch (Throwable ex) { catch (Throwable ex) {
@ -576,12 +588,11 @@ class TypeConverterDelegate {
Map<Object, Object> convertedCopy; Map<Object, Object> convertedCopy;
try { try {
if (approximable) { if (approximable && requiredType.isInstance(original)) {
convertedCopy = CollectionFactory.createApproximateMap(original, original.size()); convertedCopy = CollectionFactory.createApproximateMap(original, original.size());
} }
else { else {
convertedCopy = (Map<Object, Object>) convertedCopy = CollectionFactory.createMap(requiredType, original.size());
ReflectionUtils.accessibleConstructor(requiredType).newInstance();
} }
} }
catch (Throwable ex) { catch (Throwable ex) {

6
spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java

@ -222,11 +222,12 @@ public class ClassPathXmlApplicationContextTests {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD); ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
Service service = ctx.getBean("service", Service.class); Service service = ctx.getBean("service", Service.class);
assertThat(service.getResources()).containsExactlyInAnyOrder(contextA, contextB, contextC); assertThat(service.getResources()).containsExactlyInAnyOrder(contextA, contextB, contextC);
assertThat(service.getResourceSet()).containsExactlyInAnyOrder(contextA, contextB, contextC);
ctx.close(); ctx.close();
} }
@Test @Test
void childWithProxy() throws Exception { void childWithProxy() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD); ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext( ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(
new String[] {CHILD_WITH_PROXY_CONTEXT}, ctx); new String[] {CHILD_WITH_PROXY_CONTEXT}, ctx);
@ -337,8 +338,7 @@ public class ClassPathXmlApplicationContextTests {
}; };
ResourceTestBean resource1 = (ResourceTestBean) ctx.getBean("resource1"); ResourceTestBean resource1 = (ResourceTestBean) ctx.getBean("resource1");
ResourceTestBean resource2 = (ResourceTestBean) ctx.getBean("resource2"); ResourceTestBean resource2 = (ResourceTestBean) ctx.getBean("resource2");
boolean condition = resource1.getResource() instanceof ClassPathResource; assertThat(resource1.getResource()).isInstanceOf(ClassPathResource.class);
assertThat(condition).isTrue();
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
FileCopyUtils.copy(new InputStreamReader(resource1.getResource().getInputStream()), writer); FileCopyUtils.copy(new InputStreamReader(resource1.getResource().getInputStream()), writer);
assertThat(writer.toString()).isEqualTo("contexttest"); assertThat(writer.toString()).isEqualTo("contexttest");

2
spring-context/src/test/java/org/springframework/context/support/ConversionServiceFactoryBeanTests.java

@ -118,7 +118,7 @@ class ConversionServiceFactoryBeanTests {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(fileName, getClass()); ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(fileName, getClass());
ResourceTestBean tb = ctx.getBean("resourceTestBean", ResourceTestBean.class); ResourceTestBean tb = ctx.getBean("resourceTestBean", ResourceTestBean.class);
assertThat(resourceClass.isInstance(tb.getResource())).isTrue(); assertThat(resourceClass.isInstance(tb.getResource())).isTrue();
assertThat(tb.getResourceArray()).isNotEmpty(); assertThat(tb.getResourceArray()).hasSize(1);
assertThat(resourceClass.isInstance(tb.getResourceArray()[0])).isTrue(); assertThat(resourceClass.isInstance(tb.getResourceArray()[0])).isTrue();
assertThat(tb.getResourceMap()).hasSize(1); assertThat(tb.getResourceMap()).hasSize(1);
assertThat(resourceClass.isInstance(tb.getResourceMap().get("key1"))).isTrue(); assertThat(resourceClass.isInstance(tb.getResourceMap().get("key1"))).isTrue();

12
spring-context/src/test/java/org/springframework/context/support/Service.java

@ -16,6 +16,8 @@
package org.springframework.context.support; package org.springframework.context.support;
import java.util.Set;
import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -37,6 +39,8 @@ public class Service implements ApplicationContextAware, MessageSourceAware, Dis
private Resource[] resources; private Resource[] resources;
private Set<Resource> resourceSet;
private boolean properlyDestroyed = false; private boolean properlyDestroyed = false;
@ -65,6 +69,14 @@ public class Service implements ApplicationContextAware, MessageSourceAware, Dis
return resources; return resources;
} }
public void setResourceSet(Set<Resource> resourceSet) {
this.resourceSet = resourceSet;
}
public Set<Resource> getResourceSet() {
return resourceSet;
}
@Override @Override
public void destroy() { public void destroy() {

1
spring-context/src/test/resources/org/springframework/context/support/test/contextA.xml

@ -17,6 +17,7 @@
<bean name="service" class="org.springframework.context.support.Service"> <bean name="service" class="org.springframework.context.support.Service">
<property name="resources" value="/org/springframework/context/support/test/context*.xml"/> <property name="resources" value="/org/springframework/context/support/test/context*.xml"/>
<property name="resourceSet" value="/org/springframework/context/support/test/context*.xml"/>
</bean> </bean>
<bean name="service2" class="org.springframework.context.support.Service" autowire="byName" depends-on="service"> <bean name="service2" class="org.springframework.context.support.Service" autowire="byName" depends-on="service">

Loading…
Cancel
Save