@@ -21,6 +21,7 @@ import java.io.IOException; | |||
import java.io.Serializable; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.lang.reflect.Method; | |||
import java.util.Arrays; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
@@ -103,15 +104,16 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
} | |||
} | |||
private static class BeanPropertyDefinition<T, V> | |||
private abstract static class AbstractBeanPropertyDefinition<T, V> | |||
implements PropertyDefinition<T, V> { | |||
private final PropertyDescriptor descriptor; | |||
private final BeanPropertySet<T> propertySet; | |||
private final Class<?> propertyHolderType; | |||
public BeanPropertyDefinition(BeanPropertySet<T> propertySet, | |||
PropertyDescriptor descriptor) { | |||
public AbstractBeanPropertyDefinition(BeanPropertySet<T> propertySet, | |||
Class<?> propertyHolderType, PropertyDescriptor descriptor) { | |||
this.propertySet = propertySet; | |||
this.propertyHolderType = propertyHolderType; | |||
this.descriptor = descriptor; | |||
if (descriptor.getReadMethod() == null) { | |||
@@ -120,13 +122,52 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
+ propertySet.beanType + "." | |||
+ descriptor.getName()); | |||
} | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public Class<V> getType() { | |||
return (Class<V>) ReflectTools | |||
.convertPrimitiveType(descriptor.getPropertyType()); | |||
} | |||
@Override | |||
public String getName() { | |||
return descriptor.getName(); | |||
} | |||
@Override | |||
public String getCaption() { | |||
return SharedUtil.propertyIdToHumanFriendly(getName()); | |||
} | |||
@Override | |||
public BeanPropertySet<T> getPropertySet() { | |||
return propertySet; | |||
} | |||
protected PropertyDescriptor getDescriptor() { | |||
return descriptor; | |||
} | |||
@Override | |||
public Class<?> getPropertyHolderType() { | |||
return propertyHolderType; | |||
} | |||
} | |||
private static class BeanPropertyDefinition<T, V> | |||
extends AbstractBeanPropertyDefinition<T, V> { | |||
public BeanPropertyDefinition(BeanPropertySet<T> propertySet, | |||
Class<T> propertyHolderType, PropertyDescriptor descriptor) { | |||
super(propertySet, propertyHolderType, descriptor); | |||
} | |||
@Override | |||
public ValueProvider<T, V> getGetter() { | |||
return bean -> { | |||
Method readMethod = descriptor.getReadMethod(); | |||
Method readMethod = getDescriptor().getReadMethod(); | |||
Object value = invokeWrapExceptions(readMethod, bean); | |||
return getType().cast(value); | |||
}; | |||
@@ -134,40 +175,70 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
@Override | |||
public Optional<Setter<T, V>> getSetter() { | |||
if (descriptor.getWriteMethod() == null) { | |||
if (getDescriptor().getWriteMethod() == null) { | |||
return Optional.empty(); | |||
} | |||
Setter<T, V> setter = (bean, value) -> { | |||
// Do not "optimize" this getter call, | |||
// if its done outside the code block, that will produce | |||
// NotSerializableException because of some lambda compilation magic | |||
Method innerSetter = descriptor.getWriteMethod(); | |||
// NotSerializableException because of some lambda compilation | |||
// magic | |||
Method innerSetter = getDescriptor().getWriteMethod(); | |||
invokeWrapExceptions(innerSetter, bean, value); | |||
}; | |||
return Optional.of(setter); | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public Class<V> getType() { | |||
return (Class<V>) ReflectTools | |||
.convertPrimitiveType(descriptor.getPropertyType()); | |||
private Object writeReplace() { | |||
/* | |||
* Instead of serializing this actual property definition, only | |||
* serialize a DTO that when deserialized will get the corresponding | |||
* property definition from the cache. | |||
*/ | |||
return new SerializedPropertyDefinition(getPropertySet().beanType, | |||
getName()); | |||
} | |||
} | |||
@Override | |||
public String getName() { | |||
return descriptor.getName(); | |||
private static class NestedBeanPropertyDefinition<T, V> | |||
extends AbstractBeanPropertyDefinition<T, V> { | |||
private final PropertyDefinition<T, ?> parent; | |||
public NestedBeanPropertyDefinition(BeanPropertySet<T> propertySet, | |||
PropertyDefinition<T, ?> parent, | |||
PropertyDescriptor descriptor) { | |||
super(propertySet, parent.getType(), descriptor); | |||
this.parent = parent; | |||
} | |||
@Override | |||
public String getCaption() { | |||
return SharedUtil.propertyIdToHumanFriendly(getName()); | |||
public ValueProvider<T, V> getGetter() { | |||
return bean -> { | |||
Method readMethod = getDescriptor().getReadMethod(); | |||
Object value = invokeWrapExceptions(readMethod, | |||
parent.getGetter().apply(bean)); | |||
return getType().cast(value); | |||
}; | |||
} | |||
@Override | |||
public BeanPropertySet<T> getPropertySet() { | |||
return propertySet; | |||
public Optional<Setter<T, V>> getSetter() { | |||
if (getDescriptor().getWriteMethod() == null) { | |||
return Optional.empty(); | |||
} | |||
Setter<T, V> setter = (bean, value) -> { | |||
// Do not "optimize" this getter call, | |||
// if its done outside the code block, that will produce | |||
// NotSerializableException because of some lambda compilation | |||
// magic | |||
Method innerSetter = getDescriptor().getWriteMethod(); | |||
invokeWrapExceptions(innerSetter, | |||
parent.getGetter().apply(bean), value); | |||
}; | |||
return Optional.of(setter); | |||
} | |||
private Object writeReplace() { | |||
@@ -177,7 +248,7 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
* property definition from the cache. | |||
*/ | |||
return new SerializedPropertyDefinition(getPropertySet().beanType, | |||
getName()); | |||
parent.getName() + "." + getName()); | |||
} | |||
} | |||
@@ -194,7 +265,7 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | |||
.filter(BeanPropertySet::hasNonObjectReadMethod) | |||
.map(descriptor -> new BeanPropertyDefinition<>(this, | |||
descriptor)) | |||
beanType, descriptor)) | |||
.collect(Collectors.toMap(PropertyDefinition::getName, | |||
Function.identity())); | |||
} catch (IntrospectionException e) { | |||
@@ -227,8 +298,42 @@ public class BeanPropertySet<T> implements PropertySet<T> { | |||
} | |||
@Override | |||
public Optional<PropertyDefinition<T, ?>> getProperty(String name) { | |||
return Optional.ofNullable(definitions.get(name)); | |||
public Optional<PropertyDefinition<T, ?>> getProperty(String name) | |||
throws IllegalArgumentException { | |||
Optional<PropertyDefinition<T, ?>> definition = Optional | |||
.ofNullable(definitions.get(name)); | |||
if (!definition.isPresent() && name.contains(".")) { | |||
try { | |||
String parentName = name.substring(0, name.lastIndexOf('.')); | |||
Optional<PropertyDefinition<T, ?>> parent = getProperty( | |||
parentName); | |||
if (!parent.isPresent()) { | |||
throw new IllegalArgumentException( | |||
"Cannot find property descriptor [" + parentName | |||
+ "] for " + beanType.getName()); | |||
} | |||
Optional<PropertyDescriptor> descriptor = Optional.ofNullable( | |||
BeanUtil.getPropertyDescriptor(beanType, name)); | |||
if (descriptor.isPresent()) { | |||
NestedBeanPropertyDefinition<T, ?> nestedDefinition = new NestedBeanPropertyDefinition<>( | |||
this, parent.get(), descriptor.get()); | |||
definitions.put(name, nestedDefinition); | |||
return Optional.of(nestedDefinition); | |||
} else { | |||
throw new IllegalArgumentException( | |||
"Cannot find property descriptor [" + name | |||
+ "] for " + beanType.getName()); | |||
} | |||
} catch (IntrospectionException e) { | |||
throw new IllegalArgumentException( | |||
"Cannot find property descriptors for " | |||
+ beanType.getName(), | |||
e); | |||
} | |||
} | |||
return definition; | |||
} | |||
private static boolean hasNonObjectReadMethod( |
@@ -104,8 +104,9 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> { | |||
private void configureRequired(BindingBuilder<BEAN, ?> binding, | |||
PropertyDefinition<BEAN, ?> definition, BeanValidator validator) { | |||
assert requiredConfigurator != null; | |||
Class<?> propertyHolderType = definition.getPropertyHolderType(); | |||
BeanDescriptor descriptor = validator.getJavaxBeanValidator() | |||
.getConstraintsForClass(beanType); | |||
.getConstraintsForClass(propertyHolderType); | |||
PropertyDescriptor propertyDescriptor = descriptor | |||
.getConstraintsForProperty(definition.getName()); | |||
if (propertyDescriptor == null) { |
@@ -54,6 +54,15 @@ public interface PropertyDefinition<T, V> extends Serializable { | |||
*/ | |||
public Class<V> getType(); | |||
/** | |||
* Gets the type of the class containing this property. | |||
* | |||
* @since 8.1 | |||
* | |||
* @return the property type. not <code>null</code> | |||
*/ | |||
public Class<?> getPropertyHolderType(); | |||
/** | |||
* Gets the name of this property. | |||
* |
@@ -66,6 +66,8 @@ public class BeanBinderTest | |||
@NotEmpty | |||
private String lastname; | |||
private SubConstraint subfield; | |||
public String getFirstname() { | |||
return firstname; | |||
} | |||
@@ -89,6 +91,30 @@ public class BeanBinderTest | |||
public void setLastname(String lastname) { | |||
this.lastname = lastname; | |||
} | |||
public SubConstraint getSubfield() { | |||
return subfield; | |||
} | |||
public void setSubfield(SubConstraint subfield) { | |||
this.subfield = subfield; | |||
} | |||
public static class SubConstraint implements Serializable{ | |||
@NotNull | |||
private String name; | |||
public String getName() { | |||
return name; | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
} | |||
} | |||
} | |||
@Before | |||
@@ -337,6 +363,21 @@ public class BeanBinderTest | |||
Assert.assertTrue(field.isRequiredIndicatorVisible()); | |||
testSerialization(binder); | |||
} | |||
@Test | |||
public void subfield_name_fieldIsRequired() { | |||
BeanValidationBinder<RequiredConstraints> binder = new BeanValidationBinder<>( | |||
RequiredConstraints.class); | |||
RequiredConstraints bean = new RequiredConstraints(); | |||
bean.setSubfield(new RequiredConstraints.SubConstraint()); | |||
TextField field = new TextField(); | |||
binder.bind(field, "subfield.name"); | |||
binder.setBean(bean); | |||
Assert.assertTrue(field.isRequiredIndicatorVisible()); | |||
testSerialization(binder); | |||
} | |||
private void assertInvalid(HasValue<?> field, String message) { | |||
BinderValidationStatus<?> status = binder.validate(); |
@@ -23,6 +23,7 @@ import java.lang.reflect.Field; | |||
import java.util.Arrays; | |||
import java.util.HashSet; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
@@ -30,6 +31,10 @@ import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.provider.bov.Person; | |||
import com.vaadin.tests.data.bean.Address; | |||
import com.vaadin.tests.data.bean.Country; | |||
import com.vaadin.tests.data.bean.FatherAndSon; | |||
import com.vaadin.tests.data.bean.Sex; | |||
import com.vaadin.tests.server.ClassesSerializableTest; | |||
public class BeanPropertySetTest { | |||
@@ -101,6 +106,82 @@ public class BeanPropertySetTest { | |||
deserializedDefinition); | |||
} | |||
@Test | |||
public void testSerializeDeserialize_nestedPropertyDefinition() | |||
throws Exception { | |||
PropertyDefinition<com.vaadin.tests.data.bean.Person, ?> definition = BeanPropertySet | |||
.get(com.vaadin.tests.data.bean.Person.class) | |||
.getProperty("address.postalCode") | |||
.orElseThrow(RuntimeException::new); | |||
PropertyDefinition<com.vaadin.tests.data.bean.Person, ?> deserializedDefinition = ClassesSerializableTest | |||
.serializeAndDeserialize(definition); | |||
ValueProvider<com.vaadin.tests.data.bean.Person, ?> getter = deserializedDefinition | |||
.getGetter(); | |||
Address address = new Address("Ruukinkatu 2-4", 20540, "Turku", | |||
Country.FINLAND); | |||
com.vaadin.tests.data.bean.Person person = new com.vaadin.tests.data.bean.Person( | |||
"Jon", "Doe", "jon.doe@vaadin.com", 32, Sex.MALE, address); | |||
Integer postalCode = (Integer) getter.apply(person); | |||
Assert.assertEquals("Deserialized definition should be functional", | |||
address.getPostalCode(), postalCode); | |||
Assert.assertSame( | |||
"Deserialized instance should be the same as in the cache", | |||
BeanPropertySet.get(com.vaadin.tests.data.bean.Person.class) | |||
.getProperty("address.postalCode").orElseThrow( | |||
RuntimeException::new), | |||
deserializedDefinition); | |||
} | |||
@Test | |||
public void nestedPropertyDefinition_samePropertyNameOnMultipleLevels() | |||
throws Exception { | |||
PropertyDefinition<FatherAndSon, ?> definition = BeanPropertySet | |||
.get(FatherAndSon.class).getProperty("father.father.firstName") | |||
.orElseThrow(RuntimeException::new); | |||
ValueProvider<FatherAndSon, ?> getter = definition.getGetter(); | |||
FatherAndSon grandFather = new FatherAndSon("Grand Old Jon", "Doe", | |||
null, null); | |||
FatherAndSon father = new FatherAndSon("Old Jon", "Doe", grandFather, | |||
null); | |||
FatherAndSon son = new FatherAndSon("Jon", "Doe", father, null); | |||
String firstName = (String) getter.apply(son); | |||
Assert.assertEquals(grandFather.getFirstName(), firstName); | |||
} | |||
@Test(expected = NullPointerException.class) | |||
public void nestedPropertyDefinition_propertyChainBroken() | |||
throws Exception { | |||
PropertyDefinition<FatherAndSon, ?> definition = BeanPropertySet | |||
.get(FatherAndSon.class).getProperty("father.firstName") | |||
.orElseThrow(RuntimeException::new); | |||
ValueProvider<FatherAndSon, ?> getter = definition.getGetter(); | |||
getter.apply(new FatherAndSon("Jon", "Doe", null, null)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void nestedPropertyDefinition_invalidPropertyNameInChain() | |||
throws Exception { | |||
BeanPropertySet.get(FatherAndSon.class) | |||
.getProperty("grandfather.firstName"); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void nestedPropertyDefinition_invalidPropertyNameAtChainEnd() | |||
throws Exception { | |||
BeanPropertySet.get(FatherAndSon.class).getProperty("father.age"); | |||
} | |||
@Test | |||
public void properties() { | |||
PropertySet<Person> propertySet = BeanPropertySet.get(Person.class); |
@@ -59,6 +59,10 @@ public class BinderCustomPropertySetTest { | |||
public Class<String> getType() { | |||
return String.class; | |||
} | |||
public Class<?> getPropertyHolderType(){ | |||
return Map.class; | |||
} | |||
@Override | |||
public String getName() { |
@@ -0,0 +1,65 @@ | |||
package com.vaadin.tests.data.bean; | |||
import java.io.Serializable; | |||
public class FatherAndSon implements Serializable { | |||
private String firstName; | |||
private String lastName; | |||
private FatherAndSon father; | |||
private FatherAndSon son; | |||
public FatherAndSon() { | |||
} | |||
@Override | |||
public String toString() { | |||
return "FatherAndSon [firstName=" + firstName + ", lastName=" + lastName | |||
+ ", father=" + father + ", son=" + son + "]"; | |||
} | |||
public FatherAndSon(String firstName, String lastName, FatherAndSon father, | |||
FatherAndSon son) { | |||
super(); | |||
this.firstName = firstName; | |||
this.lastName = lastName; | |||
this.father = father; | |||
if (this.father != null) | |||
this.father.setSon(this); | |||
else | |||
this.son = son; | |||
} | |||
public String getFirstName() { | |||
return firstName; | |||
} | |||
public void setFirstName(String firstName) { | |||
this.firstName = firstName; | |||
} | |||
public String getLastName() { | |||
return lastName; | |||
} | |||
public void setLastName(String lastName) { | |||
this.lastName = lastName; | |||
} | |||
public FatherAndSon getFather() { | |||
return father; | |||
} | |||
public void setFather(FatherAndSon father) { | |||
this.father = father; | |||
} | |||
public FatherAndSon getSon() { | |||
return son; | |||
} | |||
public void setSon(FatherAndSon son) { | |||
this.son = son; | |||
} | |||
} |
@@ -88,6 +88,11 @@ public class GridCustomPropertySetTest { | |||
return String.class; | |||
} | |||
@Override | |||
public Class<?> getPropertyHolderType() { | |||
return MyBeanWithoutGetters.class; | |||
} | |||
@Override | |||
public String getName() { | |||
return "string"; | |||
@@ -129,6 +134,11 @@ public class GridCustomPropertySetTest { | |||
return Integer.class; | |||
} | |||
@Override | |||
public Class<?> getPropertyHolderType() { | |||
return MyBeanWithoutGetters.class; | |||
} | |||
@Override | |||
public String getName() { | |||
return "numbah"; |