/* * Copyright 2017 Google Inc. * * 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 com.google.firebase.database; import static com.google.firebase.database.TestHelpers.fromSingleQuotedString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import com.google.firebase.database.utilities.encoding.CustomClassMapper; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.Test; public class MapperTest { private static final double EPISLON = 0.00001f; private static <T> T deserialize(String jsonString, Class<T> clazz) { Map<String, Object> json = fromSingleQuotedString(jsonString); return CustomClassMapper.convertToCustomClass(json, clazz); } private static <T> T deserialize(String jsonString, GenericTypeIndicator<T> typeIndicator) { Map<String, Object> json = fromSingleQuotedString(jsonString); return CustomClassMapper.convertToCustomClass(json, typeIndicator); } private static Object serialize(Object object) { return CustomClassMapper.convertToPlainJavaTypes(object); } private static void assertJson(String expected, Object actual) { assertEquals(fromSingleQuotedString(expected), actual); } @Test public void primitiveDeserializeString() { StringBean bean = deserialize("{'value': 'foo'}", StringBean.class); assertEquals("foo", bean.value); // Double try { deserialize("{'value': 1.1}", StringBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Int try { deserialize("{'value': 1}", StringBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", StringBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Boolean try { deserialize("{'value': true}", StringBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test public void primitiveDeserializeBoolean() { BooleanBean beanBoolean = deserialize("{'value': true}", BooleanBean.class); assertEquals(true, beanBoolean.value); // Double try { deserialize("{'value': 1.1}", BooleanBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", BooleanBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Int try { deserialize("{'value': 1}", BooleanBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // String try { deserialize("{'value': 'foo'}", BooleanBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test public void primitiveDeserializeDouble() { DoubleBean beanDouble = deserialize("{'value': 1.1}", DoubleBean.class); assertEquals(1.1, beanDouble.value, EPISLON); // Int DoubleBean beanInt = deserialize("{'value': 1}", DoubleBean.class); assertEquals(1, beanInt.value, EPISLON); // Long DoubleBean beanLong = deserialize("{'value': 1234567890123}", DoubleBean.class); assertEquals(1234567890123L, beanLong.value, EPISLON); // Boolean try { deserialize("{'value': true}", DoubleBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // String try { deserialize("{'value': 'foo'}", DoubleBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test public void primitiveDeserializeFloat() { FloatBean beanFloat = deserialize("{'value': 1.1}", FloatBean.class); assertEquals(1.1, beanFloat.value, EPISLON); // Int FloatBean beanInt = deserialize("{'value': 1}", FloatBean.class); assertEquals(1, beanInt.value, EPISLON); // Long FloatBean beanLong = deserialize("{'value': 1234567890123}", FloatBean.class); assertEquals(Long.valueOf(1234567890123L).floatValue(), beanLong.value, EPISLON); // Boolean try { deserialize("{'value': true}", FloatBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // String try { deserialize("{'value': 'foo'}", FloatBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test public void primitiveDeserializeInt() { IntBean beanInt = deserialize("{'value': 1}", IntBean.class); assertEquals(1, beanInt.value); // Double IntBean beanDouble = deserialize("{'value': 1.1}", IntBean.class); assertEquals(1, beanDouble.value); // Large doubles try { deserialize("{'value': 1e10}", IntBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", IntBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Boolean try { deserialize("{'value': true}", IntBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // String try { deserialize("{'value': 'foo'}", IntBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test public void primitiveDeserializeLong() { LongBean beanLong = deserialize("{'value': 1234567890123}", LongBean.class); assertEquals(1234567890123L, beanLong.value); //Int LongBean beanInt = deserialize("{'value': 1}", LongBean.class); assertEquals(1, beanInt.value); // Double LongBean beanDouble = deserialize("{'value': 1.1}", LongBean.class); assertEquals(1, beanDouble.value); // Large doubles try { deserialize("{'value': 1e300}", LongBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // Boolean try { deserialize("{'value': true}", LongBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } // String try { deserialize("{'value': 'foo'}", LongBean.class); fail("Should throw"); } catch (DatabaseException e) { // ignore } } @Test(expected = DatabaseException.class) public void primitiveDeserializeWrongTypeMap() { deserialize("{'value': {'foo': 'bar'}}", StringBean.class); } @Test(expected = DatabaseException.class) public void primitiveDeserializeWrongTypeList() { deserialize("{'value': ['foo']}", StringBean.class); } @Test public void publicFieldDeserialze() { PublicFieldBean bean = deserialize("{'value': 'foo'}", PublicFieldBean.class); assertEquals("foo", bean.value); } @Test public void publicPrivateFieldDeserialze() { PublicPrivateFieldBean bean = deserialize( "{'value1': 'foo', 'value2': 'bar', 'value3': 'baz'}", PublicPrivateFieldBean.class); assertEquals("foo", bean.value1); assertEquals(null, bean.value2); assertEquals(null, bean.value3); } @Test(expected = DatabaseException.class) public void packageFieldDeserialze() { deserialize("{'value': 'foo'}", PackageFieldBean.class); } @Test(expected = DatabaseException.class) public void privateFieldDeserialize() { deserialize("{'value': 'foo'}", PrivateFieldBean.class); } @Test public void packageGetterDeserialize() { PackageGetterBean bean = deserialize("{'publicValue': 'foo', 'packageValue': 'bar'}", PackageGetterBean.class); assertEquals("foo", bean.publicValue); assertNull(bean.packageValue); } @Test public void packageGetterSerialize() { PackageGetterBean bean = new PackageGetterBean(); bean.packageValue = "foo"; bean.publicValue = "bar"; assertJson("{'publicValue': 'bar'}", serialize(bean)); } @Test public void ignoreExtraProperties() { PublicFieldBean bean = deserialize("{'value': 'foo', 'unknown': 'bar'}", PublicFieldBean.class); assertEquals("foo", bean.value); } @Test(expected = DatabaseException.class) public void throwOnUnknownProperties() { deserialize("{'value': 'foo', 'unknown': 'bar'}", ThrowOnUnknownPropertiesBean.class); } @Test(expected = DatabaseException.class) public void twoSetterBean() { deserialize("{'value': 'foo'}", TwoSetterBean.class); } @Test public void testXmlAndUrlBean() { XMLAndURLBean bean = deserialize("{'xmlandURL1': 'foo', 'XMLAndURL2': 'bar'}", XMLAndURLBean.class); assertEquals("foo", bean.XMLAndURL1); assertEquals("bar", bean.XMLAndURL2); } @Test public void setterIsCalledWhenPresent() { SetterBean bean = deserialize("{'value': 'foo'}", SetterBean.class); assertEquals("setter:foo", bean.value); } @Test public void privateSetterIsCalledWhenPresent() { PrivateSetterBean bean = deserialize("{'value': 'foo'}", PrivateSetterBean.class); assertEquals("setter:foo", bean.value); } @Test(expected = DatabaseException.class) public void setterIsCaseSensitive1() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean1.class); } @Test(expected = DatabaseException.class) public void setterIsCaseSensitive2() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean2.class); } @Test public void caseSensitiveSetterIsCalledWhenPresent1() { CaseSensitiveSetterBean3 bean = deserialize("{'value': 'foo'}", CaseSensitiveSetterBean3.class); assertEquals("setter:foo", bean.value); } @Test public void caseSensitiveSetterIsCalledWhenPresent2() { CaseSensitiveSetterBean4 bean = deserialize("{'value': 'foo'}", CaseSensitiveSetterBean4.class); assertEquals("setter:foo", bean.value); } @Test public void caseSensitiveSetterIsCalledWhenPresent3() { CaseSensitiveSetterBean5 bean = deserialize("{'value': 'foo'}", CaseSensitiveSetterBean5.class); assertEquals("foo", bean.value); } @Test(expected = DatabaseException.class) public void caseSensitiveSetterMustHaveSameCaseAsSetter() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean6.class); } @Test public void wrongSetterIsNotCalledWhenPresent() { WrongSetterBean bean = deserialize("{'value': 'foo'}", WrongSetterBean.class); assertEquals("foo", bean.value); } @Test public void recursiveParsingWorks() { RecursiveBean bean = deserialize("{'bean': {'value': 'foo'}}", RecursiveBean.class); assertEquals("foo", bean.bean.value); } @Test public void beansCanContainLists() { ListBean bean = deserialize("{'values': ['foo', 'bar']}", ListBean.class); assertEquals(Arrays.asList("foo", "bar"), bean.values); } @Test public void beansCanContainMaps() { MapBean bean = deserialize("{'values': {'foo': 'bar'}}", MapBean.class); Map<String, Object> expected = fromSingleQuotedString("{'foo': 'bar'}"); assertEquals(expected, bean.values); } @Test public void beansCanContainBeanLists() { RecursiveListBean bean = deserialize("{'values': [{'value': 'foo'}]}", RecursiveListBean.class); assertEquals(1, bean.values.size()); assertEquals("foo", bean.values.get(0).value); } @Test public void beansCanContainBeanMaps() { RecursiveMapBean bean = deserialize("{'values': {'key': {'value': 'foo'}}}", RecursiveMapBean.class); assertEquals(1, bean.values.size()); assertEquals("foo", bean.values.get("key").value); } @Test(expected = DatabaseException.class) public void beanMapsMustHaveStringKeys() { deserialize("{'values': {'1': 'bar'}}", IllegalKeyMapBean.class); } @Test public void serializeStringBean() { StringBean bean = new StringBean(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); } @Test public void serializeDoubleBean() { DoubleBean bean = new DoubleBean(); bean.value = 1.1; assertJson("{'value': 1.1}", serialize(bean)); } @Test public void serializeIntBean() { IntBean bean = new IntBean(); bean.value = 1; assertJson("{'value': 1}", serialize(bean)); } @Test public void serializeLongBean() { LongBean bean = new LongBean(); bean.value = 1234567890123L; assertJson("{'value': 1234567890123}", serialize(bean)); } @Test public void serializeBooleanBean() { BooleanBean bean = new BooleanBean(); bean.value = true; assertJson("{'value': true}", serialize(bean)); } @Test public void serializeFloatBean() { FloatBean bean = new FloatBean(); bean.value = 0.5f; assertJson("{'value': 0.5}", serialize(bean)); } @Test public void serializePublicFieldBean() { PublicFieldBean bean = new PublicFieldBean(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); } @Test(expected = DatabaseException.class) public void serializePrivateFieldBean() { PrivateFieldBean bean = new PrivateFieldBean(); bean.value = "foo"; serialize(bean); } @Test(expected = DatabaseException.class) public void serializePackageFieldBean() { PackageFieldBean bean = new PackageFieldBean(); bean.value = "foo"; serialize(bean); } @Test public void serializePublicPrivateFieldBean() { PublicPrivateFieldBean bean = new PublicPrivateFieldBean(); bean.value1 = "foo"; bean.value2 = "bar"; bean.value3 = "baz"; assertJson("{'value1': 'foo'}", serialize(bean)); } @Test public void getterOverridesField() { GetterBean bean = new GetterBean(); bean.value = "foo"; assertJson("{'value': 'getter:foo'}", serialize(bean)); } @Test public void getterOverridesPublicField() { GetterPublicFieldBean bean = new GetterPublicFieldBean(); bean.value = "foo"; assertJson("{'value': 'getter:foo'}", serialize(bean)); } @Test(expected = DatabaseException.class) public void getterAndPublicFieldsConflictOnCaseSensitivity() { GetterPublicFieldBeanCaseSensitive bean = new GetterPublicFieldBeanCaseSensitive(); bean.valueCase = "foo"; serialize(bean); } @Test public void caseSensitveGetterBean1() { CaseSensitiveGetterBean1 bean = new CaseSensitiveGetterBean1(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); } @Test public void caseSensitveGetterBean2() { CaseSensitiveGetterBean2 bean = new CaseSensitiveGetterBean2(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); } @Test public void caseSensitveGetterBean3() { CaseSensitiveGetterBean3 bean = new CaseSensitiveGetterBean3(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); } @Test public void caseSensitveGetterBean4() { CaseSensitiveGetterBean4 bean = new CaseSensitiveGetterBean4(); bean.value = "foo"; assertJson("{'vaLUE': 'foo'}", serialize(bean)); } @Test public void recursiveSerializingWorks() { RecursiveBean bean = new RecursiveBean(); bean.bean = new StringBean(); bean.bean.value = "foo"; assertJson("{'bean': {'value': 'foo'}}", serialize(bean)); } @Test public void serializingListsWorks() { ListBean bean = new ListBean(); bean.values = Arrays.asList("foo", "bar"); assertJson("{'values': ['foo', 'bar']}", serialize(bean)); } @Test public void serializingMapsWorks() { MapBean bean = new MapBean(); bean.values = new HashMap<String, String>() { { put("foo", "bar"); } }; assertJson("{'values': {'foo': 'bar'}}", serialize(bean)); } @Test public void serializeListOfBeansWorks() { RecursiveListBean bean = new RecursiveListBean(); bean.values = new ArrayList<StringBean>() { { StringBean bean = new StringBean(); bean.value = "foo"; add(bean); } }; assertJson("{'values': [{'value': 'foo'}]}", serialize(bean)); } @Test public void serializeMapOfBeansWorks() { RecursiveMapBean bean = new RecursiveMapBean(); bean.values = new HashMap<String, StringBean>() { { StringBean bean = new StringBean(); bean.value = "foo"; put("key", bean); } }; assertJson("{'values': {'key': {'value': 'foo'}}}", serialize(bean)); } @Test(expected = DatabaseException.class) public void beanMapsMustHaveStringKeysForSerializing() { IllegalKeyMapBean bean = new IllegalKeyMapBean(); bean.values = new HashMap<Integer, StringBean>() { { StringBean bean = new StringBean(); bean.value = "foo"; put(1, bean); } }; serialize(bean); } @Test(expected = DatabaseException.class) public void twoGettersThrows() { TwoGetterBean bean = new TwoGetterBean(); bean.value = "foo"; serialize(bean); } @Test public void serializeUpperCase() { XMLAndURLBean bean = new XMLAndURLBean(); bean.XMLAndURL1 = "foo"; bean.XMLAndURL2 = "bar"; assertJson("{'xmlandURL1': 'foo', 'XMLAndURL2': 'bar'}", serialize(bean)); } @Test public void onlySerializesGetterWithCorrectArguments() { GetterArgumentsBean bean = new GetterArgumentsBean(); bean.value = "foo"; assertJson("{'value1': 'foo1', 'value4': 'foo4'}", serialize(bean)); } @Test public void roundTripCaseSensitiveFieldBean1() { CaseSensitiveFieldBean1 bean = new CaseSensitiveFieldBean1(); bean.VALUE = "foo"; assertJson("{'VALUE': 'foo'}", serialize(bean)); CaseSensitiveFieldBean1 deserialized = deserialize("{'VALUE': 'foo'}", CaseSensitiveFieldBean1.class); assertEquals("foo", deserialized.VALUE); } @Test public void roundTripCaseSensitiveFieldBean2() { CaseSensitiveFieldBean2 bean = new CaseSensitiveFieldBean2(); bean.value = "foo"; assertJson("{'value': 'foo'}", serialize(bean)); CaseSensitiveFieldBean2 deserialized = deserialize("{'value': 'foo'}", CaseSensitiveFieldBean2.class); assertEquals("foo", deserialized.value); } @Test public void roundTripCaseSensitiveFieldBean3() { CaseSensitiveFieldBean3 bean = new CaseSensitiveFieldBean3(); bean.Value = "foo"; assertJson("{'Value': 'foo'}", serialize(bean)); CaseSensitiveFieldBean3 deserialized = deserialize("{'Value': 'foo'}", CaseSensitiveFieldBean3.class); assertEquals("foo", deserialized.Value); } @Test public void roundTripCaseSensitiveFieldBean4() { CaseSensitiveFieldBean4 bean = new CaseSensitiveFieldBean4(); bean.valUE = "foo"; assertJson("{'valUE': 'foo'}", serialize(bean)); CaseSensitiveFieldBean4 deserialized = deserialize("{'valUE': 'foo'}", CaseSensitiveFieldBean4.class); assertEquals("foo", deserialized.valUE); } @Test public void roundTripUnicodeBean() { UnicodeBean bean = new UnicodeBean(); bean.漢字 = "foo"; assertJson("{'漢字': 'foo'}", serialize(bean)); UnicodeBean deserialized = deserialize("{'漢字': 'foo'}", UnicodeBean.class); assertEquals("foo", deserialized.漢字); } @Test(expected = DatabaseException.class) public void shortsCantBeSerialized() { ShortBean bean = new ShortBean(); bean.value = 1; serialize(bean); } @Test(expected = DatabaseException.class) public void bytesCantBeSerialized() { ByteBean bean = new ByteBean(); bean.value = 1; serialize(bean); } @Test(expected = DatabaseException.class) public void charsCantBeSerialized() { CharBean bean = new CharBean(); bean.value = 1; serialize(bean); } @Test(expected = DatabaseException.class) public void intArraysCantBeSerialized() { IntArrayBean bean = new IntArrayBean(); bean.values = new int[] {1}; serialize(bean); } @Test(expected = DatabaseException.class) public void objectArraysCantBeSerialized() { StringArrayBean bean = new StringArrayBean(); bean.values = new String[] {"foo"}; serialize(bean); } @Test(expected = DatabaseException.class) public void shortsCantBeDeserialized() { deserialize("{'value': 1}", ShortBean.class); } @Test(expected = DatabaseException.class) public void bytesCantBeDeserialized() { deserialize("{'value': 1}", ByteBean.class); } @Test(expected = DatabaseException.class) public void charsCantBeDeserialized() { deserialize("{'value': '1'}", CharBean.class); } @Test(expected = DatabaseException.class) public void intArraysCantBeDeserialized() { deserialize("{'values': [1]}", IntArrayBean.class); } @Test(expected = DatabaseException.class) public void objectArraysCantBeDeserialized() { deserialize("{'values': ['foo']}", StringArrayBean.class); } @Test public void publicConstructorCanBeDeserialized() { PublicConstructorBean bean = deserialize("{'value': 'foo'}", PublicConstructorBean.class); assertEquals("foo", bean.value); } @Test public void privateConstructorCanBeDeserialized() { PrivateConstructorBean bean = deserialize("{'value': 'foo'}", PrivateConstructorBean.class); assertEquals("foo", bean.value); } @Test(expected = DatabaseException.class) public void argConstructorCantBeDeserialized() { deserialize("{'value': 'foo'}", ArgConstructorBean.class); } @Test public void packageConstructorCanBeDeserialized() { PackageConstructorBean bean = deserialize("{'value': 'foo'}", PackageConstructorBean.class); assertEquals("foo", bean.value); } @Test public void multipleConstructorsCanBeDeserialized() { MultipleConstructorBean bean = deserialize("{'value': 'foo'}", MultipleConstructorBean.class); assertEquals("foo", bean.value); } @Test public void objectAcceptsAnyObject() { ObjectBean stringValue = deserialize("{'value': 'foo'}", ObjectBean.class); assertEquals("foo", stringValue.value); ObjectBean listValue = deserialize("{'value': ['foo']}", ObjectBean.class); assertEquals(Collections.singletonList("foo"), listValue.value); ObjectBean mapValue = deserialize("{'value': {'foo':'bar'}}", ObjectBean.class); assertEquals(fromSingleQuotedString("{'foo':'bar'}"), mapValue.value); String complex = "{'value': {'foo':['bar', ['baz'], {'bam': 'qux'}]}, " + "'other':{'a': ['b']}}"; ObjectBean complexValue = deserialize(complex, ObjectBean.class); assertEquals(fromSingleQuotedString(complex).get("value"), complexValue.value); } @Test public void objectClassCanBePassedInAtTopLevel() { assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", Object.class)); assertEquals(1, CustomClassMapper.convertToCustomClass(1, Object.class)); assertEquals(1L, CustomClassMapper.convertToCustomClass(1L, Object.class)); assertEquals(true, CustomClassMapper.convertToCustomClass(true, Object.class)); assertEquals(1.1, CustomClassMapper.convertToCustomClass(1.1, Object.class)); List<String> fooList = Collections.singletonList("foo"); assertEquals(fooList, CustomClassMapper.convertToCustomClass(fooList, Object.class)); Map<String, String> fooMap = Collections.singletonMap("foo", "bar"); assertEquals(fooMap, CustomClassMapper.convertToCustomClass(fooMap, Object.class)); } @Test public void primitiveClassesCanBePassedInTopLevel() { assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", String.class)); assertEquals((Integer) 1, CustomClassMapper.convertToCustomClass(1, Integer.class)); assertEquals((Long) 1L, CustomClassMapper.convertToCustomClass(1L, Long.class)); assertEquals(true, CustomClassMapper.convertToCustomClass(true, Boolean.class)); assertEquals((Double) 1.1, CustomClassMapper.convertToCustomClass(1.1, Double.class)); } @Test(expected = DatabaseException.class) public void passingInListTopLevelThrows() { CustomClassMapper.convertToCustomClass(Collections.singletonList("foo"), List.class); } @Test(expected = DatabaseException.class) public void passingInMapTopLevelThrows() { CustomClassMapper.convertToCustomClass(Collections.singletonMap("foo", "bar"), Map.class); } @Test(expected = DatabaseException.class) public void passingInCharacterTopLevelThrows() { CustomClassMapper.convertToCustomClass('1', Character.class); } @Test(expected = DatabaseException.class) public void passingInShortTopLevelThrows() { CustomClassMapper.convertToCustomClass(1, Short.class); } @Test(expected = DatabaseException.class) public void passingInByteTopLevelThrows() { CustomClassMapper.convertToCustomClass(1, Byte.class); } @Test(expected = DatabaseException.class) public void passingInGenericBeanTopLevelThrows() { deserialize("{'value': 'foo'}", GenericBean.class); } @Test public void collectionsCanBeSerializedWhenList() { CollectionBean bean = new CollectionBean(); bean.values = Collections.singletonList("foo"); assertJson("{'values': ['foo']}", serialize(bean)); } @Test(expected = DatabaseException.class) public void collectionsCantBeSerializedWhenSet() { CollectionBean bean = new CollectionBean(); bean.values = Collections.singleton("foo"); serialize(bean); } @Test(expected = DatabaseException.class) public void collectionsCantBeDeserialized() { deserialize("{'values': ['foo']}", CollectionBean.class); } @Test public void allowNullEverywhere() { assertNull(CustomClassMapper.convertToCustomClass(null, Integer.class)); assertNull(CustomClassMapper.convertToCustomClass(null, String.class)); assertNull(CustomClassMapper.convertToCustomClass(null, Double.class)); assertNull(CustomClassMapper.convertToCustomClass(null, Long.class)); assertNull(CustomClassMapper.convertToCustomClass(null, Boolean.class)); assertNull(CustomClassMapper.convertToCustomClass(null, StringBean.class)); assertNull(CustomClassMapper.convertToCustomClass(null, Object.class)); assertNull(CustomClassMapper.convertToCustomClass(null, new GenericTypeIndicator<String>() {})); assertNull( CustomClassMapper.convertToCustomClass( null, new GenericTypeIndicator<Map<String, String>>() {})); } @Test public void parsingGenericBeansSupportedUsingGenericTypeIndicator() { GenericBean<String> stringBean = deserialize("{'value': 'foo'}", new GenericTypeIndicator<GenericBean<String>>() {}); assertEquals("foo", stringBean.value); GenericBean<Map<String, String>> mapBean = deserialize( "{'value': {'foo': 'bar'}}", new GenericTypeIndicator<GenericBean<Map<String, String>>>() {}); assertEquals(Collections.singletonMap("foo", "bar"), mapBean.value); GenericBean<List<String>> listBean = deserialize("{'value': ['foo']}", new GenericTypeIndicator<GenericBean<List<String>>>() {}); assertEquals(Collections.singletonList("foo"), listBean.value); GenericBean<GenericBean<String>> recursiveBean = deserialize( "{'value': {'value': 'foo'}}", new GenericTypeIndicator<GenericBean<GenericBean<String>>>() {}); assertEquals("foo", recursiveBean.value.value); DoubleGenericBean<String, Integer> doubleBean = deserialize( "{'valueA': 'foo', 'valueB': 1}", new GenericTypeIndicator<DoubleGenericBean<String, Integer>>() {}); assertEquals("foo", doubleBean.valueA); assertEquals((Integer) 1, doubleBean.valueB); } @Test public void serializingGenericBeansSupported() { GenericBean<String> stringBean = new GenericBean<>(); stringBean.value = "foo"; assertJson("{'value': 'foo'}", serialize(stringBean)); GenericBean<Map<String, String>> mapBean = new GenericBean<>(); mapBean.value = Collections.singletonMap("foo", "bar"); assertJson("{'value': {'foo': 'bar'}}", serialize(mapBean)); GenericBean<List<String>> listBean = new GenericBean<>(); listBean.value = Collections.singletonList("foo"); assertJson("{'value': ['foo']}", serialize(listBean)); GenericBean<GenericBean<String>> recursiveBean = new GenericBean<>(); recursiveBean.value = new GenericBean<>(); recursiveBean.value.value = "foo"; assertJson("{'value': {'value': 'foo'}}", serialize(recursiveBean)); DoubleGenericBean<String, Integer> doubleBean = new DoubleGenericBean<>(); doubleBean.valueA = "foo"; doubleBean.valueB = 1; assertJson("{'valueA': 'foo', 'valueB': 1}", serialize(doubleBean)); } @Test(expected = DatabaseException.class) public void deserializingWrongTypeThrows() { deserialize("{'value': 'foo'}", WrongTypeBean.class); } @Test public void serializingWrongTypeWorks() { WrongTypeBean bean = new WrongTypeBean(); bean.value = 1; assertJson("{'value': '1'}", serialize(bean)); } @Test(expected = DatabaseException.class) public void extendingGenericTypeIndicatorIsForbidden1() { deserialize("{'value': 'foo'}", new GenericTypeIndicatorSubclass<GenericBean<String>>() {}); } @Test(expected = DatabaseException.class) public void extendingGenericTypeIndicatorIsForbidden2() { deserialize("{'value': 'foo'}", new NonGenericTypeIndicatorSubclass() {}); } @Test(expected = DatabaseException.class) public void extendingGenericTypeIndicatorIsForbidden3() { deserialize("{'value': 'foo'}", new NonGenericTypeIndicatorSubclassConcreteSubclass()); } @Test public void subclassingGenericTypeIndicatorIsAllowed() { GenericBean<String> bean = deserialize("{'value': 'foo'}", new NonGenericTypeIndicatorConcreteSubclass()); assertEquals("foo", bean.value); } @Test(expected = DatabaseException.class) public void unknownTypeParametersNotSupported() { deserialize("{'value': 'foo'}", new GenericTypeIndicatorSubclass<GenericBean<?>>() {}); } @Test(expected = DatabaseException.class) public void unknownTypeParametersSupportedIfBoundedByKnownType() { GenericBean<? extends String> bean = deserialize( "{'value': 'foo'}", new GenericTypeIndicatorSubclass<GenericBean<? extends String>>() {}); assertEquals("foo", bean.value); } @Test public void excludedFieldsAreExcluded() { ExcludedBean bean = new ExcludedBean(); assertJson("{'includedGetter': 'no-value'}", serialize(bean)); } @Test public void excludedFieldsAreNotParsed() { ExcludedBean bean = deserialize( "{'includedGetter': 'foo', 'excludedField': 'bar', " + "'excludedGetter': 'qux'}", ExcludedBean.class); assertEquals("no-value", bean.excludedField); assertEquals("no-value", bean.excludedGetter); assertEquals("foo", bean.includedGetter); } @Test public void excludedSettersAreIgnored() { ExcludedSetterBean bean = deserialize("{'value': 'foo'}", ExcludedSetterBean.class); assertEquals("foo", bean.value); } @Test public void propertyNamesAreSerialized() { PropertyNameBean bean = new PropertyNameBean(); bean.key = "foo"; bean.setValue("bar"); assertJson("{'my_key': 'foo', 'my_value': 'bar'}", serialize(bean)); } @Test public void propertyNamesAreParsed() { PropertyNameBean bean = deserialize("{'my_key': 'foo', 'my_value': 'bar'}", PropertyNameBean.class); assertEquals("foo", bean.key); assertEquals("bar", bean.getValue()); } @Test public void staticFieldsAreNotParsed() { StaticFieldBean bean = deserialize("{'value1': 'foo', 'value2': 'bar'}", StaticFieldBean.class); assertEquals("static-value", StaticFieldBean.value1); assertEquals("bar", bean.value2); } @Test public void staticFieldsAreNotSerialized() { StaticFieldBean bean = new StaticFieldBean(); bean.value2 = "foo"; assertJson("{'value2': 'foo'}", serialize(bean)); } @Test public void staticSettersAreNotUsed() { StaticMethodBean bean = deserialize("{'value1': 'foo', 'value2': 'bar'}", StaticMethodBean.class); assertEquals("static-value", StaticMethodBean.value1); assertEquals("bar", bean.value2); } @Test public void staticMethodsAreNotSerialized() { StaticMethodBean bean = new StaticMethodBean(); bean.value2 = "foo"; assertJson("{'value2': 'foo'}", serialize(bean)); } @Test public void enumsAreSerialized() { EnumBean bean = new EnumBean(); bean.enumField = Enum.Bar; bean.complexEnum = ComplexEnum.One; bean.setEnumValue(Enum.Foo); assertJson("{'enumField': 'Bar', 'enumValue': 'Foo', 'complexEnum': 'One'}", serialize(bean)); } @Test public void enumsAreParsed() { String json = "{'enumField': 'Bar', 'enumValue': 'Foo', 'complexEnum': 'One'}"; EnumBean bean = deserialize(json, EnumBean.class); assertEquals(bean.enumField, Enum.Bar); assertEquals(bean.enumValue, Enum.Foo); assertEquals(bean.complexEnum, ComplexEnum.One); } @Test public void enumsCanBeParsedToNull() { String json = "{'enumField': null}"; EnumBean bean = deserialize(json, EnumBean.class); assertNull(bean.enumField); assertNull(bean.enumValue); assertNull(bean.complexEnum); } @Test(expected = DatabaseException.class) public void throwsOnUnmatchedEnums() { String json = "{'enumField': 'Unavailable', 'enumValue': 'Foo', 'complexEnum': 'One'}"; deserialize(json, EnumBean.class); } @Test public void inheritedFieldsAndGettersAreSerialized() { FinalBean bean = new FinalBean(); bean.finalValue = "final-value"; bean.inheritedValue = "inherited-value"; bean.baseValue = "base-value"; bean.overrideValue = "override-value"; bean.classPrivateValue = "private-value"; bean.packageBaseValue = "package-base-value"; bean.setFinalMethod("final-method"); bean.setInheritedMethod("inherited-method"); bean.setBaseMethod("base-method"); assertJson( "{'baseValue': 'base-value', " + "'baseMethod': 'base-method', " + "'classPrivateValue': 'private-value', " + "'finalMethod': 'final-method', " + "'finalValue': 'final-value', " + "'inheritedMethod': 'inherited-method', " + "'inheritedValue': 'inherited-value', " + "'overrideValue': 'override-value-final', " + "'packageBaseValue': 'package-base-value'}", serialize(bean)); } @Test public void inheritedFieldsAndSettersAreParsed() { String bean = "{'baseValue': 'base-value', " + "'baseMethod': 'base-method', " + "'classPrivateValue': 'private-value', " + "'finalMethod': 'final-method', " + "'finalValue': 'final-value', " + "'inheritedMethod': 'inherited-method', " + "'inheritedValue': 'inherited-value', " + "'overrideValue': 'override-value', " + "'packageBaseValue': 'package-base-value'}"; FinalBean finalBean = deserialize(bean, FinalBean.class); assertEquals("base-value", finalBean.baseValue); assertEquals("inherited-value", finalBean.inheritedValue); assertEquals("final-value", finalBean.finalValue); assertEquals("base-method", finalBean.getBaseMethod()); assertEquals("inherited-method", finalBean.getInheritedMethod()); assertEquals("final-method", finalBean.getFinalMethod()); assertEquals("override-value-final", finalBean.overrideValue); assertEquals("private-value", finalBean.classPrivateValue); assertNull(((InheritedBean) finalBean).classPrivateValue); assertNull(((BaseBean) finalBean).classPrivateValue); InheritedBean inheritedBean = deserialize(bean, InheritedBean.class); assertEquals("base-value", inheritedBean.baseValue); assertEquals("inherited-value", inheritedBean.inheritedValue); assertEquals("base-method", inheritedBean.getBaseMethod()); assertEquals("inherited-method", inheritedBean.getInheritedMethod()); assertEquals("override-value-inherited", inheritedBean.overrideValue); assertEquals("private-value", inheritedBean.classPrivateValue); assertNull(((BaseBean) inheritedBean).classPrivateValue); BaseBean baseBean = deserialize(bean, BaseBean.class); assertEquals("base-value", baseBean.baseValue); assertEquals("base-method", baseBean.getBaseMethod()); assertEquals("override-value", baseBean.overrideValue); assertEquals("private-value", baseBean.classPrivateValue); } @Test(expected = DatabaseException.class) public void settersFromSubclassConflictsWithBaseClass() { ConflictingSetterSubBean bean = new ConflictingSetterSubBean(); bean.value = 1; serialize(bean); } @Test(expected = DatabaseException.class) public void settersFromSubclassConflictsWithBaseClass2() { ConflictingSetterSubBean2 bean = new ConflictingSetterSubBean2(); bean.value = 1; serialize(bean); } @Test public void settersCanOverridePrimitiveSettersSerializing() { NonConflictingSetterSubBean bean = new NonConflictingSetterSubBean(); bean.value = 1; assertJson("{'value': 1}", serialize(bean)); } @Test public void settersCanOverridePrimitiveSettersParsing() { NonConflictingSetterSubBean bean = deserialize("{'value': 2}", NonConflictingSetterSubBean.class); // sub-bean converts to negative value assertEquals(-2, bean.value); } @Test(expected = DatabaseException.class) public void genericSettersFromSubclassConflictsWithBaseClass() { ConflictingGenericSetterSubBean<String> bean = new ConflictingGenericSetterSubBean<>(); bean.value = "hello"; serialize(bean); } // This should work, but generics and subclassing are tricky to get right. For now we will just // throw and we can add support for generics & subclassing if it becomes a high demand feature @Test(expected = DatabaseException.class) public void settersCanOverrideGenericSettersParsingNot() { NonConflictingGenericSetterSubBean bean = deserialize("{'value': 'value'}", NonConflictingGenericSetterSubBean.class); assertEquals("subsetter:value", bean.value); } private enum Enum { Foo, Bar } private enum ComplexEnum { One("one"), Two("two"); private String value; ComplexEnum(String value) { this.value = value; } public String getValue() { return this.value; } public void setValue(String value) { this.value = value; } } private static class StringBean { private String value; public String getValue() { return value; } } private static class DoubleBean { private double value; public double getValue() { return value; } } private static class FloatBean { private float value; public float getValue() { return value; } } private static class LongBean { private long value; public long getValue() { return value; } } private static class IntBean { private int value; public int getValue() { return value; } } private static class BooleanBean { private boolean value; public boolean isValue() { return value; } } private static class ShortBean { private short value; public short getValue() { return value; } } private static class ByteBean { private byte value; public byte getValue() { return value; } } private static class CharBean { private char value; public char getValue() { return value; } } private static class IntArrayBean { private int[] values; public int[] getValues() { return this.values; } } private static class StringArrayBean { private String[] values; public String[] getValues() { return this.values; } } // CSOFF: MemberName private static class XMLAndURLBean { public String XMLAndURL2; private String XMLAndURL1; public String getXMLAndURL1() { return XMLAndURL1; } public void setXMLAndURL1(String value) { this.XMLAndURL1 = value; } } private static class SetterBean { private String value; public String getValue() { return this.value; } public void setValue(String value) { this.value = "setter:" + value; } } private static class PrivateSetterBean { public String value; private void setValue(String value) { this.value = "setter:" + value; } } private static class GetterBean { private String value; public String getValue() { return "getter:" + this.value; } } private static class GetterPublicFieldBean { public String value; public String getValue() { return "getter:" + this.value; } } private static class GetterPublicFieldBeanCaseSensitive { public String valueCase; public String getValueCASE() { return "getter:" + this.valueCase; } } // CSOFF: AbbreviationAsWordInNameCheck private static class CaseSensitiveGetterBean1 { private String value; public String getVALUE() { return this.value; } } private static class CaseSensitiveGetterBean2 { private String value; public String getvalue() { return this.value; } } private static class CaseSensitiveGetterBean3 { private String value; public String getVAlue() { return this.value; } } private static class CaseSensitiveGetterBean4 { private String value; public String getvaLUE() { return this.value; } } private static class CaseSensitiveSetterBean1 { private String value; public String getValue() { return this.value; } public void setValue(String value) { this.value = "setter:" + value; } public void setVAlue(String value) { this.value = "wrong setter!"; } } private static class CaseSensitiveSetterBean2 { private String value; public String getValue() { return this.value; } public void setValue(String value) { this.value = "setter:" + value; } public void setvalue(String value) { this.value = "wrong setter!"; } } private static class CaseSensitiveSetterBean3 { private String value; public String getValue() { return this.value; } public void setvalue(String value) { this.value = "setter:" + value; } } private static class CaseSensitiveSetterBean4 { private String value; public String getValue() { return this.value; } public void setVALUE(String value) { this.value = "setter:" + value; } } //CSOFF: MethodName private static class CaseSensitiveSetterBean5 { private String value; public String getValue() { return this.value; } public void SETVALUE(String value) { this.value = "wrong setter!"; } } private static class CaseSensitiveSetterBean6 { private String value; public String getValue() { return this.value; } public void setVaLUE(String value) { this.value = "setter:" + value; } } @SuppressWarnings("ConstantField") private static class CaseSensitiveFieldBean1 { public String VALUE; } private static class CaseSensitiveFieldBean2 { public String value; } private static class CaseSensitiveFieldBean3 { public String Value; } private static class CaseSensitiveFieldBean4 { public String valUE; } private static class WrongSetterBean { private String value; public String getValue() { return this.value; } public void setValue() { this.value = "wrong setter!"; } public void setValue(String one, String two) { this.value = "wrong setter!"; } } private static class WrongTypeBean { private Integer value; public String getValue() { return "" + this.value; } } private static class RecursiveBean { private StringBean bean; public StringBean getBean() { return this.bean; } } private static class ObjectBean { private Object value; public Object getValue() { return value; } } private static class GenericBean<B> { private B value; public B getValue() { return value; } } private static class DoubleGenericBean<A, B> { private A valueA; private B valueB; public A getValueA() { return valueA; } public B getValueB() { return valueB; } } private static class ListBean { private List<String> values; public List<String> getValues() { return this.values; } } private static class SetBean { private Set<String> values; public Set<String> getValues() { return this.values; } } private static class CollectionBean { private Collection<String> values; public Collection<String> getValues() { return this.values; } } private static class MapBean { private Map<String, String> values; public Map<String, String> getValues() { return this.values; } } private static class RecursiveListBean { private List<StringBean> values; public List<StringBean> getValues() { return this.values; } } private static class RecursiveMapBean { private Map<String, StringBean> values; public Map<String, StringBean> getValues() { return this.values; } } private static class IllegalKeyMapBean { private Map<Integer, StringBean> values; public Map<Integer, StringBean> getValues() { return this.values; } } private static class PublicFieldBean { public String value; } @ThrowOnExtraProperties private static class ThrowOnUnknownPropertiesBean { public String value; } @ThrowOnExtraProperties private static class PackageFieldBean { String value; } @ThrowOnExtraProperties @SuppressWarnings("unused") // Unused, but required for the test private static class PrivateFieldBean { private String value; } private static class PackageGetterBean { private String packageValue; private String publicValue; String getPackageValue() { return this.packageValue; } public String getPublicValue() { return this.publicValue; } } private static class ExcludedBean { @Exclude public String excludedField = "no-value"; private String excludedGetter = "no-value"; private String includedGetter = "no-value"; @Exclude public String getExcludedGetter() { return this.excludedGetter; } public String getIncludedGetter() { return this.includedGetter; } } private static class ExcludedSetterBean { private String value; public String getValue() { return this.value; } @Exclude public void setValue(String value) { this.value = "wrong setter"; } } private static class PropertyNameBean { @PropertyName("my_key") public String key; private String value; @PropertyName("my_value") public String getValue() { return this.value; } @PropertyName("my_value") public void setValue(String value) { this.value = value; } } private static class PublicPrivateFieldBean { public String value1; String value2; private String value3; } private static class TwoSetterBean { private String value; public String getValue() { return this.value; } public void setValue(Integer value) { this.value = "int:" + value; } public void setValue(String value) { this.value = "string:" + value; } } private static class TwoGetterBean { private String value; public String getValue() { return this.value; } public String getVALUE() { return this.value; } } //CSON: AbbreviationAsWordInNameCheck private static class GetterArgumentsBean { private String value; public String getValue1() { return this.value + "1"; } public void getValue2() {} public String getValue3(boolean flag) { return this.value + "3"; } public String getValue4() { return this.value + "4"; } } @SuppressWarnings("ConstantField") private static class UnicodeBean { private String 漢字; public String get漢字() { return this.漢字; } } //CSON: MethodName //CSON: MemberName private static class PublicConstructorBean { private String value; public PublicConstructorBean() {} public String getValue() { return this.value; } } private static class PrivateConstructorBean { private String value; private PrivateConstructorBean() {} public String getValue() { return this.value; } } private static class PackageConstructorBean { private String value; PackageConstructorBean() {} public String getValue() { return this.value; } } private static class ArgConstructorBean { private String value; public ArgConstructorBean(String value) { this.value = value; } public String getValue() { return this.value; } } private static class MultipleConstructorBean { private String value; public MultipleConstructorBean(String value) { this.value = "wrong-value"; } public MultipleConstructorBean() {} public String getValue() { return this.value; } } private static class StaticFieldBean { public static String value1 = "static-value"; public String value2; } private static class StaticMethodBean { private static String value1 = "static-value"; public String value2; public static String getValue1() { return StaticMethodBean.value1; } public static void setValue1(String value1) { StaticMethodBean.value1 = value1; } } private static class EnumBean { public Enum enumField; public ComplexEnum complexEnum; private Enum enumValue; public Enum getEnumValue() { return this.enumValue; } public void setEnumValue(Enum enumValue) { this.enumValue = enumValue; } } private static class BaseBean { // Public field on base class public String baseValue; // Value that is accessed through overridden methods in subclasses public String overrideValue; // Field that is package private in base class String packageBaseValue; // Private field that is used in getter/setter in base class private String baseMethodValue; // Private field that has field with same name in subclasses private String classPrivateValue; public String getClassPrivateValue() { return this.classPrivateValue; } public String getBaseMethod() { return this.baseMethodValue; } public void setBaseMethod(String value) { this.baseMethodValue = value; } public String getPackageBaseValue() { return this.packageBaseValue; } } private static class InheritedBean extends BaseBean { public String inheritedValue; private String inheritedMethodValue; private String classPrivateValue; @Override public String getClassPrivateValue() { return this.classPrivateValue; } public String getInheritedMethod() { return this.inheritedMethodValue; } public void setInheritedMethod(String value) { this.inheritedMethodValue = value; } public String getOverrideValue() { return this.overrideValue + "-inherited"; } public void setOverrideValue(String value) { this.overrideValue = value + "-inherited"; } } private static final class FinalBean extends InheritedBean { public String finalValue; private String finalMethodValue; private String classPrivateValue; @Override public String getClassPrivateValue() { return this.classPrivateValue; } public String getFinalMethod() { return this.finalMethodValue; } public void setFinalMethod(String value) { this.finalMethodValue = value; } @Override public String getOverrideValue() { return this.overrideValue + "-final"; } @Override public void setOverrideValue(String value) { this.overrideValue = value + "-final"; } } // Conflicting setters are not supported. When inheriting from a base class we require all // setters be an override of a base class private static class ConflictingSetterBean { public int value; // package private so override can be public void setValue(int value) { this.value = value; } } private static class ConflictingSetterSubBean extends ConflictingSetterBean { public void setValue(String value) { this.value = -1; } } private static class ConflictingSetterSubBean2 extends ConflictingSetterBean { public void setValue(Integer value) { this.value = -1; } } private static class NonConflictingSetterSubBean extends ConflictingSetterBean { @Override public void setValue(int value) { this.value = value * -1; } } private static class GenericSetterBaseBean<T> { public T value; void setValue(T value) { this.value = value; } } private static class ConflictingGenericSetterSubBean<T> extends GenericSetterBaseBean<T> { public void setValue(String value) { // wrong setter } } private static class NonConflictingGenericSetterSubBean extends GenericSetterBaseBean<String> { @Override public void setValue(String value) { this.value = "subsetter:" + value; } } private abstract static class GenericTypeIndicatorSubclass<T> extends GenericTypeIndicator<T> {} private abstract static class NonGenericTypeIndicatorSubclass extends GenericTypeIndicator<GenericBean<String>> {} private static class NonGenericTypeIndicatorConcreteSubclass extends GenericTypeIndicator<GenericBean<String>> {} private static class NonGenericTypeIndicatorSubclassConcreteSubclass extends GenericTypeIndicatorSubclass<GenericBean<String>> {} }