Skip to content

Commit

Permalink
BeanWrapper supports traversal of nested paths with Java 8 Optional d…
Browse files Browse the repository at this point in the history
…eclarations

Issue: SPR-12241
  • Loading branch information
jhoeller committed Sep 24, 2014
1 parent 281b243 commit 0934751
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 9 deletions.
Expand Up @@ -33,6 +33,7 @@
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;
Expand All @@ -44,7 +45,9 @@
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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -209,12 +224,17 @@ public void setWrappedInstance(Object object) {
*/
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
Expand Down Expand Up @@ -549,7 +569,8 @@ private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) {
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);
}
Expand Down Expand Up @@ -1172,10 +1193,6 @@ public String toString() {
}


//---------------------------------------------------------------------
// Inner class for internal use
//---------------------------------------------------------------------

private static class PropertyTokenHolder {

public String canonicalName;
Expand All @@ -1185,4 +1202,22 @@ private static class PropertyTokenHolder {
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();
}
}

}
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -1537,9 +1538,45 @@ public void testPropertyTypeMismatch() {
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();
Expand Down Expand Up @@ -1967,6 +2004,20 @@ public Integer getObject() {
}


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;
Expand Down
Expand Up @@ -25,6 +25,7 @@
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;
Expand Down Expand Up @@ -211,6 +212,18 @@ public void testInnerBeanValidation() throws Exception {
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 {
Expand Down Expand Up @@ -318,6 +331,17 @@ public InnerBean getInner() {
}


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;
Expand Down
Expand Up @@ -25,6 +25,7 @@
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;
Expand Down Expand Up @@ -213,6 +214,18 @@ public void testInnerBeanValidation() throws Exception {
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 {
Expand Down Expand Up @@ -320,6 +333,17 @@ public InnerBean getInner() {
}


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;
Expand Down

0 comments on commit 0934751

Please sign in to comment.