/*
 * 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
 *
 *      https://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.beans.PropertyEditorSupport;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.logging.LogFactory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.beans.support.DerivedFromProtectedBaseBean;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.lang.Nullable;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
import org.springframework.tests.sample.beans.BooleanTestBean;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.IndexedTestBean;
import org.springframework.tests.sample.beans.NumberTestBean;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.*;

/**
 * Shared tests for property accessors.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Alef Arendsen
 * @author Arjen Poutsma
 * @author Chris Beams
 * @author Dave Syer
 * @author Stephane Nicoll
 */
public abstract class AbstractPropertyAccessorTests {

	@Rule
	public final ExpectedException thrown = ExpectedException.none();


	protected abstract AbstractPropertyAccessor createAccessor(Object target);


	@Test
	public void createWithNullTarget() {
		try {
			createAccessor(null);
			fail("Must throw an exception when constructed with null object");
		}
		catch (IllegalArgumentException ex) {
			// expected
		}
	}

	@Test
	public void isReadableProperty() {
		AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2));

		assertThat(accessor.isReadableProperty("name"), is(true));
	}

	@Test
	public void isReadablePropertyNotReadable() {
		AbstractPropertyAccessor accessor = createAccessor(new NoRead());

		assertFalse(accessor.isReadableProperty("age"));
	}

	/**
	 * Shouldn't throw an exception: should just return false
	 */
	@Test
	public void isReadablePropertyNoSuchProperty() {
		AbstractPropertyAccessor accessor = createAccessor(new NoRead());

		assertFalse(accessor.isReadableProperty("xxxxx"));
	}

	@Test
	public void isReadablePropertyNull() {
		AbstractPropertyAccessor accessor = createAccessor(new NoRead());

		thrown.expect(IllegalArgumentException.class);
		accessor.isReadableProperty(null);
	}

	@Test
	public void isWritableProperty() {
		AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2));

		assertThat(accessor.isWritableProperty("name"), is(true));
	}

	@Test
	public void isWritablePropertyNull() {
		AbstractPropertyAccessor accessor = createAccessor(new NoRead());

		thrown.expect(IllegalArgumentException.class);
		accessor.isWritableProperty(null);
	}

	@Test
	public void isWritablePropertyNoSuchProperty() {
		AbstractPropertyAccessor accessor = createAccessor(new NoRead());

		assertFalse(accessor.isWritableProperty("xxxxx"));
	}

	@Test
	public void isReadableWritableForIndexedProperties() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);

		assertTrue(accessor.isReadableProperty("array"));
		assertTrue(accessor.isReadableProperty("list"));
		assertTrue(accessor.isReadableProperty("set"));
		assertTrue(accessor.isReadableProperty("map"));
		assertFalse(accessor.isReadableProperty("xxx"));

		assertTrue(accessor.isWritableProperty("array"));
		assertTrue(accessor.isWritableProperty("list"));
		assertTrue(accessor.isWritableProperty("set"));
		assertTrue(accessor.isWritableProperty("map"));
		assertFalse(accessor.isWritableProperty("xxx"));

		assertTrue(accessor.isReadableProperty("array[0]"));
		assertTrue(accessor.isReadableProperty("array[0].name"));
		assertTrue(accessor.isReadableProperty("list[0]"));
		assertTrue(accessor.isReadableProperty("list[0].name"));
		assertTrue(accessor.isReadableProperty("set[0]"));
		assertTrue(accessor.isReadableProperty("set[0].name"));
		assertTrue(accessor.isReadableProperty("map[key1]"));
		assertTrue(accessor.isReadableProperty("map[key1].name"));
		assertTrue(accessor.isReadableProperty("map[key4][0]"));
		assertTrue(accessor.isReadableProperty("map[key4][0].name"));
		assertTrue(accessor.isReadableProperty("map[key4][1]"));
		assertTrue(accessor.isReadableProperty("map[key4][1].name"));
		assertFalse(accessor.isReadableProperty("array[key1]"));

		assertTrue(accessor.isWritableProperty("array[0]"));
		assertTrue(accessor.isWritableProperty("array[0].name"));
		assertTrue(accessor.isWritableProperty("list[0]"));
		assertTrue(accessor.isWritableProperty("list[0].name"));
		assertTrue(accessor.isWritableProperty("set[0]"));
		assertTrue(accessor.isWritableProperty("set[0].name"));
		assertTrue(accessor.isWritableProperty("map[key1]"));
		assertTrue(accessor.isWritableProperty("map[key1].name"));
		assertTrue(accessor.isWritableProperty("map[key4][0]"));
		assertTrue(accessor.isWritableProperty("map[key4][0].name"));
		assertTrue(accessor.isWritableProperty("map[key4][1]"));
		assertTrue(accessor.isWritableProperty("map[key4][1].name"));
		assertFalse(accessor.isWritableProperty("array[key1]"));
	}

	@Test
	public void getSimpleProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);
		assertThat(accessor.getPropertyValue("name"), is("John"));
	}

	@Test
	public void getNestedProperty() {
		Person target = createPerson("John", "London", "UK");
		AbstractPropertyAccessor accessor = createAccessor(target);
		assertThat(accessor.getPropertyValue("address.city"), is("London"));
	}

	@Test
	public void getNestedDeepProperty() {
		Person target = createPerson("John", "London", "UK");
		AbstractPropertyAccessor accessor = createAccessor(target);
		assertThat(accessor.getPropertyValue("address.country.name"), is("UK"));
	}

	@Test
	public void getAnotherNestedDeepProperty() {
		ITestBean target = new TestBean("rod", 31);
		ITestBean kerry = new TestBean("kerry", 35);
		target.setSpouse(kerry);
		kerry.setSpouse(target);
		AbstractPropertyAccessor accessor = createAccessor(target);
		Integer KA = (Integer) accessor.getPropertyValue("spouse.age");
		assertTrue("kerry is 35", KA == 35);
		Integer RA = (Integer) accessor.getPropertyValue("spouse.spouse.age");
		assertTrue("rod is 31, not" + RA, RA == 31);
		ITestBean spousesSpouse = (ITestBean) accessor.getPropertyValue("spouse.spouse");
		assertTrue("spousesSpouse = initial point", target == spousesSpouse);
	}

	@Test
	public void getPropertyIntermediatePropertyIsNull() {
		Person target = createPerson("John", "London", "UK");
		target.address = null;
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.getPropertyValue("address.country.name");
			fail("Should have failed to get value with null intermediate path");
		}
		catch (NullValueInNestedPathException e) {
			assertEquals("address", e.getPropertyName());
			assertEquals(Person.class, e.getBeanClass());
		}
	}

	@Test
	public void getPropertyIntermediatePropertyIsNullWithAutoGrow() {
		Person target = createPerson("John", "London", "UK");
		target.address = null;
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setAutoGrowNestedPaths(true);

		assertEquals("DefaultCountry", accessor.getPropertyValue("address.country.name"));
	}

	@Test
	public void getPropertyIntermediateMapEntryIsNullWithAutoGrow() {
		Foo target = new Foo();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setConversionService(new DefaultConversionService());
		accessor.setAutoGrowNestedPaths(true);
		accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9");
		assertEquals("9", target.listOfMaps.get(0).get("luckyNumber"));
	}

	@Test
	public void getUnknownProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.getPropertyValue("foo");
			fail("Should have failed to get an unknown property.");
		}
		catch (NotReadablePropertyException e) {
			assertEquals(Simple.class, e.getBeanClass());
			assertEquals("foo", e.getPropertyName());
		}
	}

	@Test
	public void getUnknownNestedProperty() {
		Person target = createPerson("John", "London", "UK");
		AbstractPropertyAccessor accessor = createAccessor(target);

		thrown.expect(NotReadablePropertyException.class);
		accessor.getPropertyValue("address.bar");
	}

	@Test
	public void setSimpleProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("name", "SomeValue");

		assertThat(target.name, is("SomeValue"));
		assertThat(target.getName(), is("SomeValue"));
	}

	@Test
	public void setNestedProperty() {
		Person target = createPerson("John", "Paris", "FR");
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("address.city", "London");
		assertThat(target.address.city, is("London"));
	}

	@Test
	public void setNestedPropertyPolymorphic() throws Exception {
		ITestBean target = new TestBean("rod", 31);
		ITestBean kerry = new Employee();

		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("spouse", kerry);
		accessor.setPropertyValue("spouse.age", new Integer(35));
		accessor.setPropertyValue("spouse.name", "Kerry");
		accessor.setPropertyValue("spouse.company", "Lewisham");
		assertTrue("kerry name is Kerry", kerry.getName().equals("Kerry"));

		assertTrue("nested set worked", target.getSpouse() == kerry);
		assertTrue("no back relation", kerry.getSpouse() == null);
		accessor.setPropertyValue(new PropertyValue("spouse.spouse", target));
		assertTrue("nested set worked", kerry.getSpouse() == target);

		AbstractPropertyAccessor kerryAccessor = createAccessor(kerry);
		assertTrue("spouse.spouse.spouse.spouse.company=Lewisham",
				"Lewisham".equals(kerryAccessor.getPropertyValue("spouse.spouse.spouse.spouse.company")));
	}

	@Test
	public void setAnotherNestedProperty() throws Exception {
		ITestBean target = new TestBean("rod", 31);
		ITestBean kerry = new TestBean("kerry", 0);

		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("spouse", kerry);

		assertTrue("nested set worked", target.getSpouse() == kerry);
		assertTrue("no back relation", kerry.getSpouse() == null);
		accessor.setPropertyValue(new PropertyValue("spouse.spouse", target));
		assertTrue("nested set worked", kerry.getSpouse() == target);
		assertTrue("kerry age not set", kerry.getAge() == 0);
		accessor.setPropertyValue(new PropertyValue("spouse.age", 35));
		assertTrue("Set primitive on spouse", kerry.getAge() == 35);

		assertEquals(kerry, accessor.getPropertyValue("spouse"));
		assertEquals(target, accessor.getPropertyValue("spouse.spouse"));
	}

	@Test
	public void setYetAnotherNestedProperties() {
		String doctorCompany = "";
		String lawyerCompany = "Dr. Sueem";
		TestBean target = new TestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("doctor.company", doctorCompany);
		accessor.setPropertyValue("lawyer.company", lawyerCompany);
		assertEquals(doctorCompany, target.getDoctor().getCompany());
		assertEquals(lawyerCompany, target.getLawyer().getCompany());
	}

	@Test
	public void setNestedDeepProperty() {
		Person target = createPerson("John", "Paris", "FR");
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("address.country.name", "UK");
		assertThat(target.address.country.name, is("UK"));
	}

	@Test
	public void testErrorMessageOfNestedProperty() {
		ITestBean target = new TestBean();
		ITestBean child = new DifferentTestBean();
		child.setName("test");
		target.setSpouse(child);
		AbstractPropertyAccessor accessor = createAccessor(target);
		try {
			accessor.getPropertyValue("spouse.bla");
		}
		catch (NotReadablePropertyException ex) {
			assertTrue(ex.getMessage().contains(TestBean.class.getName()));
		}
	}

	@Test
	public void setPropertyIntermediatePropertyIsNull() {
		Person target = createPerson("John", "Paris", "FR");
		target.address.country = null;
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.setPropertyValue("address.country.name", "UK");
			fail("Should have failed to set value with intermediate null value");
		}
		catch (NullValueInNestedPathException e) {
			assertEquals("address.country", e.getPropertyName());
			assertEquals(Person.class, e.getBeanClass());
		}
		assertThat(target.address.country, is(nullValue())); // Not touched
	}

	@Test
	public void setAnotherPropertyIntermediatePropertyIsNull() throws Exception {
		ITestBean target = new TestBean("rod", 31);
		AbstractPropertyAccessor accessor = createAccessor(target);
		try {
			accessor.setPropertyValue("spouse.age", new Integer(31));
			fail("Shouldn't have succeeded with null path");
		}
		catch (NullValueInNestedPathException ex) {
			// expected
			assertTrue("it was the spouse property that was null, not " + ex.getPropertyName(),
					ex.getPropertyName().equals("spouse"));
		}
	}

	@Test
	public void setPropertyIntermediatePropertyIsNullWithAutoGrow() {
		Person target = createPerson("John", "Paris", "FR");
		target.address.country = null;
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setAutoGrowNestedPaths(true);

		accessor.setPropertyValue("address.country.name", "UK");
		assertThat(target.address.country.name, is("UK"));
	}

	@SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
	@Test
	public void setPropertyIntermediateListIsNullWithAutoGrow() {
		Foo target = new Foo();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setConversionService(new DefaultConversionService());
		accessor.setAutoGrowNestedPaths(true);
		Map<String, String> map = new HashMap<>();
		map.put("favoriteNumber", "9");
		accessor.setPropertyValue("list[0]", map);
		assertEquals(map, target.list.get(0));
	}

	@Test
	public void setPropertyIntermediateListIsNullWithNoConversionService() {
		Foo target = new Foo();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setAutoGrowNestedPaths(true);
		accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9");
		assertEquals("9", target.listOfMaps.get(0).get("luckyNumber"));
	}

	@Test
	public void setPropertyIntermediateListIsNullWithBadConversionService() {
		Foo target = new Foo();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setConversionService(new GenericConversionService() {
			@Override
			public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
				throw new ConversionFailedException(sourceType, targetType, source, null);
			}
		});
		accessor.setAutoGrowNestedPaths(true);
		accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9");
		assertEquals("9", target.listOfMaps.get(0).get("luckyNumber"));
	}


	@Test
	public void setEmptyPropertyValues() {
		TestBean target = new TestBean();
		int age = 50;
		String name = "Tony";
		target.setAge(age);
		target.setName(name);
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			assertTrue("age is OK", target.getAge() == age);
			assertTrue("name is OK", name.equals(target.getName()));
			accessor.setPropertyValues(new MutablePropertyValues());
			// Check its unchanged
			assertTrue("age is OK", target.getAge() == age);
			assertTrue("name is OK", name.equals(target.getName()));
		}
		catch (BeansException ex) {
			fail("Shouldn't throw exception when everything is valid");
		}
	}


	@Test
	public void setValidPropertyValues() {
		TestBean target = new TestBean();
		String newName = "tony";
		int newAge = 65;
		String newTouchy = "valid";
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			MutablePropertyValues pvs = new MutablePropertyValues();
			pvs.addPropertyValue(new PropertyValue("age", newAge));
			pvs.addPropertyValue(new PropertyValue("name", newName));
			pvs.addPropertyValue(new PropertyValue("touchy", newTouchy));
			accessor.setPropertyValues(pvs);
			assertTrue("Name property should have changed", target.getName().equals(newName));
			assertTrue("Touchy property should have changed", target.getTouchy().equals(newTouchy));
			assertTrue("Age property should have changed", target.getAge() == newAge);
		}
		catch (BeansException ex) {
			fail("Shouldn't throw exception when everything is valid");
		}
	}

	@Test
	public void setIndividualValidPropertyValues() {
		TestBean target = new TestBean();
		String newName = "tony";
		int newAge = 65;
		String newTouchy = "valid";
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			accessor.setPropertyValue("age", new Integer(newAge));
			accessor.setPropertyValue(new PropertyValue("name", newName));
			accessor.setPropertyValue(new PropertyValue("touchy", newTouchy));
			assertTrue("Name property should have changed", target.getName().equals(newName));
			assertTrue("Touchy property should have changed", target.getTouchy().equals(newTouchy));
			assertTrue("Age property should have changed", target.getAge() == newAge);
		}
		catch (BeansException ex) {
			fail("Shouldn't throw exception when everything is valid");
		}
	}

	@Test
	public void setPropertyIsReflectedImmediately() {
		TestBean target = new TestBean();
		int newAge = 33;
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			target.setAge(newAge);
			Object bwAge = accessor.getPropertyValue("age");
			assertTrue("Age is an integer", bwAge instanceof Integer);
			assertTrue("Bean wrapper must pick up changes", (int) bwAge == newAge);
		}
		catch (Exception ex) {
			fail("Shouldn't throw exception when everything is valid");
		}
	}

	@Test
	public void setPropertyToNull() {
		TestBean target = new TestBean();
		target.setName("Frank");    // we need to change it back
		target.setSpouse(target);
		AbstractPropertyAccessor accessor = createAccessor(target);
		assertTrue("name is not null to start off", target.getName() != null);
		accessor.setPropertyValue("name", null);
		assertTrue("name is now null", target.getName() == null);
		// now test with non-string
		assertTrue("spouse is not null to start off", target.getSpouse() != null);
		accessor.setPropertyValue("spouse", null);
		assertTrue("spouse is now null", target.getSpouse() == null);
	}


	@Test
	public void setIndexedPropertyIgnored() {
		MutablePropertyValues values = new MutablePropertyValues();
		values.add("toBeIgnored[0]", 42);
		AbstractPropertyAccessor accessor = createAccessor(new Object());
		accessor.setPropertyValues(values, true);
	}

	@Test
	public void setPropertyWithPrimitiveConversion() {
		MutablePropertyValues values = new MutablePropertyValues();
		values.add("name", 42);
		TestBean target = new TestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValues(values);
		assertEquals("42", target.getName());
	}

	@Test
	public void setPropertyWithCustomEditor() {
		MutablePropertyValues values = new MutablePropertyValues();
		values.add("name", Integer.class);
		TestBean target = new TestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(String.class, new PropertyEditorSupport() {
			@Override
			public void setValue(Object value) {
				super.setValue(value.toString());
			}
		});
		accessor.setPropertyValues(values);
		assertEquals(Integer.class.toString(), target.getName());
	}

	@Test
	public void setStringPropertyWithCustomEditor() throws Exception {
		TestBean target = new TestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(String.class, "name", new PropertyEditorSupport() {
			@Override
			public void setValue(Object value) {
				if (value instanceof String[]) {
					setValue(StringUtils.arrayToDelimitedString(((String[]) value), "-"));
				}
				else {
					super.setValue(value != null ? value : "");
				}
			}
		});
		accessor.setPropertyValue("name", new String[] {});
		assertEquals("", target.getName());
		accessor.setPropertyValue("name", new String[] {"a1", "b2"});
		assertEquals("a1-b2", target.getName());
		accessor.setPropertyValue("name", null);
		assertEquals("", target.getName());
	}

	@Test
	public void setBooleanProperty() {
		BooleanTestBean target = new BooleanTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("bool2", "true");
		assertTrue("Correct bool2 value", Boolean.TRUE.equals(accessor.getPropertyValue("bool2")));
		assertTrue("Correct bool2 value", target.getBool2());

		accessor.setPropertyValue("bool2", "false");
		assertTrue("Correct bool2 value", Boolean.FALSE.equals(accessor.getPropertyValue("bool2")));
		assertTrue("Correct bool2 value", !target.getBool2());
	}

	@Test
	public void setNumberProperties() {
		NumberTestBean target = new NumberTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.setPropertyValue("short2", "2");
			accessor.setPropertyValue("int2", "8");
			accessor.setPropertyValue("long2", "6");
			accessor.setPropertyValue("bigInteger", "3");
			accessor.setPropertyValue("float2", "8.1");
			accessor.setPropertyValue("double2", "6.1");
			accessor.setPropertyValue("bigDecimal", "4.0");
		}
		catch (BeansException ex) {
			fail("Should not throw BeansException: " + ex.getMessage());
		}

		assertTrue("Correct short2 value", new Short("2").equals(accessor.getPropertyValue("short2")));
		assertTrue("Correct short2 value", new Short("2").equals(target.getShort2()));
		assertTrue("Correct int2 value", new Integer("8").equals(accessor.getPropertyValue("int2")));
		assertTrue("Correct int2 value", new Integer("8").equals(target.getInt2()));
		assertTrue("Correct long2 value", new Long("6").equals(accessor.getPropertyValue("long2")));
		assertTrue("Correct long2 value", new Long("6").equals(target.getLong2()));
		assertTrue("Correct bigInteger value", new BigInteger("3").equals(accessor.getPropertyValue("bigInteger")));
		assertTrue("Correct bigInteger value", new BigInteger("3").equals(target.getBigInteger()));
		assertTrue("Correct float2 value", new Float("8.1").equals(accessor.getPropertyValue("float2")));
		assertTrue("Correct float2 value", new Float("8.1").equals(target.getFloat2()));
		assertTrue("Correct double2 value", new Double("6.1").equals(accessor.getPropertyValue("double2")));
		assertTrue("Correct double2 value", new Double("6.1").equals(target.getDouble2()));
		assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal")));
		assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(target.getBigDecimal()));
	}

	@Test
	public void setNumberPropertiesWithCoercion() {
		NumberTestBean target = new NumberTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.setPropertyValue("short2", new Integer(2));
			accessor.setPropertyValue("int2", new Long(8));
			accessor.setPropertyValue("long2", new BigInteger("6"));
			accessor.setPropertyValue("bigInteger", new Integer(3));
			accessor.setPropertyValue("float2", new Double(8.1));
			accessor.setPropertyValue("double2", new BigDecimal(6.1));
			accessor.setPropertyValue("bigDecimal", new Float(4.0));
		}
		catch (BeansException ex) {
			fail("Should not throw BeansException: " + ex.getMessage());
		}

		assertTrue("Correct short2 value", new Short("2").equals(accessor.getPropertyValue("short2")));
		assertTrue("Correct short2 value", new Short("2").equals(target.getShort2()));
		assertTrue("Correct int2 value", new Integer("8").equals(accessor.getPropertyValue("int2")));
		assertTrue("Correct int2 value", new Integer("8").equals(target.getInt2()));
		assertTrue("Correct long2 value", new Long("6").equals(accessor.getPropertyValue("long2")));
		assertTrue("Correct long2 value", new Long("6").equals(target.getLong2()));
		assertTrue("Correct bigInteger value", new BigInteger("3").equals(accessor.getPropertyValue("bigInteger")));
		assertTrue("Correct bigInteger value", new BigInteger("3").equals(target.getBigInteger()));
		assertTrue("Correct float2 value", new Float("8.1").equals(accessor.getPropertyValue("float2")));
		assertTrue("Correct float2 value", new Float("8.1").equals(target.getFloat2()));
		assertTrue("Correct double2 value", new Double("6.1").equals(accessor.getPropertyValue("double2")));
		assertTrue("Correct double2 value", new Double("6.1").equals(target.getDouble2()));
		assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal")));
		assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(target.getBigDecimal()));
	}

	@Test
	public void setPrimitiveProperties() {
		NumberPropertyBean target = new NumberPropertyBean();
		AbstractPropertyAccessor accessor = createAccessor(target);

		String byteValue = " " + Byte.MAX_VALUE + " ";
		String shortValue = " " + Short.MAX_VALUE + " ";
		String intValue = " " + Integer.MAX_VALUE + " ";
		String longValue = " " + Long.MAX_VALUE + " ";
		String floatValue = " " + Float.MAX_VALUE + " ";
		String doubleValue = " " + Double.MAX_VALUE + " ";

		accessor.setPropertyValue("myPrimitiveByte", byteValue);
		accessor.setPropertyValue("myByte", byteValue);

		accessor.setPropertyValue("myPrimitiveShort", shortValue);
		accessor.setPropertyValue("myShort", shortValue);

		accessor.setPropertyValue("myPrimitiveInt", intValue);
		accessor.setPropertyValue("myInteger", intValue);

		accessor.setPropertyValue("myPrimitiveLong", longValue);
		accessor.setPropertyValue("myLong", longValue);

		accessor.setPropertyValue("myPrimitiveFloat", floatValue);
		accessor.setPropertyValue("myFloat", floatValue);

		accessor.setPropertyValue("myPrimitiveDouble", doubleValue);
		accessor.setPropertyValue("myDouble", doubleValue);

		assertEquals(Byte.MAX_VALUE, target.getMyPrimitiveByte());
		assertEquals(Byte.MAX_VALUE, target.getMyByte().byteValue());

		assertEquals(Short.MAX_VALUE, target.getMyPrimitiveShort());
		assertEquals(Short.MAX_VALUE, target.getMyShort().shortValue());

		assertEquals(Integer.MAX_VALUE, target.getMyPrimitiveInt());
		assertEquals(Integer.MAX_VALUE, target.getMyInteger().intValue());

		assertEquals(Long.MAX_VALUE, target.getMyPrimitiveLong());
		assertEquals(Long.MAX_VALUE, target.getMyLong().longValue());

		assertEquals(Float.MAX_VALUE, target.getMyPrimitiveFloat(), 0.001);
		assertEquals(Float.MAX_VALUE, target.getMyFloat().floatValue(), 0.001);

		assertEquals(Double.MAX_VALUE, target.getMyPrimitiveDouble(), 0.001);
		assertEquals(Double.MAX_VALUE, target.getMyDouble().doubleValue(), 0.001);

	}

	@Test
	public void setEnumProperty() {
		EnumTester target = new EnumTester();
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("autowire", "BY_NAME");
		assertEquals(Autowire.BY_NAME, target.getAutowire());

		accessor.setPropertyValue("autowire", "  BY_TYPE ");
		assertEquals(Autowire.BY_TYPE, target.getAutowire());

		try {
			accessor.setPropertyValue("autowire", "NHERITED");
			fail("Should have thrown TypeMismatchException");
		}
		catch (TypeMismatchException ex) {
			// expected
		}
	}

	@Test
	public void setGenericEnumProperty() {
		EnumConsumer target = new EnumConsumer();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE");
		assertEquals(TestEnum.TEST_VALUE, target.getEnumValue());
	}

	@Test
	public void setWildcardEnumProperty() {
		WildcardEnumConsumer target = new WildcardEnumConsumer();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE");
		assertEquals(TestEnum.TEST_VALUE, target.getEnumValue());
	}

	@Test
	public void setPropertiesProperty() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("name", "ptest");

		// Note format...
		String ps = "peace=war\nfreedom=slavery";
		accessor.setPropertyValue("properties", ps);

		assertTrue("name was set", target.name.equals("ptest"));
		assertTrue("properties non null", target.properties != null);
		String freedomVal = target.properties.getProperty("freedom");
		String peaceVal = target.properties.getProperty("peace");
		assertTrue("peace==war", peaceVal.equals("war"));
		assertTrue("Freedom==slavery", freedomVal.equals("slavery"));
	}

	@Test
	public void setStringArrayProperty() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("stringArray", new String[] {"foo", "fi", "fi", "fum"});
		assertTrue("stringArray length = 4", target.stringArray.length == 4);
		assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") &&
				target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum"));

		List<String> list = new ArrayList<>();
		list.add("foo");
		list.add("fi");
		list.add("fi");
		list.add("fum");
		accessor.setPropertyValue("stringArray", list);
		assertTrue("stringArray length = 4", target.stringArray.length == 4);
		assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") &&
				target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum"));

		Set<String> set = new HashSet<>();
		set.add("foo");
		set.add("fi");
		set.add("fum");
		accessor.setPropertyValue("stringArray", set);
		assertTrue("stringArray length = 3", target.stringArray.length == 3);
		List<String> result = Arrays.asList(target.stringArray);
		assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum"));

		accessor.setPropertyValue("stringArray", "one");
		assertTrue("stringArray length = 1", target.stringArray.length == 1);
		assertTrue("stringArray elt is ok", target.stringArray[0].equals("one"));

		accessor.setPropertyValue("stringArray", null);
		assertTrue("stringArray is null", target.stringArray == null);
	}

	@Test
	public void setStringArrayPropertyWithCustomStringEditor() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(text.substring(1));
			}
		});

		accessor.setPropertyValue("stringArray", new String[] {"4foo", "7fi", "6fi", "5fum"});
		assertTrue("stringArray length = 4", target.stringArray.length == 4);
		assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") &&
				target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum"));

		List<String> list = new ArrayList<>();
		list.add("4foo");
		list.add("7fi");
		list.add("6fi");
		list.add("5fum");
		accessor.setPropertyValue("stringArray", list);
		assertTrue("stringArray length = 4", target.stringArray.length == 4);
		assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") &&
				target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum"));

		Set<String> set = new HashSet<>();
		set.add("4foo");
		set.add("7fi");
		set.add("6fum");
		accessor.setPropertyValue("stringArray", set);
		assertTrue("stringArray length = 3", target.stringArray.length == 3);
		List<String> result = Arrays.asList(target.stringArray);
		assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum"));

		accessor.setPropertyValue("stringArray", "8one");
		assertTrue("stringArray length = 1", target.stringArray.length == 1);
		assertTrue("correct values", target.stringArray[0].equals("one"));
	}

	@Test
	public void setStringArrayPropertyWithStringSplitting() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.useConfigValueEditors();
		accessor.setPropertyValue("stringArray", "a1,b2");
		assertTrue("stringArray length = 2", target.stringArray.length == 2);
		assertTrue("correct values", target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2"));
	}

	@Test
	public void setStringArrayPropertyWithCustomStringDelimiter() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(String[].class, "stringArray", new StringArrayPropertyEditor("-"));
		accessor.setPropertyValue("stringArray", "a1-b2");
		assertTrue("stringArray length = 2", target.stringArray.length == 2);
		assertTrue("correct values", target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2"));
	}

	@Test
	public void setStringArrayWithAutoGrow() throws Exception {
		StringArrayBean target = new StringArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setAutoGrowNestedPaths(true);

		accessor.setPropertyValue("array[0]", "Test0");
		assertEquals(1, target.getArray().length);

		accessor.setPropertyValue("array[2]", "Test2");
		assertEquals(3, target.getArray().length);
		assertTrue("correct values", target.getArray()[0].equals("Test0") && target.getArray()[1] == null &&
				target.getArray()[2].equals("Test2"));
	}

	@Test
	public void setIntArrayProperty() {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);

		accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3});
		assertTrue("intArray length = 4", target.intArray.length == 4);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 &&
				target.intArray[2] == 2 && target.intArray[3] == 3);

		accessor.setPropertyValue("intArray", new String[] {"4", "5", "2", "3"});
		assertTrue("intArray length = 4", target.intArray.length == 4);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 &&
				target.intArray[2] == 2 && target.intArray[3] == 3);

		List<Object> list = new ArrayList<>();
		list.add(4);
		list.add("5");
		list.add(2);
		list.add("3");
		accessor.setPropertyValue("intArray", list);
		assertTrue("intArray length = 4", target.intArray.length == 4);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 &&
				target.intArray[2] == 2 && target.intArray[3] == 3);

		Set<Object> set = new HashSet<>();
		set.add("4");
		set.add(5);
		set.add("3");
		accessor.setPropertyValue("intArray", set);
		assertTrue("intArray length = 3", target.intArray.length == 3);
		List<Integer> result = new ArrayList<>();
		result.add(target.intArray[0]);
		result.add(target.intArray[1]);
		result.add(target.intArray[2]);
		assertTrue("correct values", result.contains(new Integer(4)) && result.contains(new Integer(5)) &&
				result.contains(new Integer(3)));

		accessor.setPropertyValue("intArray", new Integer[] {1});
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);

		accessor.setPropertyValue("intArray", new Integer(1));
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);

		accessor.setPropertyValue("intArray", new String[] {"1"});
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);

		accessor.setPropertyValue("intArray", "1");
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);
	}

	@Test
	public void setIntArrayPropertyWithCustomEditor() {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(int.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(new Integer(Integer.parseInt(text) + 1));
			}
		});

		accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3});
		assertTrue("intArray length = 4", target.intArray.length == 4);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 &&
				target.intArray[2] == 2 && target.intArray[3] == 3);

		accessor.setPropertyValue("intArray", new String[] {"3", "4", "1", "2"});
		assertTrue("intArray length = 4", target.intArray.length == 4);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 &&
				target.intArray[2] == 2 && target.intArray[3] == 3);

		accessor.setPropertyValue("intArray", new Integer(1));
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);

		accessor.setPropertyValue("intArray", new String[] {"0"});
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);

		accessor.setPropertyValue("intArray", "0");
		assertTrue("intArray length = 4", target.intArray.length == 1);
		assertTrue("correct values", target.intArray[0] == 1);
	}

	@Test
	public void setIntArrayPropertyWithStringSplitting() throws Exception {
		PropsTester target = new PropsTester();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.useConfigValueEditors();
		accessor.setPropertyValue("intArray", "4,5");
		assertTrue("intArray length = 2", target.intArray.length == 2);
		assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5);
	}

	@Test
	public void setPrimitiveArrayProperty() {
		PrimitiveArrayBean target = new PrimitiveArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("array", new String[] {"1", "2"});
		assertEquals(2, target.getArray().length);
		assertEquals(1, target.getArray()[0]);
		assertEquals(2, target.getArray()[1]);
	}

	@Test
	public void setPrimitiveArrayPropertyLargeMatching() {
		Assume.group(TestGroup.PERFORMANCE);
		Assume.notLogging(LogFactory.getLog(AbstractPropertyAccessorTests.class));

		PrimitiveArrayBean target = new PrimitiveArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		int[] input = new int[1024];
		StopWatch sw = new StopWatch();
		sw.start("array1");
		for (int i = 0; i < 1000; i++) {
			accessor.setPropertyValue("array", input);
		}
		sw.stop();
		assertEquals(1024, target.getArray().length);
		assertEquals(0, target.getArray()[0]);
		long time1 = sw.getLastTaskTimeMillis();
		assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100);

		accessor.registerCustomEditor(String.class, new StringTrimmerEditor(false));
		sw.start("array2");
		for (int i = 0; i < 1000; i++) {
			accessor.setPropertyValue("array", input);
		}
		sw.stop();
		assertTrue("Took too long", sw.getLastTaskTimeMillis() < 125);

		accessor.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false));
		sw.start("array3");
		for (int i = 0; i < 1000; i++) {
			accessor.setPropertyValue("array", input);
		}
		sw.stop();
		assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100);

		accessor.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false));
		sw.start("array3");
		for (int i = 0; i < 1000; i++) {
			accessor.setPropertyValue("array", input);
		}
		sw.stop();
		assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100);

		accessor.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false));
		sw.start("array4");
		for (int i = 0; i < 100; i++) {
			accessor.setPropertyValue("array", input);
		}
		sw.stop();
		assertEquals(1024, target.getArray().length);
		assertEquals(0, target.getArray()[0]);
		assertTrue("Took too long", sw.getLastTaskTimeMillis() > time1);
	}

	@Test
	public void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() {
		PrimitiveArrayBean target = new PrimitiveArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(int.class, "array", new PropertyEditorSupport() {
			@Override
			public void setValue(Object value) {
				if (value instanceof Integer) {
					super.setValue(new Integer((Integer) value + 1));
				}
			}
		});
		int[] input = new int[1024];
		accessor.setPropertyValue("array", input);
		assertEquals(1024, target.getArray().length);
		assertEquals(1, target.getArray()[0]);
		assertEquals(1, target.getArray()[1]);
	}

	@Test
	public void setPrimitiveArrayPropertyLargeMatchingWithIndexSpecificEditor() {
		PrimitiveArrayBean target = new PrimitiveArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(int.class, "array[1]", new PropertyEditorSupport() {
			@Override
			public void setValue(Object value) {
				if (value instanceof Integer) {
					super.setValue(new Integer((Integer) value + 1));
				}
			}
		});
		int[] input = new int[1024];
		accessor.setPropertyValue("array", input);
		assertEquals(1024, target.getArray().length);
		assertEquals(0, target.getArray()[0]);
		assertEquals(1, target.getArray()[1]);
	}

	@Test
	public void setPrimitiveArrayPropertyWithAutoGrow() throws Exception {
		PrimitiveArrayBean target = new PrimitiveArrayBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setAutoGrowNestedPaths(true);

		accessor.setPropertyValue("array[0]", 1);
		assertEquals(1, target.getArray().length);

		accessor.setPropertyValue("array[2]", 3);
		assertEquals(3, target.getArray().length);
		assertTrue("correct values", target.getArray()[0] == 1 && target.getArray()[1] == 0 &&
				target.getArray()[2] == 3);
	}

	@Test
	public void setGenericArrayProperty() {
		SkipReaderStub target = new SkipReaderStub();
		AbstractPropertyAccessor accessor = createAccessor(target);
		List<String> values = new LinkedList<>();
		values.add("1");
		values.add("2");
		values.add("3");
		values.add("4");
		accessor.setPropertyValue("items", values);
		Object[] result = target.items;
		assertEquals(4, result.length);
		assertEquals("1", result[0]);
		assertEquals("2", result[1]);
		assertEquals("3", result[2]);
		assertEquals("4", result[3]);
	}

	@Test
	public void setArrayPropertyToObject() {
		ArrayToObject target = new ArrayToObject();
		AbstractPropertyAccessor accessor = createAccessor(target);

		Object[] array = new Object[] {"1", "2"};
		accessor.setPropertyValue("object", array);
		assertThat(target.getObject(), equalTo((Object) array));

		array = new Object[] {"1"};
		accessor.setPropertyValue("object", array);
		assertThat(target.getObject(), equalTo((Object) array));
	}


	@Test
	public void setCollectionProperty() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Collection<String> coll = new HashSet<>();
		coll.add("coll1");
		accessor.setPropertyValue("collection", coll);
		Set<String> set = new HashSet<>();
		set.add("set1");
		accessor.setPropertyValue("set", set);
		SortedSet<String> sortedSet = new TreeSet<>();
		sortedSet.add("sortedSet1");
		accessor.setPropertyValue("sortedSet", sortedSet);
		List<String> list = new LinkedList<>();
		list.add("list1");
		accessor.setPropertyValue("list", list);
		assertSame(coll, target.getCollection());
		assertSame(set, target.getSet());
		assertSame(sortedSet, target.getSortedSet());
		assertSame(list, target.getList());
	}

	@SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests
	@Test
	public void setCollectionPropertyNonMatchingType() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Collection<String> coll = new ArrayList<>();
		coll.add("coll1");
		accessor.setPropertyValue("collection", coll);
		List<String> set = new LinkedList<>();
		set.add("set1");
		accessor.setPropertyValue("set", set);
		List<String> sortedSet = new ArrayList<>();
		sortedSet.add("sortedSet1");
		accessor.setPropertyValue("sortedSet", sortedSet);
		Set<String> list = new HashSet<>();
		list.add("list1");
		accessor.setPropertyValue("list", list);
		assertEquals(1, target.getCollection().size());
		assertTrue(target.getCollection().containsAll(coll));
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().containsAll(set));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().containsAll(sortedSet));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().containsAll(list));
	}

	@SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests
	@Test
	public void setCollectionPropertyWithArrayValue() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Collection<String> coll = new HashSet<>();
		coll.add("coll1");
		accessor.setPropertyValue("collection", coll.toArray());
		List<String> set = new LinkedList<>();
		set.add("set1");
		accessor.setPropertyValue("set", set.toArray());
		List<String> sortedSet = new ArrayList<>();
		sortedSet.add("sortedSet1");
		accessor.setPropertyValue("sortedSet", sortedSet.toArray());
		Set<String> list = new HashSet<>();
		list.add("list1");
		accessor.setPropertyValue("list", list.toArray());
		assertEquals(1, target.getCollection().size());
		assertTrue(target.getCollection().containsAll(coll));
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().containsAll(set));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().containsAll(sortedSet));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().containsAll(list));
	}

	@SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests
	@Test
	public void setCollectionPropertyWithIntArrayValue() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Collection<Integer> coll = new HashSet<>();
		coll.add(0);
		accessor.setPropertyValue("collection", new int[] {0});
		List<Integer> set = new LinkedList<>();
		set.add(1);
		accessor.setPropertyValue("set", new int[] {1});
		List<Integer> sortedSet = new ArrayList<>();
		sortedSet.add(2);
		accessor.setPropertyValue("sortedSet", new int[] {2});
		Set<Integer> list = new HashSet<>();
		list.add(3);
		accessor.setPropertyValue("list", new int[] {3});
		assertEquals(1, target.getCollection().size());
		assertTrue(target.getCollection().containsAll(coll));
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().containsAll(set));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().containsAll(sortedSet));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().containsAll(list));
	}

	@SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests
	@Test
	public void setCollectionPropertyWithIntegerValue() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Collection<Integer> coll = new HashSet<>();
		coll.add(0);
		accessor.setPropertyValue("collection", new Integer(0));
		List<Integer> set = new LinkedList<>();
		set.add(1);
		accessor.setPropertyValue("set", new Integer(1));
		List<Integer> sortedSet = new ArrayList<>();
		sortedSet.add(2);
		accessor.setPropertyValue("sortedSet", new Integer(2));
		Set<Integer> list = new HashSet<>();
		list.add(3);
		accessor.setPropertyValue("list", new Integer(3));
		assertEquals(1, target.getCollection().size());
		assertTrue(target.getCollection().containsAll(coll));
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().containsAll(set));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().containsAll(sortedSet));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().containsAll(list));
	}

	@SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests
	@Test
	public void setCollectionPropertyWithStringValue() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		List<String> set = new LinkedList<>();
		set.add("set1");
		accessor.setPropertyValue("set", "set1");
		List<String> sortedSet = new ArrayList<>();
		sortedSet.add("sortedSet1");
		accessor.setPropertyValue("sortedSet", "sortedSet1");
		Set<String> list = new HashSet<>();
		list.add("list1");
		accessor.setPropertyValue("list", "list1");
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().containsAll(set));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().containsAll(sortedSet));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().containsAll(list));
	}

	@Test
	public void setCollectionPropertyWithStringValueAndCustomEditor() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(String.class, "set", new StringTrimmerEditor(false));
		accessor.registerCustomEditor(String.class, "list", new StringTrimmerEditor(false));

		accessor.setPropertyValue("set", "set1 ");
		accessor.setPropertyValue("sortedSet", "sortedSet1");
		accessor.setPropertyValue("list", "list1 ");
		assertEquals(1, target.getSet().size());
		assertTrue(target.getSet().contains("set1"));
		assertEquals(1, target.getSortedSet().size());
		assertTrue(target.getSortedSet().contains("sortedSet1"));
		assertEquals(1, target.getList().size());
		assertTrue(target.getList().contains("list1"));

		accessor.setPropertyValue("list", Collections.singletonList("list1 "));
		assertTrue(target.getList().contains("list1"));
	}

	@Test
	public void setMapProperty() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Map<String, String> map = new HashMap<>();
		map.put("key", "value");
		accessor.setPropertyValue("map", map);
		SortedMap<?, ?> sortedMap = new TreeMap<>();
		map.put("sortedKey", "sortedValue");
		accessor.setPropertyValue("sortedMap", sortedMap);
		assertSame(map, target.getMap());
		assertSame(sortedMap, target.getSortedMap());
	}

	@Test
	public void setMapPropertyNonMatchingType() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Map<String, String> map = new TreeMap<>();
		map.put("key", "value");
		accessor.setPropertyValue("map", map);
		Map<String, String> sortedMap = new TreeMap<>();
		sortedMap.put("sortedKey", "sortedValue");
		accessor.setPropertyValue("sortedMap", sortedMap);
		assertEquals(1, target.getMap().size());
		assertEquals("value", target.getMap().get("key"));
		assertEquals(1, target.getSortedMap().size());
		assertEquals("sortedValue", target.getSortedMap().get("sortedKey"));
	}

	@Test
	public void setMapPropertyWithTypeConversion() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) throws IllegalArgumentException {
				if (!StringUtils.hasLength(text)) {
					throw new IllegalArgumentException();
				}
				setValue(new TestBean(text));
			}
		});

		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("map[key1]", "rod");
		pvs.add("map[key2]", "rob");
		accessor.setPropertyValues(pvs);
		assertEquals("rod", ((TestBean) target.getMap().get("key1")).getName());
		assertEquals("rob", ((TestBean) target.getMap().get("key2")).getName());

		pvs = new MutablePropertyValues();
		pvs.add("map[key1]", "rod");
		pvs.add("map[key2]", "");
		try {
			accessor.setPropertyValues(pvs);
			fail("Should have thrown TypeMismatchException");
		}
		catch (PropertyBatchUpdateException ex) {
			PropertyAccessException pae = ex.getPropertyAccessException("map[key2]");
			assertTrue(pae instanceof TypeMismatchException);
		}
	}

	@Test
	public void setMapPropertyWithUnmodifiableMap() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) throws IllegalArgumentException {
				if (!StringUtils.hasLength(text)) {
					throw new IllegalArgumentException();
				}
				setValue(new TestBean(text));
			}
		});

		Map<Integer, String> inputMap = new HashMap<>();
		inputMap.put(1, "rod");
		inputMap.put(2, "rob");
		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("map", Collections.unmodifiableMap(inputMap));
		accessor.setPropertyValues(pvs);
		assertEquals("rod", ((TestBean) target.getMap().get(1)).getName());
		assertEquals("rob", ((TestBean) target.getMap().get(2)).getName());
	}

	@Test
	public void setMapPropertyWithCustomUnmodifiableMap() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) throws IllegalArgumentException {
				if (!StringUtils.hasLength(text)) {
					throw new IllegalArgumentException();
				}
				setValue(new TestBean(text));
			}
		});

		Map<Object, Object> inputMap = new HashMap<>();
		inputMap.put(1, "rod");
		inputMap.put(2, "rob");
		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("map", new ReadOnlyMap<>(inputMap));
		accessor.setPropertyValues(pvs);
		assertEquals("rod", ((TestBean) target.getMap().get(1)).getName());
		assertEquals("rob", ((TestBean) target.getMap().get(2)).getName());
	}

	@SuppressWarnings("unchecked") // must work with raw map in this test
	@Test
	public void setRawMapPropertyWithNoEditorRegistered() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		Map inputMap = new HashMap();
		inputMap.put(1, "rod");
		inputMap.put(2, "rob");
		ReadOnlyMap readOnlyMap = new ReadOnlyMap(inputMap);
		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("map", readOnlyMap);
		accessor.setPropertyValues(pvs);
		assertSame(readOnlyMap, target.getMap());
		assertFalse(readOnlyMap.isAccessed());
	}

	@Test
	public void setUnknownProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.setPropertyValue("name1", "value");
			fail("Should have failed to set an unknown property.");
		}
		catch (NotWritablePropertyException e) {
			assertEquals(Simple.class, e.getBeanClass());
			assertEquals("name1", e.getPropertyName());
			assertEquals("Invalid number of possible matches", 1, e.getPossibleMatches().length);
			assertEquals("name", e.getPossibleMatches()[0]);
		}
	}

	@Test
	public void setUnknownPropertyWithPossibleMatches() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			accessor.setPropertyValue("foo", "value");
			fail("Should have failed to set an unknown property.");
		}
		catch (NotWritablePropertyException e) {
			assertEquals(Simple.class, e.getBeanClass());
			assertEquals("foo", e.getPropertyName());
		}
	}

	@Test
	public void setUnknownOptionalProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		try {
			PropertyValue value = new PropertyValue("foo", "value");
			value.setOptional(true);
			accessor.setPropertyValue(value);
		}
		catch (NotWritablePropertyException e) {
			fail("Should not have failed to set an unknown optional property.");
		}
	}

	@Test
	public void setPropertyInProtectedBaseBean() {
		DerivedFromProtectedBaseBean target = new DerivedFromProtectedBaseBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("someProperty", "someValue");
		assertEquals("someValue", accessor.getPropertyValue("someProperty"));
		assertEquals("someValue", target.getSomeProperty());
	}

	@Test
	public void setPropertyTypeMismatch() {
		TestBean target = new TestBean();
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			accessor.setPropertyValue("age", "foobar");
			fail("Should throw exception on type mismatch");
		}
		catch (TypeMismatchException ex) {
			// expected
		}
	}

	@Test
	public void setEmptyValueForPrimitiveProperty() {
		TestBean target = new TestBean();
		try {
			AbstractPropertyAccessor accessor = createAccessor(target);
			accessor.setPropertyValue("age", "");
			fail("Should throw exception on type mismatch");
		}
		catch (TypeMismatchException ex) {
			// expected
		}
		catch (Exception ex) {
			fail("Shouldn't throw exception other than Type mismatch");
		}
	}

	@Test
	public void setUnknownNestedProperty() {
		Person target = createPerson("John", "Paris", "FR");
		AbstractPropertyAccessor accessor = createAccessor(target);

		thrown.expect(NotWritablePropertyException.class);
		accessor.setPropertyValue("address.bar", "value");
	}

	@Test
	public void setPropertyValuesIgnoresInvalidNestedOnRequest() {
		ITestBean target = new TestBean();
		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.addPropertyValue(new PropertyValue("name", "rod"));
		pvs.addPropertyValue(new PropertyValue("graceful.rubbish", "tony"));
		pvs.addPropertyValue(new PropertyValue("more.garbage", new Object()));
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValues(pvs, true);
		assertTrue("Set valid and ignored invalid", target.getName().equals("rod"));
		try {
			// Don't ignore: should fail
			accessor.setPropertyValues(pvs, false);
			fail("Shouldn't have ignored invalid updates");
		}
		catch (NotWritablePropertyException ex) {
			// OK: but which exception??
		}
	}

	@Test
	public void getAndSetIndexedProperties() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		TestBean tb0 = target.getArray()[0];
		TestBean tb1 = target.getArray()[1];
		TestBean tb2 = ((TestBean) target.getList().get(0));
		TestBean tb3 = ((TestBean) target.getList().get(1));
		TestBean tb6 = ((TestBean) target.getSet().toArray()[0]);
		TestBean tb7 = ((TestBean) target.getSet().toArray()[1]);
		TestBean tb4 = ((TestBean) target.getMap().get("key1"));
		TestBean tb5 = ((TestBean) target.getMap().get("key.3"));
		TestBean tb8 = ((TestBean) target.getMap().get("key5[foo]"));
		assertEquals("name0", tb0.getName());
		assertEquals("name1", tb1.getName());
		assertEquals("name2", tb2.getName());
		assertEquals("name3", tb3.getName());
		assertEquals("name6", tb6.getName());
		assertEquals("name7", tb7.getName());
		assertEquals("name4", tb4.getName());
		assertEquals("name5", tb5.getName());
		assertEquals("name8", tb8.getName());
		assertEquals("name0", accessor.getPropertyValue("array[0].name"));
		assertEquals("name1", accessor.getPropertyValue("array[1].name"));
		assertEquals("name2", accessor.getPropertyValue("list[0].name"));
		assertEquals("name3", accessor.getPropertyValue("list[1].name"));
		assertEquals("name6", accessor.getPropertyValue("set[0].name"));
		assertEquals("name7", accessor.getPropertyValue("set[1].name"));
		assertEquals("name4", accessor.getPropertyValue("map[key1].name"));
		assertEquals("name5", accessor.getPropertyValue("map[key.3].name"));
		assertEquals("name4", accessor.getPropertyValue("map['key1'].name"));
		assertEquals("name5", accessor.getPropertyValue("map[\"key.3\"].name"));
		assertEquals("nameX", accessor.getPropertyValue("map[key4][0].name"));
		assertEquals("nameY", accessor.getPropertyValue("map[key4][1].name"));
		assertEquals("name8", accessor.getPropertyValue("map[key5[foo]].name"));
		assertEquals("name8", accessor.getPropertyValue("map['key5[foo]'].name"));
		assertEquals("name8", accessor.getPropertyValue("map[\"key5[foo]\"].name"));

		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("array[0].name", "name5");
		pvs.add("array[1].name", "name4");
		pvs.add("list[0].name", "name3");
		pvs.add("list[1].name", "name2");
		pvs.add("set[0].name", "name8");
		pvs.add("set[1].name", "name9");
		pvs.add("map[key1].name", "name1");
		pvs.add("map['key.3'].name", "name0");
		pvs.add("map[key4][0].name", "nameA");
		pvs.add("map[key4][1].name", "nameB");
		pvs.add("map[key5[foo]].name", "name10");
		accessor.setPropertyValues(pvs);
		assertEquals("name5", tb0.getName());
		assertEquals("name4", tb1.getName());
		assertEquals("name3", tb2.getName());
		assertEquals("name2", tb3.getName());
		assertEquals("name1", tb4.getName());
		assertEquals("name0", tb5.getName());
		assertEquals("name5", accessor.getPropertyValue("array[0].name"));
		assertEquals("name4", accessor.getPropertyValue("array[1].name"));
		assertEquals("name3", accessor.getPropertyValue("list[0].name"));
		assertEquals("name2", accessor.getPropertyValue("list[1].name"));
		assertEquals("name8", accessor.getPropertyValue("set[0].name"));
		assertEquals("name9", accessor.getPropertyValue("set[1].name"));
		assertEquals("name1", accessor.getPropertyValue("map[\"key1\"].name"));
		assertEquals("name0", accessor.getPropertyValue("map['key.3'].name"));
		assertEquals("nameA", accessor.getPropertyValue("map[key4][0].name"));
		assertEquals("nameB", accessor.getPropertyValue("map[key4][1].name"));
		assertEquals("name10", accessor.getPropertyValue("map[key5[foo]].name"));
	}

	@Test
	public void getAndSetIndexedPropertiesWithDirectAccess() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		TestBean tb0 = target.getArray()[0];
		TestBean tb1 = target.getArray()[1];
		TestBean tb2 = ((TestBean) target.getList().get(0));
		TestBean tb3 = ((TestBean) target.getList().get(1));
		TestBean tb6 = ((TestBean) target.getSet().toArray()[0]);
		TestBean tb7 = ((TestBean) target.getSet().toArray()[1]);
		TestBean tb4 = ((TestBean) target.getMap().get("key1"));
		TestBean tb5 = ((TestBean) target.getMap().get("key2"));
		assertEquals(tb0, accessor.getPropertyValue("array[0]"));
		assertEquals(tb1, accessor.getPropertyValue("array[1]"));
		assertEquals(tb2, accessor.getPropertyValue("list[0]"));
		assertEquals(tb3, accessor.getPropertyValue("list[1]"));
		assertEquals(tb6, accessor.getPropertyValue("set[0]"));
		assertEquals(tb7, accessor.getPropertyValue("set[1]"));
		assertEquals(tb4, accessor.getPropertyValue("map[key1]"));
		assertEquals(tb5, accessor.getPropertyValue("map[key2]"));
		assertEquals(tb4, accessor.getPropertyValue("map['key1']"));
		assertEquals(tb5, accessor.getPropertyValue("map[\"key2\"]"));

		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("array[0]", tb5);
		pvs.add("array[1]", tb4);
		pvs.add("list[0]", tb3);
		pvs.add("list[1]", tb2);
		pvs.add("list[2]", tb0);
		pvs.add("list[4]", tb1);
		pvs.add("map[key1]", tb1);
		pvs.add("map['key2']", tb0);
		pvs.add("map[key5]", tb4);
		pvs.add("map['key9']", tb5);
		accessor.setPropertyValues(pvs);
		assertEquals(tb5, target.getArray()[0]);
		assertEquals(tb4, target.getArray()[1]);
		assertEquals(tb3, (target.getList().get(0)));
		assertEquals(tb2, (target.getList().get(1)));
		assertEquals(tb0, (target.getList().get(2)));
		assertEquals(null, (target.getList().get(3)));
		assertEquals(tb1, (target.getList().get(4)));
		assertEquals(tb1, (target.getMap().get("key1")));
		assertEquals(tb0, (target.getMap().get("key2")));
		assertEquals(tb4, (target.getMap().get("key5")));
		assertEquals(tb5, (target.getMap().get("key9")));
		assertEquals(tb5, accessor.getPropertyValue("array[0]"));
		assertEquals(tb4, accessor.getPropertyValue("array[1]"));
		assertEquals(tb3, accessor.getPropertyValue("list[0]"));
		assertEquals(tb2, accessor.getPropertyValue("list[1]"));
		assertEquals(tb0, accessor.getPropertyValue("list[2]"));
		assertEquals(null, accessor.getPropertyValue("list[3]"));
		assertEquals(tb1, accessor.getPropertyValue("list[4]"));
		assertEquals(tb1, accessor.getPropertyValue("map[\"key1\"]"));
		assertEquals(tb0, accessor.getPropertyValue("map['key2']"));
		assertEquals(tb4, accessor.getPropertyValue("map[\"key5\"]"));
		assertEquals(tb5, accessor.getPropertyValue("map['key9']"));
	}

	@Test
	public void propertyType() {
		Person target = createPerson("John", "Paris", "FR");
		AbstractPropertyAccessor accessor = createAccessor(target);

		assertEquals(String.class, accessor.getPropertyType("address.city"));
	}

	@Test
	public void propertyTypeUnknownProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		assertThat(accessor.getPropertyType("foo"), is(nullValue()));
	}

	@Test
	public void propertyTypeDescriptor() {
		Person target = createPerson("John", "Paris", "FR");
		AbstractPropertyAccessor accessor = createAccessor(target);

		assertThat(accessor.getPropertyTypeDescriptor("address.city"), is(notNullValue()));
	}

	@Test
	public void propertyTypeDescriptorUnknownProperty() {
		Simple target = new Simple("John", 2);
		AbstractPropertyAccessor accessor = createAccessor(target);

		assertThat(accessor.getPropertyTypeDescriptor("foo"), is(nullValue()));
	}

	@Test
	public void propertyTypeIndexedProperty() {
		IndexedTestBean target = new IndexedTestBean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		assertEquals(null, accessor.getPropertyType("map[key0]"));

		accessor = createAccessor(target);
		accessor.setPropertyValue("map[key0]", "my String");
		assertEquals(String.class, accessor.getPropertyType("map[key0]"));

		accessor = createAccessor(target);
		accessor.registerCustomEditor(String.class, "map[key0]", new StringTrimmerEditor(false));
		assertEquals(String.class, accessor.getPropertyType("map[key0]"));
	}

	@Test
	public void cornerSpr10115() {
		Spr10115Bean target = new Spr10115Bean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("prop1", "val1");
		assertEquals("val1", Spr10115Bean.prop1);
	}

	@Test
	public void cornerSpr13837() {
		Spr13837Bean target = new Spr13837Bean();
		AbstractPropertyAccessor accessor = createAccessor(target);
		accessor.setPropertyValue("something", 42);
		assertEquals(Integer.valueOf(42), target.something);
	}


	private Person createPerson(String name, String city, String country) {
		return new Person(name, new Address(city, country));
	}


	private static class Simple {

		private String name;

		private Integer integer;

		private Simple(String name, Integer integer) {
			this.name = name;
			this.integer = integer;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Integer getInteger() {
			return integer;
		}

		public void setInteger(Integer integer) {
			this.integer = integer;
		}
	}

	private static class Person {
		private String name;

		private Address address;

		private Person(String name, Address address) {
			this.name = name;
			this.address = address;
		}

		public Person() {
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Address getAddress() {
			return address;
		}

		public void setAddress(Address address) {
			this.address = address;
		}
	}

	private static class Address {
		private String city;

		private Country country;

		private Address(String city, String country) {
			this.city = city;
			this.country = new Country(country);
		}

		public Address() {
			this("DefaultCity", "DefaultCountry");
		}

		public String getCity() {
			return city;
		}

		public void setCity(String city) {
			this.city = city;
		}

		public Country getCountry() {
			return country;
		}

		public void setCountry(Country country) {
			this.country = country;
		}
	}

	private static class Country {
		private String name;

		public Country(String name) {
			this.name = name;
		}

		public Country() {
			this(null);
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}
	}


	@SuppressWarnings("unused")
	static class NoRead {

		public void setAge(int age) {
		}
	}

	@SuppressWarnings("unused")
	private static class Foo {

		private List list;

		private List<Map> listOfMaps;

		public List getList() {
			return list;
		}

		public void setList(List list) {
			this.list = list;
		}

		public List<Map> getListOfMaps() {
			return listOfMaps;
		}

		public void setListOfMaps(List<Map> listOfMaps) {
			this.listOfMaps = listOfMaps;
		}
	}


	@SuppressWarnings("unused")
	private static class EnumTester {

		private Autowire autowire;

		public void setAutowire(Autowire autowire) {
			this.autowire = autowire;
		}

		public Autowire getAutowire() {
			return autowire;
		}
	}

	@SuppressWarnings("unused")
	private static class PropsTester {

		private Properties properties;

		private String name;

		private String[] stringArray;

		private int[] intArray;

		public void setProperties(Properties p) {
			properties = p;
		}

		public void setName(String name) {
			this.name = name;
		}

		public void setStringArray(String[] sa) {
			this.stringArray = sa;
		}

		public void setIntArray(int[] intArray) {
			this.intArray = intArray;
		}
	}

	@SuppressWarnings("unused")
	private static class StringArrayBean {

		private String[] array;

		public String[] getArray() {
			return array;
		}

		public void setArray(String[] array) {
			this.array = array;
		}
	}


	@SuppressWarnings("unused")
	private static class PrimitiveArrayBean {

		private int[] array;

		public int[] getArray() {
			return array;
		}

		public void setArray(int[] array) {
			this.array = array;
		}
	}

	@SuppressWarnings("unused")
	private static class Employee extends TestBean {

		private String company;

		public String getCompany() {
			return company;
		}

		public void setCompany(String co) {
			this.company = co;
		}
	}

	@SuppressWarnings("unused")
	private static class DifferentTestBean extends TestBean {
		// class to test naming of beans in a error message
	}


	@SuppressWarnings("unused")
	private static class NumberPropertyBean {

		private byte myPrimitiveByte;

		private Byte myByte;

		private short myPrimitiveShort;

		private Short myShort;

		private int myPrimitiveInt;

		private Integer myInteger;

		private long myPrimitiveLong;

		private Long myLong;

		private float myPrimitiveFloat;

		private Float myFloat;

		private double myPrimitiveDouble;

		private Double myDouble;

		public byte getMyPrimitiveByte() {
			return myPrimitiveByte;
		}

		public void setMyPrimitiveByte(byte myPrimitiveByte) {
			this.myPrimitiveByte = myPrimitiveByte;
		}

		public Byte getMyByte() {
			return myByte;
		}

		public void setMyByte(Byte myByte) {
			this.myByte = myByte;
		}

		public short getMyPrimitiveShort() {
			return myPrimitiveShort;
		}

		public void setMyPrimitiveShort(short myPrimitiveShort) {
			this.myPrimitiveShort = myPrimitiveShort;
		}

		public Short getMyShort() {
			return myShort;
		}

		public void setMyShort(Short myShort) {
			this.myShort = myShort;
		}

		public int getMyPrimitiveInt() {
			return myPrimitiveInt;
		}

		public void setMyPrimitiveInt(int myPrimitiveInt) {
			this.myPrimitiveInt = myPrimitiveInt;
		}

		public Integer getMyInteger() {
			return myInteger;
		}

		public void setMyInteger(Integer myInteger) {
			this.myInteger = myInteger;
		}

		public long getMyPrimitiveLong() {
			return myPrimitiveLong;
		}

		public void setMyPrimitiveLong(long myPrimitiveLong) {
			this.myPrimitiveLong = myPrimitiveLong;
		}

		public Long getMyLong() {
			return myLong;
		}

		public void setMyLong(Long myLong) {
			this.myLong = myLong;
		}

		public float getMyPrimitiveFloat() {
			return myPrimitiveFloat;
		}

		public void setMyPrimitiveFloat(float myPrimitiveFloat) {
			this.myPrimitiveFloat = myPrimitiveFloat;
		}

		public Float getMyFloat() {
			return myFloat;
		}

		public void setMyFloat(Float myFloat) {
			this.myFloat = myFloat;
		}

		public double getMyPrimitiveDouble() {
			return myPrimitiveDouble;
		}

		public void setMyPrimitiveDouble(double myPrimitiveDouble) {
			this.myPrimitiveDouble = myPrimitiveDouble;
		}

		public Double getMyDouble() {
			return myDouble;
		}

		public void setMyDouble(Double myDouble) {
			this.myDouble = myDouble;
		}
	}


	public static class EnumConsumer {

		private Enum<TestEnum> enumValue;

		public Enum<TestEnum> getEnumValue() {
			return enumValue;
		}

		public void setEnumValue(Enum<TestEnum> enumValue) {
			this.enumValue = enumValue;
		}
	}


	public static class WildcardEnumConsumer {

		private Enum<?> enumValue;

		public Enum<?> getEnumValue() {
			return enumValue;
		}

		public void setEnumValue(Enum<?> enumValue) {
			this.enumValue = enumValue;
		}
	}


	public enum TestEnum {

		TEST_VALUE
	}


	public static class ArrayToObject {

		private Object object;

		public void setObject(Object object) {
			this.object = object;
		}

		public Object getObject() {
			return object;
		}
	}


	public static class SkipReaderStub<T> {

		public T[] items;

		public SkipReaderStub() {
		}

		public SkipReaderStub(T... items) {
			this.items = items;
		}

		public void setItems(T... items) {
			this.items = items;
		}
	}


	static class Spr10115Bean {

		private static String prop1;

		public static void setProp1(String prop1) {
			Spr10115Bean.prop1 = prop1;
		}
	}

	interface Spr13837 {

		Integer getSomething();

		<T extends Spr13837> T setSomething(Integer something);

	}

	static class Spr13837Bean implements Spr13837 {

		protected Integer something;

		@Override
		public Integer getSomething() {
			return this.something;
		}

		@Override
		public Spr13837Bean setSomething(final Integer something) {
			this.something = something;
			return this;
		}
	}

	@SuppressWarnings("serial")
	public static class ReadOnlyMap<K, V> extends HashMap<K, V> {

		private boolean frozen = false;

		private boolean accessed = false;

		public ReadOnlyMap() {
			this.frozen = true;
		}

		public ReadOnlyMap(Map<? extends K, ? extends V> map) {
			super(map);
			this.frozen = true;
		}

		@Override
		public V put(K key, V value) {
			if (this.frozen) {
				throw new UnsupportedOperationException();
			}
			else {
				return super.put(key, value);
			}
		}

		@Override
		public Set<Map.Entry<K, V>> entrySet() {
			this.accessed = true;
			return super.entrySet();
		}

		@Override
		public Set<K> keySet() {
			this.accessed = true;
			return super.keySet();
		}

		@Override
		public int size() {
			this.accessed = true;
			return super.size();
		}

		public boolean isAccessed() {
			return this.accessed;
		}
	}

}
