import java.io.Serializable; | import java.io.Serializable; | ||||
import java.lang.reflect.InvocationTargetException; | import java.lang.reflect.InvocationTargetException; | ||||
import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||
import java.util.Arrays; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
} | } | ||||
} | } | ||||
private static class BeanPropertyDefinition<T, V> | |||||
private abstract static class AbstractBeanPropertyDefinition<T, V> | |||||
implements PropertyDefinition<T, V> { | implements PropertyDefinition<T, V> { | ||||
private final PropertyDescriptor descriptor; | private final PropertyDescriptor descriptor; | ||||
private final BeanPropertySet<T> propertySet; | 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.propertySet = propertySet; | ||||
this.propertyHolderType = propertyHolderType; | |||||
this.descriptor = descriptor; | this.descriptor = descriptor; | ||||
if (descriptor.getReadMethod() == null) { | if (descriptor.getReadMethod() == null) { | ||||
+ propertySet.beanType + "." | + propertySet.beanType + "." | ||||
+ descriptor.getName()); | + 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 | @Override | ||||
public ValueProvider<T, V> getGetter() { | public ValueProvider<T, V> getGetter() { | ||||
return bean -> { | return bean -> { | ||||
Method readMethod = descriptor.getReadMethod(); | |||||
Method readMethod = getDescriptor().getReadMethod(); | |||||
Object value = invokeWrapExceptions(readMethod, bean); | Object value = invokeWrapExceptions(readMethod, bean); | ||||
return getType().cast(value); | return getType().cast(value); | ||||
}; | }; | ||||
@Override | @Override | ||||
public Optional<Setter<T, V>> getSetter() { | public Optional<Setter<T, V>> getSetter() { | ||||
if (descriptor.getWriteMethod() == null) { | |||||
if (getDescriptor().getWriteMethod() == null) { | |||||
return Optional.empty(); | return Optional.empty(); | ||||
} | } | ||||
Setter<T, V> setter = (bean, value) -> { | Setter<T, V> setter = (bean, value) -> { | ||||
// Do not "optimize" this getter call, | // Do not "optimize" this getter call, | ||||
// if its done outside the code block, that will produce | // 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); | invokeWrapExceptions(innerSetter, bean, value); | ||||
}; | }; | ||||
return Optional.of(setter); | 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 | @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 | @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() { | private Object writeReplace() { | ||||
* property definition from the cache. | * property definition from the cache. | ||||
*/ | */ | ||||
return new SerializedPropertyDefinition(getPropertySet().beanType, | return new SerializedPropertyDefinition(getPropertySet().beanType, | ||||
getName()); | |||||
parent.getName() + "." + getName()); | |||||
} | } | ||||
} | } | ||||
definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | ||||
.filter(BeanPropertySet::hasNonObjectReadMethod) | .filter(BeanPropertySet::hasNonObjectReadMethod) | ||||
.map(descriptor -> new BeanPropertyDefinition<>(this, | .map(descriptor -> new BeanPropertyDefinition<>(this, | ||||
descriptor)) | |||||
beanType, descriptor)) | |||||
.collect(Collectors.toMap(PropertyDefinition::getName, | .collect(Collectors.toMap(PropertyDefinition::getName, | ||||
Function.identity())); | Function.identity())); | ||||
} catch (IntrospectionException e) { | } catch (IntrospectionException e) { | ||||
} | } | ||||
@Override | @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( | private static boolean hasNonObjectReadMethod( |
private void configureRequired(BindingBuilder<BEAN, ?> binding, | private void configureRequired(BindingBuilder<BEAN, ?> binding, | ||||
PropertyDefinition<BEAN, ?> definition, BeanValidator validator) { | PropertyDefinition<BEAN, ?> definition, BeanValidator validator) { | ||||
assert requiredConfigurator != null; | assert requiredConfigurator != null; | ||||
Class<?> propertyHolderType = definition.getPropertyHolderType(); | |||||
BeanDescriptor descriptor = validator.getJavaxBeanValidator() | BeanDescriptor descriptor = validator.getJavaxBeanValidator() | ||||
.getConstraintsForClass(beanType); | |||||
.getConstraintsForClass(propertyHolderType); | |||||
PropertyDescriptor propertyDescriptor = descriptor | PropertyDescriptor propertyDescriptor = descriptor | ||||
.getConstraintsForProperty(definition.getName()); | .getConstraintsForProperty(definition.getName()); | ||||
if (propertyDescriptor == null) { | if (propertyDescriptor == null) { |
*/ | */ | ||||
public Class<V> getType(); | 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. | * Gets the name of this property. | ||||
* | * |
@NotEmpty | @NotEmpty | ||||
private String lastname; | private String lastname; | ||||
private SubConstraint subfield; | |||||
public String getFirstname() { | public String getFirstname() { | ||||
return firstname; | return firstname; | ||||
} | } | ||||
public void setLastname(String lastname) { | public void setLastname(String lastname) { | ||||
this.lastname = 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 | @Before | ||||
Assert.assertTrue(field.isRequiredIndicatorVisible()); | Assert.assertTrue(field.isRequiredIndicatorVisible()); | ||||
testSerialization(binder); | 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) { | private void assertInvalid(HasValue<?> field, String message) { | ||||
BinderValidationStatus<?> status = binder.validate(); | BinderValidationStatus<?> status = binder.validate(); |
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import com.vaadin.data.provider.bov.Person; | 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; | import com.vaadin.tests.server.ClassesSerializableTest; | ||||
public class BeanPropertySetTest { | public class BeanPropertySetTest { | ||||
deserializedDefinition); | 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 | @Test | ||||
public void properties() { | public void properties() { | ||||
PropertySet<Person> propertySet = BeanPropertySet.get(Person.class); | PropertySet<Person> propertySet = BeanPropertySet.get(Person.class); |
public Class<String> getType() { | public Class<String> getType() { | ||||
return String.class; | return String.class; | ||||
} | } | ||||
public Class<?> getPropertyHolderType(){ | |||||
return Map.class; | |||||
} | |||||
@Override | @Override | ||||
public String getName() { | public String getName() { |
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; | |||||
} | |||||
} |
return String.class; | return String.class; | ||||
} | } | ||||
@Override | |||||
public Class<?> getPropertyHolderType() { | |||||
return MyBeanWithoutGetters.class; | |||||
} | |||||
@Override | @Override | ||||
public String getName() { | public String getName() { | ||||
return "string"; | return "string"; | ||||
return Integer.class; | return Integer.class; | ||||
} | } | ||||
@Override | |||||
public Class<?> getPropertyHolderType() { | |||||
return MyBeanWithoutGetters.class; | |||||
} | |||||
@Override | @Override | ||||
public String getName() { | public String getName() { | ||||
return "numbah"; | return "numbah"; |