/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import org.junit.Test;
import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
* Specific {@link BeanWrapperImpl} tests.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Alef Arendsen
* @author Arjen Poutsma
* @author Chris Beams
* @author Dave Syer
*/
public class BeanWrapperTests extends AbstractPropertyAccessorTests {
@Override
protected BeanWrapperImpl createAccessor(Object target) {
return new BeanWrapperImpl(target);
}
@Test
public void setterDoesNotCallGetter() {
GetterBean target = new GetterBean();
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "tom");
assertTrue("Set name to tom", target.getName().equals("tom"));
}
@Test
public void getterSilentlyFailWithOldValueExtraction() {
GetterBean target = new GetterBean();
BeanWrapper accessor = createAccessor(target);
accessor.setExtractOldValueForEditor(true); // This will call the getter
accessor.setPropertyValue("name", "tom");
assertTrue("Set name to tom", target.getName().equals("tom"));
}
@Test
public void aliasedSetterThroughDefaultMethod() {
GetterBean target = new GetterBean();
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("aliasedName", "tom");
assertTrue("Set name to tom", target.getAliasedName().equals("tom"));
}
@Test
public void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() {
TestBean target = new TestBean();
String newName = "tony";
String invalidTouchy = ".valid";
try {
BeanWrapper accessor = createAccessor(target);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue(new PropertyValue("age", "foobar"));
pvs.addPropertyValue(new PropertyValue("name", newName));
pvs.addPropertyValue(new PropertyValue("touchy", invalidTouchy));
accessor.setPropertyValues(pvs);
fail("Should throw exception when everything is valid");
}
catch (PropertyBatchUpdateException ex) {
assertTrue("Must contain 2 exceptions", ex.getExceptionCount() == 2);
// Test validly set property matches
assertTrue("Vaid set property must stick", target.getName().equals(newName));
assertTrue("Invalid set property must retain old value", target.getAge() == 0);
assertTrue("New value of dodgy setter must be available through exception",
ex.getPropertyAccessException("touchy").getPropertyChangeEvent().getNewValue().equals(invalidTouchy));
}
}
@Test
public void checkNotWritablePropertyHoldPossibleMatches() {
TestBean target = new TestBean();
try {
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("ag", "foobar");
fail("Should throw exception on invalid property");
}
catch (NotWritablePropertyException ex) {
// expected
assertEquals(1, ex.getPossibleMatches().length);
assertEquals("age", ex.getPossibleMatches()[0]);
}
}
@Test // Can't be shared; there is no such thing as a read-only field
public void setReadOnlyMapProperty() {
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("map", map);
}
@Test
public void notWritablePropertyExceptionContainsAlternativeMatch() {
IntelliBean target = new IntelliBean();
BeanWrapper bw = createAccessor(target);
try {
bw.setPropertyValue("names", "Alef");
}
catch (NotWritablePropertyException ex) {
assertNotNull("Possible matches not determined", ex.getPossibleMatches());
assertEquals("Invalid amount of alternatives", 1, ex.getPossibleMatches().length);
}
}
@Test
public void notWritablePropertyExceptionContainsAlternativeMatches() {
IntelliBean target = new IntelliBean();
BeanWrapper bw = createAccessor(target);
try {
bw.setPropertyValue("mystring", "Arjen");
}
catch (NotWritablePropertyException ex) {
assertNotNull("Possible matches not determined", ex.getPossibleMatches());
assertEquals("Invalid amount of alternatives", 3, ex.getPossibleMatches().length);
}
}
@Test // Can't be shared: no type mismatch with a field
public void setPropertyTypeMismatch() {
PropertyTypeMismatch target = new PropertyTypeMismatch();
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("object", "a String");
assertEquals("a String", target.value);
assertTrue(target.getObject() == 8);
assertEquals(8, accessor.getPropertyValue("object"));
}
@Test
public void propertyDescriptors() {
TestBean target = new TestBean();
target.setSpouse(new TestBean());
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "a");
accessor.setPropertyValue("spouse.name", "b");
assertEquals("a", target.getName());
assertEquals("b", target.getSpouse().getName());
assertEquals("a", accessor.getPropertyValue("name"));
assertEquals("b", accessor.getPropertyValue("spouse.name"));
assertEquals(String.class, accessor.getPropertyDescriptor("name").getPropertyType());
assertEquals(String.class, accessor.getPropertyDescriptor("spouse.name").getPropertyType());
}
@Test
public void getPropertyWithOptional() {
GetterWithOptional target = new GetterWithOptional();
TestBean tb = new TestBean("x");
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("object", tb);
assertSame(tb, target.value);
assertSame(tb, target.getObject().get());
assertSame(tb, ((Optional<String>) accessor.getPropertyValue("object")).get());
assertEquals("x", target.value.getName());
assertEquals("x", target.getObject().get().getName());
assertEquals("x", accessor.getPropertyValue("object.name"));
accessor.setPropertyValue("object.name", "y");
assertSame(tb, target.value);
assertSame(tb, target.getObject().get());
assertSame(tb, ((Optional<String>) accessor.getPropertyValue("object")).get());
assertEquals("y", target.value.getName());
assertEquals("y", target.getObject().get().getName());
assertEquals("y", accessor.getPropertyValue("object.name"));
}
@Test
public void getPropertyWithOptionalAndAutoGrow() {
GetterWithOptional target = new GetterWithOptional();
BeanWrapper accessor = createAccessor(target);
accessor.setAutoGrowNestedPaths(true);
accessor.setPropertyValue("object.name", "x");
assertEquals("x", target.value.getName());
assertEquals("x", target.getObject().get().getName());
assertEquals("x", accessor.getPropertyValue("object.name"));
}
@Test
public void incompletelyQuotedKeyLeadsToPropertyException() {
TestBean target = new TestBean();
try {
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("[']", "foobar");
fail("Should throw exception on invalid property");
}
catch (NotWritablePropertyException ex) {
assertNull(ex.getPossibleMatches());
}
}
@SuppressWarnings("unused")
private interface AliasedProperty {
default void setAliasedName(String name) {
setName(name);
}
default String getAliasedName() {
return getName();
}
void setName(String name);
String getName();
}
@SuppressWarnings("unused")
private static class GetterBean implements AliasedProperty {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
if (this.name == null) {
throw new RuntimeException("name property must be set");
}
return name;
}
}
@SuppressWarnings("unused")
private static class IntelliBean {
public void setName(String name) {
}
public void setMyString(String string) {
}
public void setMyStrings(String string) {
}
public void setMyStriNg(String string) {
}
public void setMyStringss(String string) {
}
}
@SuppressWarnings("serial")
public static class TypedReadOnlyMap extends ReadOnlyMap<String, TestBean> {
public TypedReadOnlyMap() {
}
public TypedReadOnlyMap(Map<? extends String, ? extends TestBean> map) {
super(map);
}
}
public static class TypedReadOnlyMapClient {
public void setMap(TypedReadOnlyMap map) {
}
}
public static class PropertyTypeMismatch {
public String value;
public void setObject(String object) {
this.value = object;
}
public Integer getObject() {
return (this.value != null ? this.value.length() : null);
}
}
public static class GetterWithOptional {
public TestBean value;
public void setObject(TestBean object) {
this.value = object;
}
public Optional<TestBean> getObject() {
return Optional.ofNullable(this.value);
}
}
}