BeanWrapper supports traversal of nested paths with Java 8 Optional declarations

Issue: SPR-12241
This commit is contained in:
Juergen Hoeller 2014-09-24 17:56:24 +02:00
parent 281b243b88
commit 0934751d7a
4 changed files with 143 additions and 9 deletions

View File

@ -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();
}
}
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;