From d299d6ecb966971fa0226d4da7415bb1de62fe8c Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Wed, 13 Feb 2019 10:42:19 -0500 Subject: Refactor common class from gson code The TypeAdapters in the gson-jackson code were nearly identical, so a common class was factored out. This also enabled junit tests to attain 100% coverage on that code, whereas there were a couple of branches that could not be tested previously. Addressed new sonar issues. Removed unused import. Change-Id: Id8e6460c881c6ce0239768f182e4e652cd10645f Issue-ID: POLICY-1428 Signed-off-by: Jim Hahn --- .../policy/common/gson/GsonMessageBodyHandler.java | 3 +- .../common/gson/JacksonFieldAdapterFactory.java | 132 +++---------- .../common/gson/JacksonMethodAdapterFactory.java | 162 ++++------------ .../common/gson/internal/JacksonTypeAdapter.java | 108 +++++++++++ .../gson/internal/JacksonTypeAdapterTest.java | 206 +++++++++++++++++++++ 5 files changed, 381 insertions(+), 230 deletions(-) create mode 100644 gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java create mode 100644 gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java diff --git a/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java index 66475e3e..6906696f 100644 --- a/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java +++ b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java @@ -31,7 +31,6 @@ import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; @@ -117,7 +116,7 @@ public class GsonMessageBodyHandler implements MessageBodyReader, Messag @Override public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) - throws IOException, WebApplicationException { + throws IOException { try (InputStreamReader streamReader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) { Type jsonType = (type.equals(genericType) ? type : genericType); diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java index 67c51449..3458a590 100644 --- a/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java +++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java @@ -21,14 +21,9 @@ package org.onap.policy.common.gson; import com.google.gson.Gson; -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.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -36,6 +31,7 @@ import org.onap.policy.common.gson.internal.ClassWalker; import org.onap.policy.common.gson.internal.Deserializer; import org.onap.policy.common.gson.internal.FieldDeserializer; import org.onap.policy.common.gson.internal.FieldSerializer; +import org.onap.policy.common.gson.internal.JacksonTypeAdapter; import org.onap.policy.common.gson.internal.Serializer; /** @@ -66,116 +62,40 @@ public class JacksonFieldAdapterFactory implements TypeAdapterFactory { return null; } - return new JacksonFieldAdapter<>(gson, data, gson.getDelegateAdapter(this, type)); + TypeAdapter delegate = gson.getDelegateAdapter(this, type); + List sers = makeSerializers(gson, data); + List desers = makeDeserializers(gson, data); + + return new JacksonTypeAdapter<>(gson, delegate, sers, desers); } /** - * Adapter for a single class. + * Creates a complete list of serializers. * - * @param type of class on which the adapter works + * @param gson the associated gson object + * @param data data used to configure the serializers + * @return a list of all serializers */ - private static class JacksonFieldAdapter extends TypeAdapter { - - /** - * Used to create an object of the given class. - */ - private final TypeAdapter delegate; - - /** - * Used to serialize/deserialize a JsonElement. - */ - private final TypeAdapter elementAdapter; - - /** - * Serializers for each item within the object. - */ - private final Serializer[] serializers; - - /** - * Deserializers for each item within the object. - */ - private final Deserializer[] deserializers; - - /** - * Constructs the object. - * - * @param gson the associated gson object - * @param data data used to configure the adapter - * @param delegate default constructor for the type - */ - public JacksonFieldAdapter(Gson gson, ClassWalker data, TypeAdapter delegate) { - this.delegate = delegate; - - this.elementAdapter = gson.getAdapter(JsonElement.class); - - // create serializers - this.serializers = makeSerializers(gson, data).toArray(new Serializer[0]); - - // create deserializers - this.deserializers = makeDeserializers(gson, data).toArray(new Deserializer[0]); - } - - /** - * Creates a complete list of serializers. - * - * @param gson the associated gson object - * @param data data used to configure the serializers - * @return a list of all serializers - */ - private List makeSerializers(Gson gson, ClassWalker data) { - List ser = new ArrayList(); - - data.getOutProps(Field.class).forEach(field -> ser.add(new FieldSerializer(gson, field))); - - return ser; - } + private List makeSerializers(Gson gson, ClassWalker data) { + List ser = new ArrayList<>(); - /** - * Creates a complete list of deserializers. - * - * @param gson the associated gson object - * @param data data used to configure the deserializers - * @return a list of all deserializers - */ - private List makeDeserializers(Gson gson, ClassWalker data) { - List deser = new ArrayList(); + data.getOutProps(Field.class).forEach(field -> ser.add(new FieldSerializer(gson, field))); - data.getInProps(Field.class).forEach(field -> deser.add(new FieldDeserializer(gson, field))); - - return deser; - } - - @Override - public void write(JsonWriter out, T value) throws IOException { - JsonElement tree = delegate.toJsonTree(value); - - if (tree.isJsonObject()) { - JsonObject jsonObj = tree.getAsJsonObject(); - - // serialize each item from the value into the target tree - for (Serializer serializer : serializers) { - serializer.addToTree(value, jsonObj); - } - } - - elementAdapter.write(out, tree); - } - - @Override - public T read(JsonReader in) throws IOException { - JsonElement tree = elementAdapter.read(in); - T object = delegate.fromJsonTree(tree); + return ser; + } - if (tree.isJsonObject()) { - JsonObject jsonObj = tree.getAsJsonObject(); + /** + * Creates a complete list of deserializers. + * + * @param gson the associated gson object + * @param data data used to configure the deserializers + * @return a list of all deserializers + */ + private List makeDeserializers(Gson gson, ClassWalker data) { + List deser = new ArrayList<>(); - // deserialize each item from the tree into the target object - for (Deserializer dser : deserializers) { - dser.getFromTree(jsonObj, object); - } - } + data.getInProps(Field.class).forEach(field -> deser.add(new FieldDeserializer(gson, field))); - return object; - } + return deser; } } diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java index 1c3039f7..de962316 100644 --- a/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java +++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java @@ -21,14 +21,9 @@ package org.onap.policy.common.gson; import com.google.gson.Gson; -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.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; @@ -38,6 +33,7 @@ import org.onap.policy.common.gson.internal.AnyGetterSerializer; import org.onap.policy.common.gson.internal.AnySetterDeserializer; import org.onap.policy.common.gson.internal.ClassWalker; import org.onap.policy.common.gson.internal.Deserializer; +import org.onap.policy.common.gson.internal.JacksonTypeAdapter; import org.onap.policy.common.gson.internal.MethodDeserializer; import org.onap.policy.common.gson.internal.MethodSerializer; import org.onap.policy.common.gson.internal.Serializer; @@ -65,138 +61,60 @@ public class JacksonMethodAdapterFactory implements TypeAdapterFactory { ClassWalker data = new ClassWalker(); data.walkClassHierarchy(clazz); - if (data.getInProps(Method.class).isEmpty() && data.getOutProps(Method.class).isEmpty()) { - if (data.getAnyGetter() == null && data.getAnySetter() == null) { - // no methods to serialize - return null; - } + if (data.getInProps(Method.class).isEmpty() && data.getOutProps(Method.class).isEmpty() + && data.getAnyGetter() == null && data.getAnySetter() == null) { + // no methods to serialize + return null; } - return new JacksonMethodAdapter<>(gson, data, gson.getDelegateAdapter(this, type)); + Set unliftedProps = new HashSet<>(); + unliftedProps.addAll(data.getInNotIgnored()); + unliftedProps.addAll(data.getOutNotIgnored()); + + TypeAdapter delegate = gson.getDelegateAdapter(this, type); + List sers = makeSerializers(gson, data, unliftedProps); + List desers = makeDeserializers(gson, data, unliftedProps); + + return new JacksonTypeAdapter<>(gson, delegate, sers, desers); } /** - * Adapter for a single class. + * Creates a complete list of serializers. * - * @param type of class on which the adapter works + * @param gson the associated gson object + * @param data data used to configure the serializers + * @param unliftedProps properties that should not be lowered by "any-getters" + * @return a list of all serializers */ - private static class JacksonMethodAdapter extends TypeAdapter { - - /** - * Used to create an object of the given class. - */ - private final TypeAdapter delegate; - - /** - * Used to serialize/deserialize a JsonElement. - */ - private final TypeAdapter elementAdapter; - - /** - * Serializers for each item within the object. - */ - private final Serializer[] serializers; - - /** - * Deserializers for each item within the object. - */ - private final Deserializer[] deserializers; - - /** - * Constructs the object. - * - * @param gson the associated gson object - * @param data data used to configure the adapter - * @param delegate default constructor for the type - */ - public JacksonMethodAdapter(Gson gson, ClassWalker data, TypeAdapter delegate) { - this.delegate = delegate; - - this.elementAdapter = gson.getAdapter(JsonElement.class); - - Set unliftedProps = new HashSet<>(); - unliftedProps.addAll(data.getInNotIgnored()); - unliftedProps.addAll(data.getOutNotIgnored()); - - // create serializers - this.serializers = makeSerializers(gson, data, unliftedProps).toArray(new Serializer[0]); - - // create deserializers - this.deserializers = makeDeserializers(gson, data, unliftedProps).toArray(new Deserializer[0]); - } - - /** - * Creates a complete list of serializers. - * - * @param gson the associated gson object - * @param data data used to configure the serializers - * @param unliftedProps properties that should not be lowered by "any-getters" - * @return a list of all serializers - */ - private List makeSerializers(Gson gson, ClassWalker data, Set unliftedProps) { - List ser = new ArrayList(); - - if (data.getAnyGetter() != null) { - ser.add(new AnyGetterSerializer(gson, unliftedProps, data.getAnyGetter())); - } - - data.getOutProps(Method.class).forEach(method -> ser.add(new MethodSerializer(gson, method))); - - return ser; - } - - /** - * Creates a complete list of deserializers. - * - * @param gson the associated gson object - * @param data data used to configure the deserializers - * @param unliftedProps properties that should not be lifted by "any-setters" - * @return a list of all deserializers - */ - private List makeDeserializers(Gson gson, ClassWalker data, Set unliftedProps) { - List deser = new ArrayList(); + private List makeSerializers(Gson gson, ClassWalker data, Set unliftedProps) { + List ser = new ArrayList<>(); - if (data.getAnySetter() != null) { - deser.add(new AnySetterDeserializer(gson, unliftedProps, data.getAnySetter())); - } - - data.getInProps(Method.class).forEach(method -> deser.add(new MethodDeserializer(gson, method))); - - return deser; + if (data.getAnyGetter() != null) { + ser.add(new AnyGetterSerializer(gson, unliftedProps, data.getAnyGetter())); } - @Override - public void write(JsonWriter out, T value) throws IOException { - JsonElement tree = delegate.toJsonTree(value); + data.getOutProps(Method.class).forEach(method -> ser.add(new MethodSerializer(gson, method))); - if (tree.isJsonObject()) { - JsonObject jsonObj = tree.getAsJsonObject(); + return ser; + } - // serialize each item from the value into the target tree - for (Serializer serializer : serializers) { - serializer.addToTree(value, jsonObj); - } - } + /** + * Creates a complete list of deserializers. + * + * @param gson the associated gson object + * @param data data used to configure the deserializers + * @param unliftedProps properties that should not be lifted by "any-setters" + * @return a list of all deserializers + */ + private List makeDeserializers(Gson gson, ClassWalker data, Set unliftedProps) { + List deser = new ArrayList<>(); - elementAdapter.write(out, tree); + if (data.getAnySetter() != null) { + deser.add(new AnySetterDeserializer(gson, unliftedProps, data.getAnySetter())); } - @Override - public T read(JsonReader in) throws IOException { - JsonElement tree = elementAdapter.read(in); - - T object = delegate.fromJsonTree(tree); - - if (tree.isJsonObject()) { - JsonObject jsonObj = tree.getAsJsonObject(); + data.getInProps(Method.class).forEach(method -> deser.add(new MethodDeserializer(gson, method))); - // deserialize each item from the tree into the target object - for (Deserializer dser : deserializers) { - dser.getFromTree(jsonObj, object); - } - } - - return object; - } + return deser; } } diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java new file mode 100644 index 00000000..1171fd4d --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java @@ -0,0 +1,108 @@ +/* + * ============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.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.List; + + +/** + * Adapter for a single class that implements a jackson-style behavior. + * + * @param type of class on which the adapter works + */ +public class JacksonTypeAdapter extends TypeAdapter { + + /** + * Used to create an object of the given class. + */ + private final TypeAdapter delegate; + + /** + * Used to serialize/deserialize a JsonElement. + */ + private final TypeAdapter elementAdapter; + + /** + * Serializers for each item within the object. + */ + private final Serializer[] serializers; + + /** + * Deserializers for each item within the object. + */ + private final Deserializer[] deserializers; + + /** + * Constructs the object. + * + * @param gson the associated gson object + * @param delegate default constructor for the type + * @param serializers the serializers to use to serialize items within the object + * @param deserializers the deserializers to use to deserialize items into the object + */ + public JacksonTypeAdapter(Gson gson, TypeAdapter delegate, List serializers, + List deserializers) { + this.delegate = delegate; + this.elementAdapter = gson.getAdapter(JsonElement.class); + this.serializers = serializers.toArray(new Serializer[0]); + this.deserializers = deserializers.toArray(new Deserializer[0]); + } + + @Override + public void write(JsonWriter out, T value) throws IOException { + JsonElement tree = delegate.toJsonTree(value); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + + // serialize each item from the value into the target tree + for (Serializer serializer : serializers) { + serializer.addToTree(value, jsonObj); + } + } + + elementAdapter.write(out, tree); + } + + @Override + public T read(JsonReader in) throws IOException { + JsonElement tree = elementAdapter.read(in); + T object = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + + // deserialize each item from the tree into the target object + for (Deserializer dser : deserializers) { + dser.getFromTree(jsonObj, object); + } + } + + return object; + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java new file mode 100644 index 00000000..a75fe17e --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java @@ -0,0 +1,206 @@ +/* + * ============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 com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; + +public class JacksonTypeAdapterTest { + private static final String HELLO = "hello"; + private static final String WORLD = "world"; + + /** + * Gson object that excludes fields, as we're going to process the fields ourselves. + */ + private static Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + + private JacksonTypeAdapter adapter; + private List sers; + private List desers; + + /** + * Initializes the previously defined fields. + */ + @Before + public void setUp() { + // create list of serializers, one for "id" and one for "value" + sers = new ArrayList<>(2); + sers.add(new NamedSer(HELLO) { + @Override + protected String getValue(Data data) { + return data.id; + } + }); + sers.add(new NamedSer(WORLD) { + @Override + protected String getValue(Data data) { + return data.value; + } + }); + + // create list of deserializers, one for "id" and one for "value" + desers = new ArrayList<>(2); + desers.add(new NamedDeser(HELLO) { + @Override + protected void setValue(Data data, String value) { + data.id = value; + } + }); + desers.add(new NamedDeser(WORLD) { + @Override + protected void setValue(Data data, String value) { + data.value = value; + } + }); + + TypeAdapter delegate = gson.getDelegateAdapter(null, TypeToken.get(Data.class)); + + adapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers); + } + + @Test + public void testWriteJsonWriterT() throws Exception { + Data data = new Data("abc", "def"); + + StringWriter wtr = new StringWriter(); + adapter.write(new JsonWriter(wtr), data); + + assertEquals("{'hello':'abc','world':'def'}".replace('\'', '"'), wtr.toString()); + } + + /** + * Tests the case where the delegate does not return a JsonObject. + * + * @throws Exception if an error occurs + */ + @Test + public void testWriteJsonWriterT_NotAnObject() throws Exception { + TypeAdapter delegate = gson.getAdapter(String.class); + JacksonTypeAdapter stringAdapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers); + + StringWriter wtr = new StringWriter(); + stringAdapter.write(new JsonWriter(wtr), "write text"); + + assertEquals("'write text'".replace('\'', '"'), wtr.toString()); + } + + @Test + public void testReadJsonReader() throws Exception { + Data data = adapter + .read(new JsonReader(new StringReader("{'hello':'four','world':'score'}".replace('\'', '"')))); + + assertEquals(new Data("four", "score").toString(), data.toString()); + } + + /** + * Tests the case where the delegate does not use a JsonObject. + * + * @throws Exception if an error occurs + */ + @Test + public void testReadJsonReader_NotAnObject() throws Exception { + TypeAdapter delegate = gson.getAdapter(String.class); + JacksonTypeAdapter stringAdapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers); + + String data = stringAdapter.read(new JsonReader(new StringReader("'read text'".replace('\'', '"')))); + + assertEquals("read text", data.toString()); + } + + private static class Data { + private String id; + private String value; + + /* + * This is invoked by gson via reflection, thus no direct invocation. Hence it has + * to be labeled "unused". + */ + @SuppressWarnings("unused") + public Data() { + super(); + } + + public Data(String id, String value) { + this.id = id; + this.value = value; + } + + @Override + public String toString() { + return "Data [id=" + id + ", value=" + value + "]"; + } + } + + private abstract static class NamedSer implements Serializer { + private final String name; + + /** + * Constructs the object. + * + * @param name the name of the field, when stored in a JsonObject + */ + public NamedSer(String name) { + this.name = name; + } + + @Override + public void addToTree(Object source, JsonObject target) { + Data data = (Data) source; + target.addProperty(name, getValue(data)); + } + + protected abstract String getValue(Data data); + } + + private abstract static class NamedDeser implements Deserializer { + private final String name; + + /** + * Constructs the object. + * + * @param name the name of the field, when stored in a JsonObject + */ + public NamedDeser(String name) { + this.name = name; + } + + @Override + public void getFromTree(JsonObject source, Object target) { + Data data = (Data) target; + setValue(data, source.get(name).getAsString()); + } + + protected abstract void setValue(Data data, String value); + } +} -- cgit 1.2.3-korg