BeanWrapper supports traversal of nested paths with Java 8 Optional declarations
Issue: SPR-12241
This commit is contained in:
parent
281b243b88
commit
0934751d7a
|
|
@ -33,6 +33,7 @@ import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
@ -44,7 +45,9 @@ import org.springframework.core.convert.ConversionException;
|
||||||
import org.springframework.core.convert.ConverterNotFoundException;
|
import org.springframework.core.convert.ConverterNotFoundException;
|
||||||
import org.springframework.core.convert.Property;
|
import org.springframework.core.convert.Property;
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
import org.springframework.lang.UsesJava8;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
@ -91,6 +94,18 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
||||||
*/
|
*/
|
||||||
private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
|
private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
|
||||||
|
|
||||||
|
private static Class<?> javaUtilOptionalClass = null;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
javaUtilOptionalClass =
|
||||||
|
ClassUtils.forName("java.util.Optional", BeanWrapperImpl.class.getClassLoader());
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex) {
|
||||||
|
// Java 8 not available - Optional references simply not supported then.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** The wrapped object */
|
/** The wrapped object */
|
||||||
private Object object;
|
private Object object;
|
||||||
|
|
@ -209,12 +224,17 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
||||||
*/
|
*/
|
||||||
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
||||||
Assert.notNull(object, "Bean object must not be null");
|
Assert.notNull(object, "Bean object must not be null");
|
||||||
this.object = object;
|
if (object.getClass().equals(javaUtilOptionalClass)) {
|
||||||
|
this.object = OptionalUnwrapper.unwrap(object);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.object = object;
|
||||||
|
}
|
||||||
this.nestedPath = (nestedPath != null ? nestedPath : "");
|
this.nestedPath = (nestedPath != null ? nestedPath : "");
|
||||||
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
|
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object);
|
||||||
this.nestedBeanWrappers = null;
|
this.nestedBeanWrappers = null;
|
||||||
this.typeConverterDelegate = new TypeConverterDelegate(this, object);
|
this.typeConverterDelegate = new TypeConverterDelegate(this, this.object);
|
||||||
setIntrospectionClass(object.getClass());
|
setIntrospectionClass(this.object.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -549,7 +569,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
||||||
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
|
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
|
||||||
String canonicalName = tokens.canonicalName;
|
String canonicalName = tokens.canonicalName;
|
||||||
Object propertyValue = getPropertyValue(tokens);
|
Object propertyValue = getPropertyValue(tokens);
|
||||||
if (propertyValue == null) {
|
if (propertyValue == null ||
|
||||||
|
(propertyValue.getClass().equals(javaUtilOptionalClass) && OptionalUnwrapper.isEmpty(propertyValue))) {
|
||||||
if (isAutoGrowNestedPaths()) {
|
if (isAutoGrowNestedPaths()) {
|
||||||
propertyValue = setDefaultValue(tokens);
|
propertyValue = setDefaultValue(tokens);
|
||||||
}
|
}
|
||||||
|
|
@ -1172,10 +1193,6 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
// Inner class for internal use
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
private static class PropertyTokenHolder {
|
private static class PropertyTokenHolder {
|
||||||
|
|
||||||
public String canonicalName;
|
public String canonicalName;
|
||||||
|
|
@ -1185,4 +1202,22 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
||||||
public String[] keys;
|
public String[] keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class to avoid a hard dependency on Java 8.
|
||||||
|
*/
|
||||||
|
@UsesJava8
|
||||||
|
private static class OptionalUnwrapper {
|
||||||
|
|
||||||
|
public static Object unwrap(Object optionalObject) {
|
||||||
|
Optional<?> optional = (Optional<?>) optionalObject;
|
||||||
|
Assert.isTrue(optional.isPresent(), "Optional value must be present");
|
||||||
|
return optional.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEmpty(Object optionalObject) {
|
||||||
|
return !((Optional<?>) optionalObject).isPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
|
|
@ -1537,9 +1538,45 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor
|
||||||
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
|
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
|
||||||
bwi.setPropertyValue("object", "a String");
|
bwi.setPropertyValue("object", "a String");
|
||||||
assertEquals("a String", foo.value);
|
assertEquals("a String", foo.value);
|
||||||
|
assertTrue(foo.getObject() == 8);
|
||||||
assertEquals(8, bwi.getPropertyValue("object"));
|
assertEquals(8, bwi.getPropertyValue("object"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetterWithOptional() {
|
||||||
|
GetterWithOptional foo = new GetterWithOptional();
|
||||||
|
TestBean tb = new TestBean("x");
|
||||||
|
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
|
||||||
|
|
||||||
|
bwi.setPropertyValue("object", tb);
|
||||||
|
assertSame(tb, foo.value);
|
||||||
|
assertSame(tb, foo.getObject().get());
|
||||||
|
assertSame(tb, ((Optional<String>) bwi.getPropertyValue("object")).get());
|
||||||
|
assertEquals("x", foo.value.getName());
|
||||||
|
assertEquals("x", foo.getObject().get().getName());
|
||||||
|
assertEquals("x", bwi.getPropertyValue("object.name"));
|
||||||
|
|
||||||
|
bwi.setPropertyValue("object.name", "y");
|
||||||
|
assertSame(tb, foo.value);
|
||||||
|
assertSame(tb, foo.getObject().get());
|
||||||
|
assertSame(tb, ((Optional<String>) bwi.getPropertyValue("object")).get());
|
||||||
|
assertEquals("y", foo.value.getName());
|
||||||
|
assertEquals("y", foo.getObject().get().getName());
|
||||||
|
assertEquals("y", bwi.getPropertyValue("object.name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetterWithOptionalAndAutoGrowing() {
|
||||||
|
GetterWithOptional foo = new GetterWithOptional();
|
||||||
|
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
|
||||||
|
bwi.setAutoGrowNestedPaths(true);
|
||||||
|
|
||||||
|
bwi.setPropertyValue("object.name", "x");
|
||||||
|
assertEquals("x", foo.value.getName());
|
||||||
|
assertEquals("x", foo.getObject().get().getName());
|
||||||
|
assertEquals("x", bwi.getPropertyValue("object.name"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenericArraySetter() {
|
public void testGenericArraySetter() {
|
||||||
SkipReaderStub foo = new SkipReaderStub();
|
SkipReaderStub foo = new SkipReaderStub();
|
||||||
|
|
@ -1967,6 +2004,20 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class GetterWithOptional {
|
||||||
|
|
||||||
|
public TestBean value;
|
||||||
|
|
||||||
|
public void setObject(TestBean object) {
|
||||||
|
this.value = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TestBean> getObject() {
|
||||||
|
return Optional.ofNullable(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class SkipReaderStub<T> {
|
public static class SkipReaderStub<T> {
|
||||||
|
|
||||||
public T[] items;
|
public T[] items;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.validation.Constraint;
|
import javax.validation.Constraint;
|
||||||
import javax.validation.ConstraintValidator;
|
import javax.validation.ConstraintValidator;
|
||||||
|
|
@ -211,6 +212,18 @@ public class ValidatorFactoryTests {
|
||||||
assertNull(rejected);
|
assertNull(rejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidationWithOptionalField() throws Exception {
|
||||||
|
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||||
|
validator.afterPropertiesSet();
|
||||||
|
|
||||||
|
MainBeanWithOptional mainBean = new MainBeanWithOptional();
|
||||||
|
Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean");
|
||||||
|
validator.validate(mainBean, errors);
|
||||||
|
Object rejected = errors.getFieldValue("inner.value");
|
||||||
|
assertNull(rejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@NameAddressValid
|
@NameAddressValid
|
||||||
public static class ValidPerson {
|
public static class ValidPerson {
|
||||||
|
|
@ -318,6 +331,17 @@ public class ValidatorFactoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MainBeanWithOptional {
|
||||||
|
|
||||||
|
@InnerValid
|
||||||
|
private InnerBean inner = new InnerBean();
|
||||||
|
|
||||||
|
public Optional<InnerBean> getInner() {
|
||||||
|
return Optional.ofNullable(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class InnerBean {
|
public static class InnerBean {
|
||||||
|
|
||||||
private String value;
|
private String value;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.validation.Constraint;
|
import javax.validation.Constraint;
|
||||||
import javax.validation.ConstraintValidator;
|
import javax.validation.ConstraintValidator;
|
||||||
|
|
@ -213,6 +214,18 @@ public class ValidatorFactoryTests {
|
||||||
assertNull(rejected);
|
assertNull(rejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidationWithOptionalField() throws Exception {
|
||||||
|
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||||
|
validator.afterPropertiesSet();
|
||||||
|
|
||||||
|
MainBeanWithOptional mainBean = new MainBeanWithOptional();
|
||||||
|
Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean");
|
||||||
|
validator.validate(mainBean, errors);
|
||||||
|
Object rejected = errors.getFieldValue("inner.value");
|
||||||
|
assertNull(rejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@NameAddressValid
|
@NameAddressValid
|
||||||
public static class ValidPerson {
|
public static class ValidPerson {
|
||||||
|
|
@ -320,6 +333,17 @@ public class ValidatorFactoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MainBeanWithOptional {
|
||||||
|
|
||||||
|
@InnerValid
|
||||||
|
private InnerBean inner = new InnerBean();
|
||||||
|
|
||||||
|
public Optional<InnerBean> getInner() {
|
||||||
|
return Optional.ofNullable(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class InnerBean {
|
public static class InnerBean {
|
||||||
|
|
||||||
private String value;
|
private String value;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue