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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
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.Property;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
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 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 */
|
||||
private Object object;
|
||||
|
|
@ -209,12 +224,17 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
*/
|
||||
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
||||
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.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
|
||||
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object);
|
||||
this.nestedBeanWrappers = null;
|
||||
this.typeConverterDelegate = new TypeConverterDelegate(this, object);
|
||||
setIntrospectionClass(object.getClass());
|
||||
this.typeConverterDelegate = new TypeConverterDelegate(this, this.object);
|
||||
setIntrospectionClass(this.object.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -549,7 +569,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
|
||||
String canonicalName = tokens.canonicalName;
|
||||
Object propertyValue = getPropertyValue(tokens);
|
||||
if (propertyValue == null) {
|
||||
if (propertyValue == null ||
|
||||
(propertyValue.getClass().equals(javaUtilOptionalClass) && OptionalUnwrapper.isEmpty(propertyValue))) {
|
||||
if (isAutoGrowNestedPaths()) {
|
||||
propertyValue = setDefaultValue(tokens);
|
||||
}
|
||||
|
|
@ -1172,10 +1193,6 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Inner class for internal use
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private static class PropertyTokenHolder {
|
||||
|
||||
public String canonicalName;
|
||||
|
|
@ -1185,4 +1202,22 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
|
|
@ -1537,9 +1538,45 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor
|
|||
BeanWrapperImpl bwi = new BeanWrapperImpl(foo);
|
||||
bwi.setPropertyValue("object", "a String");
|
||||
assertEquals("a String", foo.value);
|
||||
assertTrue(foo.getObject() == 8);
|
||||
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
|
||||
public void testGenericArraySetter() {
|
||||
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 T[] items;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintValidator;
|
||||
|
|
@ -211,6 +212,18 @@ public class ValidatorFactoryTests {
|
|||
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
|
||||
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 {
|
||||
|
||||
private String value;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintValidator;
|
||||
|
|
@ -213,6 +214,18 @@ public class ValidatorFactoryTests {
|
|||
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
|
||||
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 {
|
||||
|
||||
private String value;
|
||||
|
|
|
|||
Loading…
Reference in New Issue