From 4ec725ef0905cd5490ed71b6576fdc1ef8fef17e Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Wed, 6 Feb 2019 13:14:57 -0500 Subject: Add superclasses for gson-jackson migration Added common classes needed by other gson-jackson code. Modified some logic to make it more maintainable or perform better. Updated comments and spacing. Fix another comment. Moved gson classes from utils to a separate gson project. Added GsonXxx annotations to mirror jackson annotations. Removed unneeded dependencies from gson pom. Removed old GsonMessage class from policy-endpoints. Removed trailing spaces. Updated licenses. Removed more trailing spaces. Removed unneeded checkstyle suppression file from utils. Change-Id: I1a285500faeb0a0b6a1467d09b92ecd3cded713e Issue-ID: POLICY-1428 Signed-off-by: Jim Hahn --- .../common/gson/GsonMessageBodyHandlerTest.java | 156 +++++++ .../common/gson/JacksonExclusionStrategyTest.java | 203 +++++++++ .../policy/common/gson/internal/AdapterTest.java | 387 ++++++++++++++++ .../common/gson/internal/ClassWalkerTest.java | 507 +++++++++++++++++++++ .../common/gson/internal/DataAdapterFactory.java | 310 +++++++++++++ 5 files changed, 1563 insertions(+) create mode 100644 gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java create mode 100644 gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java create mode 100644 gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java create mode 100644 gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java create mode 100644 gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java (limited to 'gson/src/test/java') diff --git a/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java new file mode 100644 index 00000000..85ecfea4 --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java @@ -0,0 +1,156 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import javax.ws.rs.core.MediaType; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.gson.GsonMessageBodyHandler; + +public class GsonMessageBodyHandlerTest { + private static final String GEN_TYPE = "some-type"; + private static final String[] subtypes = {"json", "jSoN", "hello+json", "javascript", "x-javascript", "x-json"}; + + @SuppressWarnings("rawtypes") + private static final Class GEN_CLASS = MyObject.class; + + @SuppressWarnings("unchecked") + private static final Class CLASS_OBJ = GEN_CLASS; + + private GsonMessageBodyHandler hdlr; + + @Before + public void setUp() { + hdlr = new GsonMessageBodyHandler(); + } + + @Test + public void testIsWriteable() { + // null media type + assertTrue(hdlr.isWriteable(null, null, null, null)); + + for (String subtype : subtypes) { + assertTrue("writeable " + subtype, hdlr.isWriteable(null, null, null, new MediaType(GEN_TYPE, subtype))); + + } + + // the remaining should be FALSE + + // null subtype + assertFalse(hdlr.isWriteable(null, null, null, new MediaType(GEN_TYPE, null))); + + // text subtype + assertFalse(hdlr.isWriteable(null, null, null, MediaType.TEXT_HTML_TYPE)); + } + + @Test + public void testGetSize() { + assertEquals(-1, hdlr.getSize(null, null, null, null, null)); + } + + @Test + public void testWriteTo_testReadFrom() throws Exception { + ByteArrayOutputStream outstr = new ByteArrayOutputStream(); + MyObject obj1 = new MyObject(10); + hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr); + + Object obj2 = hdlr.readFrom(CLASS_OBJ, CLASS_OBJ, null, null, null, + new ByteArrayInputStream(outstr.toByteArray())); + assertEquals(obj1.toString(), obj2.toString()); + } + + @Test + public void testWriteTo_DifferentTypes() throws Exception { + ByteArrayOutputStream outstr = new ByteArrayOutputStream(); + + // use a derived type, but specify the base type when writing + MyObject obj1 = new MyObject(10) {}; + hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr); + + Object obj2 = hdlr.readFrom(CLASS_OBJ, CLASS_OBJ, null, null, null, + new ByteArrayInputStream(outstr.toByteArray())); + assertEquals(obj1.toString(), obj2.toString()); + } + + @Test + public void testIsReadable() { + // null media type + assertTrue(hdlr.isReadable(null, null, null, null)); + + // null subtype + assertFalse(hdlr.isReadable(null, null, null, new MediaType(GEN_TYPE, null))); + + for (String subtype : subtypes) { + assertTrue("readable " + subtype, hdlr.isReadable(null, null, null, new MediaType(GEN_TYPE, subtype))); + + } + + // the remaining should be FALSE + + // null subtype + assertFalse(hdlr.isReadable(null, null, null, new MediaType(GEN_TYPE, null))); + + // text subtype + assertFalse(hdlr.isReadable(null, null, null, MediaType.TEXT_HTML_TYPE)); + } + + @Test + public void testReadFrom_DifferentTypes() throws Exception { + ByteArrayOutputStream outstr = new ByteArrayOutputStream(); + MyObject obj1 = new MyObject(10); + hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr); + + // use a derived type, but specify the base type when reading + @SuppressWarnings("rawtypes") + Class clazz = new MyObject() {}.getClass(); + + @SuppressWarnings("unchecked") + Class objclazz = clazz; + + Object obj2 = hdlr.readFrom(objclazz, CLASS_OBJ, null, null, null, + new ByteArrayInputStream(outstr.toByteArray())); + assertEquals(obj1.toString(), obj2.toString()); + } + + public static class MyObject { + private int id; + + public MyObject() { + super(); + } + + public MyObject(int id) { + this.id = id; + } + + @Override + public String toString() { + return "MyObject [id=" + id + "]"; + } + } + +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java new file mode 100644 index 00000000..4b5473c5 --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java @@ -0,0 +1,203 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import java.lang.reflect.GenericArrayType; +import java.util.LinkedList; +import java.util.TreeMap; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.gson.JacksonExclusionStrategy; + +public class JacksonExclusionStrategyTest { + + private static JacksonExclusionStrategy strategy; + private static Gson gson; + + @BeforeClass + public static void setUpBeforeClass() { + strategy = new JacksonExclusionStrategy(); + gson = new GsonBuilder().setExclusionStrategies(strategy).create(); + } + + @Test + public void testWithGson() { + Derived data = new Derived(); + data.setId(10); + data.setText("some text"); + data.setValue("some value"); + + // no fields should be serialized + String result = gson.toJson(data); + assertEquals("{}", result); + + // no fields should be deserialized + result = "{'id':20, 'text':'my text', 'value':'my value'}".replace('\'', '"'); + Derived data2 = gson.fromJson(result, Derived.class); + assertEquals(new Derived().toString(), data2.toString()); + } + + @Test + public void testShouldSkipField() throws Exception { + // should skip every field of Data + assertTrue(strategy.shouldSkipField(new FieldAttributes(Data.class.getDeclaredField("id")))); + assertTrue(strategy.shouldSkipField(new FieldAttributes(Data.class.getDeclaredField("text")))); + + // should not skip fields in Map + assertFalse(strategy.shouldSkipField(new FieldAttributes(MyMap.class.getDeclaredField("mapId")))); + } + + @Test + public void testShouldSkipClass() { + assertFalse(strategy.shouldSkipClass(null)); + assertFalse(strategy.shouldSkipClass(Object.class)); + } + + @Test + public void testIsManaged() { + assertTrue(JacksonExclusionStrategy.isManaged(Data.class)); + assertTrue(JacksonExclusionStrategy.isManaged(Intfc.class)); + assertTrue(JacksonExclusionStrategy.isManaged(com.google.gson.TypeAdapter.class)); + + // generic classes + assertFalse(JacksonExclusionStrategy.isManaged(new Data[0].getClass())); + assertFalse(JacksonExclusionStrategy.isManaged(Enum.class)); + assertFalse(JacksonExclusionStrategy.isManaged(boolean.class)); + assertFalse(JacksonExclusionStrategy.isManaged(byte.class)); + assertFalse(JacksonExclusionStrategy.isManaged(short.class)); + assertFalse(JacksonExclusionStrategy.isManaged(int.class)); + assertFalse(JacksonExclusionStrategy.isManaged(long.class)); + assertFalse(JacksonExclusionStrategy.isManaged(float.class)); + assertFalse(JacksonExclusionStrategy.isManaged(double.class)); + assertFalse(JacksonExclusionStrategy.isManaged(char.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Boolean.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Byte.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Short.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Integer.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Long.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Float.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Double.class)); + assertFalse(JacksonExclusionStrategy.isManaged(Character.class)); + assertFalse(JacksonExclusionStrategy.isManaged(String.class)); + assertFalse(JacksonExclusionStrategy.isManaged(MyMap.class)); + assertFalse(JacksonExclusionStrategy.isManaged(MyList.class)); + assertFalse(JacksonExclusionStrategy.isManaged(MyJson.class)); + assertFalse(JacksonExclusionStrategy.isManaged(GenericArrayType.class)); + } + + /** + * Used to verify that no fields are exposed. + */ + public static class Data { + private int id; + public String text; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + return "Data [id=" + id + ", text=" + text + "]"; + } + } + + public static class Derived extends Data { + protected String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "Derived [value=" + value + ", " + super.toString() + "]"; + } + } + + /** + * Used to verify that enums are not managed. + */ + public static enum Enum { + UP, DOWN, + } + + /** + * Used to verify that interfaces are managed. + */ + public static interface Intfc { + int getId(); + } + + /** + * Used to verify that Maps are not managed. + */ + public static class MyMap extends TreeMap { + private static final long serialVersionUID = 1L; + + private int mapId; + + public int getMapId() { + return mapId; + } + } + + /** + * Used to verify that Collections are not managed. + */ + public static class MyList extends LinkedList { + private static final long serialVersionUID = 1L; + } + + /** + * Used to verify that JsonElements are not managed. + */ + public static class MyJson extends JsonElement { + @Override + public JsonElement deepCopy() { + return null; + } + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java new file mode 100644 index 00000000..fcb0d9ad --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java @@ -0,0 +1,387 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import org.junit.Test; +import org.onap.policy.common.gson.JacksonExclusionStrategy; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; +import org.onap.policy.common.gson.internal.Adapter; +import org.onap.policy.common.gson.internal.DataAdapterFactory.Data; +import org.onap.policy.common.gson.internal.DataAdapterFactory.DerivedData; + +public class AdapterTest { + private static final String GET_VALUE_NAME = "getValue"; + private static final String VALUE_NAME = "value"; + private static final String MY_NAME = AdapterTest.class.getName(); + + private static DataAdapterFactory dataAdapter = new DataAdapterFactory(); + + private static Gson gson = new GsonBuilder().registerTypeAdapterFactory(dataAdapter) + .setExclusionStrategies(new JacksonExclusionStrategy()).create(); + + /* + * The remaining fields are just used within the tests. + */ + + private String value; + + // empty alias - should use field name + @GsonJsonProperty("") + protected String emptyAlias; + + @GsonJsonProperty("name-with-alias") + protected String nameWithAlias; + + protected String unaliased; + + protected String $invalidFieldName; + + private List listField; + + private Data dataField; + + + @Test + public void testIsManagedField() { + assertTrue(Adapter.isManaged(field(VALUE_NAME))); + + assertFalse(Adapter.isManaged(field("$invalidFieldName"))); + } + + @Test + public void testIsManagedMethod() { + assertTrue(Adapter.isManaged(mget(GET_VALUE_NAME))); + + assertFalse(Adapter.isManaged(mget("get$InvalidName"))); + assertFalse(Adapter.isManaged(mset("set$InvalidName"))); + } + + @Test + public void testAdapterField_Converter() { + Adapter adapter = new Adapter(gson, field("dataField")); + + // first, write something of type Data + dataAdapter.reset(); + dataField = new Data(300); + JsonElement tree = adapter.toJsonTree(dataField); + assertEquals("{'id':300}".replace('\'', '"'), tree.toString()); + + // now try a subclass + dataAdapter.reset(); + dataField = new DerivedData(300, "three"); + tree = adapter.toJsonTree(dataField); + assertEquals("{'id':300,'text':'three'}".replace('\'', '"'), tree.toString()); + } + + @Test + @SuppressWarnings("unchecked") + public void testAdapterField_Converter_List() { + listField = DataAdapterFactory.makeList(); + + Adapter adapter = new Adapter(gson, field("listField")); + + dataAdapter.reset(); + JsonElement tree = adapter.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + // encode it twice so it uses the cached converter + dataAdapter.reset(); + tree = adapter.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + dataAdapter.reset(); + List lst2 = (List) adapter.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + + // decode it twice so it uses the cached converter + dataAdapter.reset(); + lst2 = (List) adapter.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + } + + @Test + public void testAdapterMethod_Converter() throws Exception { + listField = DataAdapterFactory.makeList(); + + Method getter = mget("getMyList"); + + Adapter aget = new Adapter(gson, getter, true, getter.getReturnType()); + + dataAdapter.reset(); + JsonElement tree = aget.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + Method setter = AdapterTest.class.getDeclaredMethod("setMyList", List.class); + Adapter aset = new Adapter(gson, setter, true, setter.getGenericParameterTypes()[0]); + + dataAdapter.reset(); + @SuppressWarnings("unchecked") + List lst2 = (List) aset.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + } + + @Test + public void testGetPropName_testGetFullName_testMakeError() { + // test field + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".value", adapter.getFullName()); + + + // test getter + adapter = new Adapter(gson, mget(GET_VALUE_NAME), true, String.class); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".getValue", adapter.getFullName()); + + assertEquals("hello: " + MY_NAME + ".getValue", adapter.makeError("hello: ")); + + + // test setter + adapter = new Adapter(gson, mset("setValue"), false, String.class); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".setValue", adapter.getFullName()); + } + + @Test + public void testToJsonTree() { + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + JsonElement tree = adapter.toJsonTree("hello"); + assertTrue(tree.isJsonPrimitive()); + assertEquals("hello", tree.getAsString()); + } + + @Test + public void testFromJsonTree() { + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + assertEquals("world", adapter.fromJsonTree(new JsonPrimitive("world"))); + } + + @Test + public void testDetmPropName() { + assertEquals("emptyAlias", Adapter.detmPropName(field("emptyAlias"))); + assertEquals("name-with-alias", Adapter.detmPropName(field("nameWithAlias"))); + assertEquals("unaliased", Adapter.detmPropName(field("unaliased"))); + assertEquals(null, Adapter.detmPropName(field("$invalidFieldName"))); + } + + @Test + public void testDetmGetterPropName() { + assertEquals("emptyAlias", Adapter.detmGetterPropName(mget("getEmptyAlias"))); + assertEquals("get-with-alias", Adapter.detmGetterPropName(mget("getWithAlias"))); + assertEquals("plain", Adapter.detmGetterPropName(mget("getPlain"))); + assertEquals("primBool", Adapter.detmGetterPropName(mget("isPrimBool"))); + assertEquals("boxedBool", Adapter.detmGetterPropName(mget("isBoxedBool"))); + assertEquals(null, Adapter.detmGetterPropName(mget("isString"))); + assertEquals(null, Adapter.detmGetterPropName(mget("noGet"))); + assertEquals(null, Adapter.detmGetterPropName(mget("get"))); + assertEquals(null, Adapter.detmGetterPropName(mget("get$InvalidName"))); + } + + @Test + public void testDetmSetterPropName() { + assertEquals("emptyAlias", Adapter.detmSetterPropName(mset("setEmptyAlias"))); + assertEquals("set-with-alias", Adapter.detmSetterPropName(mset("setWithAlias"))); + assertEquals("plain", Adapter.detmSetterPropName(mset("setPlain"))); + assertEquals(null, Adapter.detmSetterPropName(mset("noSet"))); + assertEquals(null, Adapter.detmSetterPropName(mset("set"))); + assertEquals(null, Adapter.detmSetterPropName(mset("set$InvalidName"))); + } + + @Test + public void testGetQualifiedNameField() throws Exception { + assertEquals(MY_NAME + ".value", Adapter.getQualifiedName(AdapterTest.class.getDeclaredField(VALUE_NAME))); + } + + @Test + public void testGetQualifiedNameMethod() throws Exception { + assertEquals(MY_NAME + ".getValue", Adapter.getQualifiedName(mget(GET_VALUE_NAME))); + } + + /** + * Gets a field from this class, by name. + * + * @param name name of the field to get + * @return the field + */ + private Field field(String name) { + try { + return AdapterTest.class.getDeclaredField(name); + + } catch (SecurityException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets a "getter" method from this class, by name. + * + * @param name name of the method to get + * @return the method + */ + private Method mget(String name) { + try { + return AdapterTest.class.getDeclaredMethod(name); + + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets a "setter" method from this class, by name. + * + * @param name name of the method to get + * @return the method + */ + private Method mset(String name) { + try { + return AdapterTest.class.getDeclaredMethod(name, String.class); + + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + } + + /* + * The remaining methods are just used within the tests. + */ + + protected String getValue() { + return value; + } + + // empty alias - should use method name + @GsonJsonProperty("") + protected String getEmptyAlias() { + return ""; + } + + @GsonJsonProperty("get-with-alias") + protected String getWithAlias() { + return ""; + } + + // no alias, begins with "get" + protected String getPlain() { + return ""; + } + + // begins with "is", returns primitive boolean + protected boolean isPrimBool() { + return true; + } + + // begins with "is", returns boxed Boolean + protected Boolean isBoxedBool() { + return true; + } + + // begins with "is", but doesn't return a boolean + protected String isString() { + return ""; + } + + // doesn't begin with "get" + protected String noGet() { + return ""; + } + + // nothing after "get" + protected String get() { + return ""; + } + + // name has a bogus character + protected String get$InvalidName() { + return ""; + } + + + protected void setValue(String text) { + // do nothing + } + + // empty alias - should use method name + @GsonJsonProperty("") + protected void setEmptyAlias(String text) { + // do nothing + } + + @GsonJsonProperty("set-with-alias") + protected void setWithAlias(String text) { + // do nothing + } + + // no alias, begins with "set" + protected void setPlain(String text) { + // do nothing + } + + // doesn't begin with "set" + protected void noSet(String text) { + // do nothing + } + + // nothing after "get" + protected void set(String text) { + // do nothing + } + + // name has a bogus character + protected void set$InvalidName(String text) { + // do nothing + } + + // returns a list + protected List getMyList() { + return listField; + } + + // accepts a list + protected void setMyList(List newList) { + listField = newList; + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java new file mode 100644 index 00000000..1a15be09 --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java @@ -0,0 +1,507 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.JsonParseException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.gson.annotation.GsonJsonAnyGetter; +import org.onap.policy.common.gson.annotation.GsonJsonAnySetter; +import org.onap.policy.common.gson.annotation.GsonJsonIgnore; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; +import org.onap.policy.common.gson.internal.Adapter; +import org.onap.policy.common.gson.internal.ClassWalker; + +public class ClassWalkerTest { + + private MyWalker walker; + + /** + * Set up. + */ + @Before + public void setUp() { + walker = new MyWalker(); + } + + @Test + public void testExamineClassOfQ_testExamineField_testExamineInField_testExamineOutField() { + walker.walkClassHierarchy(DerivedFromBottom.class); + + assertEquals("[Intfc1, Intfc2, Intfc1, Intfc3, Bottom, DerivedFromBottom]", walker.classes.toString()); + + List inFields = walker.getInProps(Field.class).stream().map(field -> field.getName()) + .collect(Collectors.toList()); + Collections.sort(inFields); + assertEquals("[exposedField, overriddenValue, transField]", inFields.toString()); + + List outFields = walker.getInProps(Field.class).stream().map(field -> field.getName()) + .collect(Collectors.toList()); + Collections.sort(outFields); + assertEquals("[exposedField, overriddenValue, transField]", outFields.toString()); + + // should work with interfaces without throwing an NPE + walker.walkClassHierarchy(Intfc1.class); + } + + @Test + public void testHasAnyGetter() { + walker.walkClassHierarchy(Object.class); + assertNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + + walker.walkClassHierarchy(AnyGetterIgnored.class); + assertNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + + walker.walkClassHierarchy(AnyGetterOnly.class); + assertNotNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + } + + @Test + public void testHasAnySetter() { + walker.walkClassHierarchy(Object.class); + assertNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + + walker.walkClassHierarchy(AnySetterIgnored.class); + assertNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + + walker.walkClassHierarchy(AnySetterOnly.class); + assertNotNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + } + + @Test + public void testExamineMethod() { + walker.walkClassHierarchy(DerivedFromData.class); + + assertEquals("[Data, DerivedFromData]", walker.classes.toString()); + + // ensure all methods were examined + Collections.sort(walker.methods); + List lst = Arrays.asList("getId", "getValue", "getOnlyOut", "getStatic", "getText", "getTheMap", + "getUnserialized", "getValue", "getWithParams", "setExtraParams", "setId", "setMap", + "setMapValue", "setMissingParams", "setNonPublic", "setOnlyIn", "setText", "setUnserialized", + "setValue", "setValue", "wrongGetPrefix", "wrongSetPrefix"); + Collections.sort(lst); + assertEquals(lst.toString(), walker.methods.toString()); + + assertNotNull(walker.getAnyGetter()); + assertEquals("getTheMap", walker.getAnyGetter().getName()); + + List getters = walker.getOutProps(Method.class).stream().map(method -> method.getName()) + .collect(Collectors.toList()); + Collections.sort(getters); + assertEquals("[getId, getOnlyOut, getValue]", getters.toString()); + + assertNotNull(walker.getAnySetter()); + assertEquals("setMapValue", walker.getAnySetter().getName()); + + List setters = walker.getInProps(Method.class).stream().map(method -> method.getName()) + .collect(Collectors.toList()); + Collections.sort(setters); + assertEquals("[setId, setOnlyIn, setValue]", setters.toString()); + + // getter with invalid parameter count + assertThatThrownBy(() -> walker.walkClassHierarchy(AnyGetterMismatchParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_GETTER_MISMATCH_ERR + + AnyGetterMismatchParams.class.getName() + ".getTheMap"); + + // setter with too few parameters + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooFewParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR + + AnySetterTooFewParams.class.getName() + ".setOverride"); + + // setter with too many parameters + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooManyParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR + + AnySetterTooManyParams.class.getName() + ".setOverride"); + + // setter with invalid parameter type + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterInvalidParam.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_TYPE_ERR + + AnySetterInvalidParam.class.getName() + ".setOverride"); + } + + @Test + public void testExamineMethod_AnyGetter() { + walker.walkClassHierarchy(AnyGetterOverride.class); + + assertNotNull(walker.getAnyGetter()); + assertEquals("getOverride", walker.getAnyGetter().getName()); + } + + @Test + public void testExamineMethod_AnySetter() { + walker.walkClassHierarchy(AnySetterOverride.class); + + assertNotNull(walker.getAnySetter()); + assertEquals("setOverride", walker.getAnySetter().getName()); + } + + @Test + public void testGetInNotIgnored_testGetOutNotIgnored() { + walker.walkClassHierarchy(DerivedFromData.class); + + assertEquals("[id, onlyIn, text, value]", new TreeSet<>(walker.getInNotIgnored()).toString()); + assertEquals("[id, onlyOut, text, value]", new TreeSet<>(walker.getOutNotIgnored()).toString()); + } + + /** + * Walker subclass that records items that are examined. + */ + private static class MyWalker extends ClassWalker { + private List classes = new ArrayList<>(); + private List methods = new ArrayList<>(); + + @Override + protected void examine(Class clazz) { + classes.add(clazz.getSimpleName()); + + super.examine(clazz); + } + + @Override + protected void examine(Method method) { + if (Adapter.isManaged(method)) { + methods.add(method.getName()); + } + + super.examine(method); + } + } + + protected static interface Intfc1 { + int id = 1000; + } + + protected static interface Intfc2 { + String text = "intfc2-text"; + } + + private static interface Intfc3 { + + } + + protected static class Bottom implements Intfc1, Intfc3 { + private int id; + public String value; + + public String invalid$fieldName; + + @GsonJsonProperty("exposed") + private String exposedField; + + @GsonJsonIgnore + public int ignored; + + public transient int ignoredTransField; + + @GsonJsonProperty("trans") + public transient int transField; + + @GsonJsonIgnore + public int getId() { + return id; + } + + @GsonJsonIgnore + public void setId(int id) { + this.id = id; + } + } + + protected static class DerivedFromBottom extends Bottom implements Intfc1, Intfc2 { + private String text; + protected String anotherValue; + + @GsonJsonProperty("value") + public String overriddenValue; + + @GsonJsonIgnore + public String getText() { + return text; + } + + @GsonJsonIgnore + public void setText(String text) { + this.text = text; + } + } + + protected static class Data { + private int id; + private String text; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + // not public, but property provided + @GsonJsonProperty("text") + protected String getText() { + return text; + } + + // this will be ignored, because there's already a field by this name + public void setText(String text) { + this.text = text; + } + + // should only show up in the output list + public int getOnlyOut() { + return 1100; + } + + // will be overridden by subclass + @GsonJsonProperty("super-value-getter") + public String getValue() { + return null; + } + + // will be overridden by subclass + @GsonJsonProperty("super-value-setter") + public void setValue(String value) { + // do nothing + } + } + + protected static class DerivedFromData extends Data { + // not serialized + private String unserialized; + + // overrides private field and public method from Data + public String text; + + private Map map; + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @GsonJsonAnyGetter + public Map getTheMap() { + return map; + } + + @GsonJsonIgnore + public void setMap(Map map) { + this.map = map; + } + + @GsonJsonAnySetter + public void setMapValue(String key, String value) { + if (map == null) { + map = new TreeMap<>(); + } + + map.put(key, value); + } + + @GsonJsonIgnore + public String getUnserialized() { + return unserialized; + } + + @GsonJsonIgnore + public void setUnserialized(String unserialized) { + this.unserialized = unserialized; + } + + // should only show up in the input list + public void setOnlyIn(int value) { + // do nothing + } + + // has a param - shouldn't be serialized + public int getWithParams(String text) { + return 1000; + } + + // too few params - shouldn't be serialized + public void setMissingParams() { + // do nothing + } + + // too many params - shouldn't be serialized + public void setExtraParams(String text, String moreText) { + // do nothing + } + + // not public - shouldn't be serialized + protected void setNonPublic(String text) { + // do nothing + } + + // doesn't start with "get" + public String wrongGetPrefix() { + return null; + } + + // doesn't start with "set" + public void wrongSetPrefix(String text) { + // do nothing + } + + // static + public static String getStatic() { + return null; + } + } + + /** + * The "get" method has an incorrect argument count. + */ + private static class AnyGetterMismatchParams { + @GsonJsonAnyGetter + public Map getTheMap(String arg) { + return new TreeMap<>(); + } + } + + /** + * Has {@link GsonJsonAnyGetter} method. + */ + private static class AnyGetterOnly { + @GsonJsonAnyGetter + private Map getOverride() { + return null; + } + } + + /** + * Has {@link GsonJsonAnyGetter} method, but it's ignored. + */ + private static class AnyGetterIgnored { + @GsonJsonAnyGetter + @GsonJsonIgnore + private Map getOverride() { + return null; + } + } + + /** + * Has {@link GsonJsonAnySetter} method. + */ + private static class AnySetterOnly { + @GsonJsonAnySetter + private void setOverride(String key, int value) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method, but it's ignored. + */ + private static class AnySetterIgnored { + @GsonJsonAnySetter + @GsonJsonIgnore + private void setOverride(String key, int value) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnyGetter} method that overrides the super class' method. + */ + private static class AnyGetterOverride extends DerivedFromData { + private Map overMap; + + @GsonJsonAnyGetter + private Map getOverride() { + return overMap; + } + } + + /** + * Has {@link GsonJsonAnySetter} method that overrides the super class' method. + */ + private static class AnySetterOverride extends DerivedFromData { + private Map overMap; + + @GsonJsonAnySetter + private void setOverride(String key, int value) { + if (overMap == null) { + overMap = new TreeMap<>(); + } + + overMap.put(key, value); + } + } + + /** + * Has {@link GsonJsonAnySetter} method with too few parameters. + */ + private static class AnySetterTooFewParams extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(String key) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method with too few parameters. + */ + private static class AnySetterTooManyParams extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(String key, int value, String anotherValue) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method whose first argument type is incorrect. + */ + private static class AnySetterInvalidParam extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(Integer key, String value) { + // do nothing + } + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java new file mode 100644 index 00000000..d0f0b1ec --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java @@ -0,0 +1,310 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Factory used with test Data. + */ +public class DataAdapterFactory implements TypeAdapterFactory { + + /** + * Output of {@link #makeList()}, encoded as json. + */ + public static final String ENCODED_LIST = "[{'id':100},{'id':101}]".replace('\'', '"'); + + /** + * Output of {@link #makeMap()}, encoded as json. + */ + public static final String ENCODED_MAP = "'data-100':{'id':100},'data-101':{'id':101}".replace('\'', '"'); + + /** + * Object handled by this factory. + */ + public static class Data { + private int id; + + public Data() { + super(); + } + + public Data(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public String toString() { + return "Data [id=" + id + "]"; + } + } + + /** + * Object derived from Data. + */ + public static class DerivedData extends Data { + private String text; + + public DerivedData() { + super(); + } + + public DerivedData(int id, String text) { + super(id); + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + return "DerivedData [text=" + text + ", toString()=" + super.toString() + "]"; + } + } + + /** + * Set to {@code true} when {@link #write(JsonWriter, Data)} has been invoked. + */ + private boolean dataWritten = false; + + /** + * Set to {@code true} when {@link #read(JsonReader)} has been invoked. + */ + private boolean dataRead = false; + + /** + * Clears the flags that indicate that "read" or "write" has been invoked. + */ + public void reset() { + dataWritten = true; + dataRead = true; + } + + public boolean isDataWritten() { + return dataWritten; + } + + public boolean isDataRead() { + return dataRead; + } + + /** + * Makes a list of Data. + * + * @return a new list of Data + */ + public static List makeList() { + List listField = new ArrayList<>(); + + listField.add(new Data(100)); + listField.add(new Data(101)); + + return listField; + } + + /** + * Makes an array of Data. + * + * @return a new array of Data + */ + public static JsonArray makeArray() { + JsonArray arr = new JsonArray(); + + for (Data data : makeList()) { + JsonObject json = new JsonObject(); + json.addProperty("id", data.getId()); + arr.add(json); + } + + return arr; + } + + /** + * Makes a map of Data. + * + * @return a new map of Data + */ + public static Map> makeMap() { + Map> map = new TreeMap<>(); + + for (Data data : makeList()) { + map.put("data-" + data.getId(), Arrays.asList(data)); + } + + return map; + } + + /** + * Adds Data objects to a tree, mirroring {@link #makeMap()}. + * + * @param tree tree into which objects are to be added + */ + public static void addToObject(JsonObject tree) { + for (JsonElement ent : makeArray()) { + JsonObject obj = ent.getAsJsonObject(); + JsonArray arr = new JsonArray(); + arr.add(obj); + tree.add("data-" + obj.get("id").getAsString(), arr); + } + } + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() == Data.class) { + return (TypeAdapter) new DataTypeAdapter(gson.getDelegateAdapter(this, TypeToken.get(Data.class)), + gson.getAdapter(JsonElement.class)); + } + + if (type.getRawType() == DerivedData.class) { + return (TypeAdapter) new DerivedDataTypeAdapter( + gson.getDelegateAdapter(this, TypeToken.get(DerivedData.class)), + gson.getAdapter(JsonElement.class)); + } + + return null; + } + + /** + * Adapter for "Data". + */ + private class DataTypeAdapter extends TypeAdapter { + private TypeAdapter delegate; + private TypeAdapter elementAdapter; + + /** + * Constructs the object. + * + * @param delegate delegate adapter + * @param elementAdapter element adapter + */ + public DataTypeAdapter(TypeAdapter delegate, TypeAdapter elementAdapter) { + this.delegate = delegate; + this.elementAdapter = elementAdapter; + } + + @Override + public void write(JsonWriter out, Data data) throws IOException { + dataWritten = true; + + JsonElement tree = delegate.toJsonTree(data); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + jsonObj.addProperty("id", data.getId()); + } + + elementAdapter.write(out, tree); + } + + @Override + public Data read(JsonReader in) throws IOException { + dataRead = true; + + JsonElement tree = elementAdapter.read(in); + Data data = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + data.setId(jsonObj.get("id").getAsInt()); + } + + return data; + } + } + /** + * Adapter for "DerivedData". + */ + private class DerivedDataTypeAdapter extends TypeAdapter { + private TypeAdapter delegate; + private TypeAdapter elementAdapter; + + /** + * Constructs the object. + * + * @param delegate delegate adapter + * @param elementAdapter element adapter + */ + public DerivedDataTypeAdapter(TypeAdapter delegate, TypeAdapter elementAdapter) { + this.delegate = delegate; + this.elementAdapter = elementAdapter; + } + + @Override + public void write(JsonWriter out, DerivedData data) throws IOException { + dataWritten = true; + + JsonElement tree = delegate.toJsonTree(data); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + jsonObj.addProperty("id", data.getId()); + jsonObj.addProperty("text", data.getText()); + } + + elementAdapter.write(out, tree); + } + + @Override + public DerivedData read(JsonReader in) throws IOException { + dataRead = true; + + JsonElement tree = elementAdapter.read(in); + DerivedData data = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + data.setId(jsonObj.get("id").getAsInt()); + data.setText(jsonObj.get("text").getAsString()); + } + + return data; + } + } +} -- cgit 1.2.3-korg