aboutsummaryrefslogtreecommitdiffstats
path: root/gson
diff options
context:
space:
mode:
authorJim Hahn <jrh3@att.com>2019-02-06 13:14:57 -0500
committerJim Hahn <jrh3@att.com>2019-02-08 17:18:45 -0500
commit4ec725ef0905cd5490ed71b6576fdc1ef8fef17e (patch)
tree45fa7a22bbb586b3c6bf3821dcd4618403dbe9e5 /gson
parent7f1be8710503b4c34a49c96be8a6818757499bd6 (diff)
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 <jrh3@att.com>
Diffstat (limited to 'gson')
-rw-r--r--gson/checkstyle-suppressions.xml39
-rw-r--r--gson/pom.xml143
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java124
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java104
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java38
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java38
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java39
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java44
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java339
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java389
-rw-r--r--gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java39
-rw-r--r--gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java156
-rw-r--r--gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java203
-rw-r--r--gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java387
-rw-r--r--gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java507
-rw-r--r--gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java310
16 files changed, 2899 insertions, 0 deletions
diff --git a/gson/checkstyle-suppressions.xml b/gson/checkstyle-suppressions.xml
new file mode 100644
index 00000000..0705e0e6
--- /dev/null
+++ b/gson/checkstyle-suppressions.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ Copyright (C) 2019 AT&T Technologies. 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.
+
+ SPDX-License-Identifier: Apache-2.0
+ ============LICENSE_END=========================================================
+-->
+
+<!--
+ NOTE: The sole purpose of this supression file is to allow "$" in field and method
+ names so that the gson class tests can verify that those fields are ignored
+ when doing serialization and de-serialization.
+ -->
+
+<!DOCTYPE suppressions PUBLIC
+ "-//Puppy Crawl//DTD Suppressions 1.0//EN"
+ "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
+
+<suppressions>
+ <suppress checks="MemberName"
+ files="AdapterTest.java|ClassWalkerTest.java"
+ lines="1-9999"/>
+ <suppress checks="MethodName"
+ files="AdapterTest.java"
+ lines="1-9999"/>
+</suppressions>
diff --git a/gson/pom.xml b/gson/pom.xml
new file mode 100644
index 00000000..3b9983f2
--- /dev/null
+++ b/gson/pom.xml
@@ -0,0 +1,143 @@
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Common Modules
+ ================================================================================
+ Copyright (C) 2018-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=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>common-modules</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>gson</artifactId>
+ <description>Common Utilities</description>
+ <packaging>jar</packaging>
+
+ <properties>
+ <!-- TODO move to top-level or parent -->
+ <jersey.version>2.25.1</jersey.version>
+ <jackson.version>2.9.5</jackson.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>3.11.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings
+ only. It has no influence on the Maven build itself. -->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.jacoco</groupId>
+ <artifactId>
+ jacoco-maven-plugin
+ </artifactId>
+ <versionRange>
+ [0.7.1.201405082137,)
+ </versionRange>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore />
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>onap-java-style</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <phase>process-sources</phase>
+ <configuration>
+ <!-- Use Google Java Style Guide:
+ https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
+ with minor changes -->
+ <configLocation>onap-checkstyle/onap-java-style.xml</configLocation>
+ <!-- <sourceDirectory> is needed so that checkstyle ignores the generated sources directory -->
+ <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+ <includeResources>true</includeResources>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <includeTestResources>true</includeTestResources>
+ <excludes>
+ </excludes>
+ <suppressionsLocation>${project.basedir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <consoleOutput>true</consoleOutput>
+ <failsOnViolation>true</failsOnViolation>
+ <violationSeverity>warning</violationSeverity>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.oparent</groupId>
+ <artifactId>checkstyle</artifactId>
+ <version>${oparent.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..2112c97c
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
@@ -0,0 +1,124 @@
+/*
+ * ============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 com.google.gson.Gson;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+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;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Provider that serializes and de-serializes JSON via gson.
+ */
+@Provider
+@Consumes(MediaType.WILDCARD)
+@Produces(MediaType.WILDCARD)
+public class GsonMessageBodyHandler implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
+
+ /**
+ * Object to be used to serialize and de-serialize.
+ */
+ private Gson gson;
+
+ /**
+ * Constructs the object, using a plain Gson object.
+ */
+ public GsonMessageBodyHandler() {
+ this(new Gson());
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param gson the Gson object to be used to serialize and de-serialize
+ */
+ public GsonMessageBodyHandler(Gson gson) {
+ this.gson = gson;
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return canHandle(mediaType);
+ }
+
+ @Override
+ public long getSize(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+ throws IOException, WebApplicationException {
+
+ try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) {
+ Type jsonType = (type.equals(genericType) ? type : genericType);
+ gson.toJson(object, jsonType, writer);
+ }
+ }
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return canHandle(mediaType);
+ }
+
+ /**
+ * Determines if this provider can handle the given media type.
+ *
+ * @param mediaType the media type of interest
+ * @return {@code true} if this provider handles the given media type, {@code false}
+ * otherwise
+ */
+ private boolean canHandle(MediaType mediaType) {
+ if (mediaType == null) {
+ return true;
+ }
+
+ String subtype = mediaType.getSubtype();
+
+ return "json".equalsIgnoreCase(subtype) || subtype.endsWith("+json") || "javascript".equals(subtype)
+ || "x-javascript".equals(subtype) || "x-json".equals(subtype);
+ }
+
+ @Override
+ public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+ throws IOException, WebApplicationException {
+
+ try (InputStreamReader streamReader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) {
+ Type jsonType = (type.equals(genericType) ? type : genericType);
+ return gson.fromJson(streamReader, jsonType);
+ }
+ }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java
new file mode 100644
index 00000000..cb959c43
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java
@@ -0,0 +1,104 @@
+/*
+ * ============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 com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.JsonElement;
+import java.lang.reflect.GenericArrayType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Excludes all fields from serialization/deserialization, if the class is managed.
+ */
+public class JacksonExclusionStrategy implements ExclusionStrategy {
+
+ /**
+ * Classes that are explicitly not managed by the GSON jackson adapters.
+ */
+ // @formatter:off
+ private static final Set<Class<?>> unmanaged = new HashSet<>(Arrays.asList(
+ boolean.class,
+ byte.class,
+ short.class,
+ int.class,
+ long.class,
+ float.class,
+ double.class,
+ char.class,
+ Boolean.class,
+ Byte.class,
+ Short.class,
+ Integer.class,
+ Long.class,
+ Float.class,
+ Double.class,
+ Character.class,
+ String.class));
+ // @formatter:on
+
+ /**
+ * Classes whose subclasses are explicitly not managed by the GSON jackson adapters.
+ */
+ // @formatter:off
+ private static final Set<Class<?>> unmanagedSuper = new HashSet<>(Arrays.asList(
+ GenericArrayType.class,
+ Map.class,
+ Collection.class,
+ JsonElement.class));
+ // @formatter:on
+
+ @Override
+ public boolean shouldSkipField(FieldAttributes attrs) {
+ return isManaged(attrs.getDeclaringClass());
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return false;
+ }
+
+ /**
+ * Determines if a class is managed by this adapter, which typically means that it is
+ * <i>not</i> a generic class such as {@link JsonElement} or some type of collection.
+ *
+ * @param clazz the class to be examined
+ * @return {@code true} if the class is managed by this adapter, {@code false}
+ * otherwise
+ */
+ public static boolean isManaged(Class<?> clazz) {
+ if (clazz.isArray() || clazz.isEnum() || clazz.isPrimitive() || unmanaged.contains(clazz)) {
+ return false;
+ }
+
+ for (Class<?> sup : unmanagedSuper) {
+ if (sup.isAssignableFrom(clazz)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java
new file mode 100644
index 00000000..859f5386
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java
@@ -0,0 +1,38 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonAnyGetter annotation, but used by gson. This requires the gson
+ * object to be configured with the jackson default behaviors (i.e., the associated
+ * JacksonXxx strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface GsonJsonAnyGetter {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java
new file mode 100644
index 00000000..87e0f330
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java
@@ -0,0 +1,38 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonAnySetter annotation, but used by gson. This requires the gson
+ * object to be configured with the jackson default behaviors (i.e., the associated
+ * JacksonXxx strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface GsonJsonAnySetter {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java
new file mode 100644
index 00000000..cf2d4394
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java
@@ -0,0 +1,39 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonIgnore annotation, but used by gson. This requires the gson object
+ * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx
+ * strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target({FIELD, METHOD})
+public @interface GsonJsonIgnore {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java
new file mode 100644
index 00000000..c31c19bb
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java
@@ -0,0 +1,44 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonProperty annotation, but used by gson. This requires the gson object
+ * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx
+ * strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target({FIELD, METHOD})
+public @interface GsonJsonProperty {
+
+ /**
+ * Property name of this item when placed into a JsonObject.
+ * @return the item's serialized name
+ */
+ String value() default "";
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java
new file mode 100644
index 00000000..b4ef53f7
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java
@@ -0,0 +1,339 @@
+/*
+ * ============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.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import org.onap.policy.common.gson.annotation.GsonJsonProperty;
+
+/**
+ * Super class of adapters used to serialize and de-serialize an item.
+ */
+public class Adapter {
+
+ /**
+ * Pattern to match valid identifiers.
+ */
+ private static final Pattern VALID_NAME_PAT = Pattern.compile("[a-zA-Z_]\\w*");
+
+ /**
+ * Name of the property within the json structure containing the item.
+ */
+ private final String propName;
+
+ /**
+ * Gson object that will provide the type converter.
+ */
+ private final Gson gson;
+
+ /**
+ * Converter used when reading.
+ */
+ private final ConvInfo reader;
+
+ /**
+ * Converter used when writing, allocated lazily, once an actual type is determined.
+ */
+ private volatile ConvInfo writer = null;
+
+ /**
+ * Name of the item being lifted - used when throwing exceptions.
+ */
+ private final String fullName;
+
+ /**
+ * Constructs the object.
+ *
+ * @param gson Gson object providing type adapters
+ * @param field field used to access the item from within an object
+ */
+ public Adapter(Gson gson, Field field) {
+ this.propName = detmPropName(field);
+ this.reader = new ConvInfo(TypeToken.get(field.getGenericType()));
+ this.gson = gson;
+ this.fullName = getQualifiedName(field);
+
+ field.setAccessible(true);
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param gson Gson object providing type adapters
+ * @param accessor method used to access the item from within an object
+ * @param forGetter {@code true} if the name is for a "getter" method, {@code false}
+ * if for a "setter"
+ * @param valueType the class of value on which this operates
+ */
+ public Adapter(Gson gson, Method accessor, boolean forGetter, Type valueType) {
+ this.propName = (forGetter ? detmGetterPropName(accessor) : detmSetterPropName(accessor));
+ this.reader = new ConvInfo(TypeToken.get(valueType));
+ this.gson = gson;
+ this.fullName = getQualifiedName(accessor);
+
+ accessor.setAccessible(true);
+ }
+
+ /**
+ * Converts an object to a json tree.
+ *
+ * @param object the object to be converted
+ * @return a json tree representing the object
+ */
+ @SuppressWarnings("unchecked")
+ public JsonElement toJsonTree(Object object) {
+ // always use a converter for the specific subclass
+ Class<? extends Object> clazz = object.getClass();
+
+ if (writer == null) {
+ // race condition here, but it's ok to overwrite a previous value
+ writer = new ConvInfo(TypeToken.get(clazz));
+ }
+
+ ConvInfo wtr = writer;
+ TypeAdapter<Object> conv =
+ (TypeAdapter<Object>) (wtr.clazz == clazz ? wtr.getConverter() : gson.getAdapter(clazz));
+
+ return conv.toJsonTree(object);
+ }
+
+ /**
+ * Converts a json tree to an object.
+ *
+ * @param tree the tree to be converted
+ * @return the object represented by the tree
+ */
+ public Object fromJsonTree(JsonElement tree) {
+ return reader.getConverter().fromJsonTree(tree);
+ }
+
+ public final String getPropName() {
+ return propName;
+ }
+
+ public final String getFullName() {
+ return fullName;
+ }
+
+ /**
+ * Makes an error message, appending the item's full name to the message prefix.
+ *
+ * @param prefix the message prefix
+ * @return the error message
+ */
+ public String makeError(String prefix) {
+ return (prefix + fullName);
+ }
+
+ /**
+ * Determines if the field is managed by the walker.
+ *
+ * @param field the field to examine
+ * @return {@code true} if the field is managed by the walker, {@code false} otherwise
+ */
+ public static boolean isManaged(Field field) {
+ return VALID_NAME_PAT.matcher(field.getName()).matches();
+ }
+
+ /**
+ * Determines if the method is managed by the walker.
+ *
+ * @param method the method to examine
+ * @return {@code true} if the method is managed by the walker, {@code false}
+ * otherwise
+ */
+ public static boolean isManaged(Method method) {
+ return VALID_NAME_PAT.matcher(method.getName()).matches();
+ }
+
+ /**
+ * Determines the property name of an item within the json structure.
+ *
+ * @param field the item within the object
+ * @return the json property name for the item or {@code null} if the name is invalid
+ */
+ public static String detmPropName(Field field) {
+ // use the serialized name, if specified
+ GsonJsonProperty prop = field.getAnnotation(GsonJsonProperty.class);
+ if (prop != null && !prop.value().isEmpty()) {
+ return prop.value();
+ }
+
+ // no name provided - use it as is
+ return (isManaged(field) ? field.getName() : null);
+ }
+
+ /**
+ * Determines the property name of an item, within the json structure, associated with
+ * a "get" method.
+ *
+ * @param method method to be invoked to get the item within the object
+ * @return the json property name for the item, or {@code null} if the method name is
+ * not valid
+ */
+ public static String detmGetterPropName(Method method) {
+
+ return detmPropNameCommon(method, () -> {
+
+ if (!isManaged(method)) {
+ return null;
+ }
+
+ String name = method.getName();
+
+ if (name.startsWith("get")) {
+ return name.substring(3);
+
+ } else if (name.startsWith("is")) {
+ Class<?> treturn = method.getReturnType();
+
+ if (treturn == boolean.class || treturn == Boolean.class) {
+ return name.substring(2);
+ }
+ }
+
+ // not a valid name for a "getter" method
+ return null;
+ });
+ }
+
+ /**
+ * Determines the property name of an item, within the json structure, associated with
+ * a "set" method.
+ *
+ * @param method method to be invoked to set the item within the object
+ * @return the json property name for the item, or {@code null} if the method name is
+ * not valid
+ */
+ public static String detmSetterPropName(Method method) {
+
+ return detmPropNameCommon(method, () -> {
+
+ if (!isManaged(method)) {
+ return null;
+ }
+
+ String name = method.getName();
+
+ if (name.startsWith("set")) {
+ return name.substring(3);
+ }
+
+ // not a valid name for a "setter" method
+ return null;
+ });
+ }
+
+ /**
+ * Determines the property name of an item within the json structure.
+ *
+ * @param method method to be invoked to get/set the item within the object
+ * @param extractor function to extract the name directly from the method name
+ * @return the json property name for the item, or {@code null} if the method name is
+ * not valid
+ */
+ private static String detmPropNameCommon(Method method, Supplier<String> extractor) {
+
+ // use the property name, if specified
+ GsonJsonProperty propName = method.getAnnotation(GsonJsonProperty.class);
+ if (propName != null && !propName.value().isEmpty()) {
+ return propName.value();
+ }
+
+ // no name provided - must compute it from the method name
+ String name = extractor.get();
+
+ if (name == null || name.isEmpty()) {
+ // nothing left after stripping the prefix - invalid name
+ return null;
+ }
+
+ // translate the first letter to lower-case
+ return name.substring(0, 1).toLowerCase() + name.substring(1);
+ }
+
+ /**
+ * Gets the fully qualified name of a field.
+ *
+ * @param field field whose name is desired
+ * @return the field fully qualified name
+ */
+ public static String getQualifiedName(Field field) {
+ return (field.getDeclaringClass().getName() + "." + field.getName());
+ }
+
+ /**
+ * Gets the fully qualified name of a method.
+ *
+ * @param method method whose name is desired
+ * @return the method's fully qualified name
+ */
+ public static String getQualifiedName(Method method) {
+ return (method.getDeclaringClass().getName() + "." + method.getName());
+ }
+
+ /**
+ * Converter info.
+ */
+ private class ConvInfo {
+
+ /**
+ * Type on which the converter works.
+ */
+ private TypeToken<?> type;
+
+ /**
+ * Class of object on which the converter works.
+ */
+ private Class<?> clazz;
+
+ /**
+ * Converter to use, initialized lazily.
+ */
+ private volatile TypeAdapter<?> conv = null;
+
+ /**
+ * Constructs the object.
+ *
+ * @param type type of object to be converted
+ */
+ public ConvInfo(TypeToken<?> type) {
+ this.type = type;
+ this.clazz = type.getRawType();
+ }
+
+ public final TypeAdapter<?> getConverter() {
+ if (conv == null) {
+ // race condition here, but it's ok to overwrite a previous value
+ this.conv = gson.getAdapter(type);
+ }
+
+ return conv;
+ }
+ }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java
new file mode 100644
index 00000000..e985d98a
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java
@@ -0,0 +1,389 @@
+/*
+ * ============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.JsonParseException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+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;
+
+/**
+ * Data populated while walking the hierarchy of a class.
+ */
+public class ClassWalker {
+
+ public static final String ANY_GETTER_MISMATCH_ERR =
+ GsonJsonAnyGetter.class.getSimpleName() + " parameter mismatch for: ";
+
+ public static final String ANY_SETTER_MISMATCH_ERR =
+ GsonJsonAnySetter.class.getSimpleName() + " parameter mismatch for: ";
+
+ public static final String ANY_SETTER_TYPE_ERR =
+ GsonJsonAnySetter.class.getSimpleName() + " first parameter must be a string: ";
+
+ /**
+ * Maps an input property name to an item within the class, where item is one of:
+ * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new
+ * items are added.
+ */
+ private final Map<String, Object> inProps = new HashMap<>();
+
+ /**
+ * Maps an output property name to an item within the class, where item is one of:
+ * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new
+ * items are added.
+ */
+ private final Map<String, Object> outProps = new HashMap<>();
+
+ /**
+ * Maps a method name to a "get" method. Used when overriding properties associated
+ * with a method.
+ */
+ private final Map<String, Method> getters = new HashMap<>();
+
+ /**
+ * Maps a method name to a "set" method. Used when overriding properties associated
+ * with a method.
+ */
+ private final Map<String, Method> setters = new HashMap<>();
+
+ /**
+ * Method having {@link GsonJsonAnyGetter} annotation. Overwritten as new "any-getters"
+ * are identified.
+ */
+ private Method anyGetter = null;
+
+ /**
+ * Method having {@link GsonJsonAnySetter} annotation. Overwritten as new "any-setters"
+ * are identified.
+ */
+ private Method anySetter = null;
+
+
+ public Method getAnyGetter() {
+ return anyGetter;
+ }
+
+ public Method getAnySetter() {
+ return anySetter;
+ }
+
+ /**
+ * Gets the names of input properties that are not being ignored.
+ *
+ * @return the non-ignored input property names
+ */
+ public List<String> getInNotIgnored() {
+ return getNonNull(inProps);
+ }
+
+ /**
+ * Gets the names of output properties that are not being ignored.
+ *
+ * @return the non-ignored output property names
+ */
+ public List<String> getOutNotIgnored() {
+ return getNonNull(outProps);
+ }
+
+ /**
+ * Gets the property names, associated with a non-null value, from a set of
+ * properties.
+ *
+ * @param props set of properties from which to extract the names
+ * @return the property names having a non-null value
+ */
+ private List<String> getNonNull(Map<String, Object> props) {
+ List<String> lst = new ArrayList<String>(props.size());
+
+ for (Entry<String, Object> ent : props.entrySet()) {
+ if (ent.getValue() != null) {
+ lst.add(ent.getKey());
+ }
+ }
+
+ return lst;
+ }
+
+ /**
+ * Gets the input properties whose values are of the given class.
+ *
+ * @param clazz class of properties to get
+ * @return the input properties of the given class
+ */
+ public <T> List<T> getInProps(Class<T> clazz) {
+ return getProps(clazz, inProps.values());
+ }
+
+ /**
+ * Gets the output properties whose values are of the given class.
+ *
+ * @param clazz class of properties to get
+ * @return the output properties of the given class
+ */
+ public <T> List<T> getOutProps(Class<T> clazz) {
+ return getProps(clazz, outProps.values());
+ }
+
+ /**
+ * Gets the properties whose values are of the given class.
+ *
+ * @param clazz class of properties to get
+ * @param values values from which to select
+ * @return the output properties of the given class
+ */
+ @SuppressWarnings("unchecked")
+ private <T> List<T> getProps(Class<T> clazz, Collection<Object> values) {
+ List<T> lst = new ArrayList<T>(values.size());
+
+ for (Object val : values) {
+ if (val != null && val.getClass() == clazz) {
+ lst.add((T) val);
+ }
+ }
+
+ return lst;
+ }
+
+ /**
+ * Recursively walks a class hierarchy, including super classes and interfaces,
+ * examining each class for various annotations.
+ *
+ * @param clazz class whose hierarchy is to be walked
+ */
+ public void walkClassHierarchy(Class<?> clazz) {
+ if (clazz == Object.class) {
+ return;
+ }
+
+ // walk interfaces first
+ for (Class<?> intfc : clazz.getInterfaces()) {
+ walkClassHierarchy(intfc);
+ }
+
+ // walk superclass next, overwriting previous items
+ Class<?> sup = clazz.getSuperclass();
+ if (sup != null) {
+ walkClassHierarchy(sup);
+ }
+
+ // finally, examine this class, overwriting previous items
+ examine(clazz);
+ }
+
+ /**
+ * Examines a class for annotations, examining fields and then methods.
+ *
+ * @param clazz class to be examined
+ */
+ protected void examine(Class<?> clazz) {
+ for (Field field : clazz.getDeclaredFields()) {
+ examine(field);
+ }
+
+ for (Method method : clazz.getDeclaredMethods()) {
+ examine(method);
+ }
+ }
+
+ /**
+ * Examines a field for annotations.
+ *
+ * @param field field to be examined
+ */
+ protected void examine(Field field) {
+ if (field.isSynthetic()) {
+ return;
+ }
+
+ int mod = field.getModifiers();
+
+ if (Modifier.isStatic(mod)) {
+ // skip static fields
+ return;
+ }
+
+ if (!Modifier.isPublic(mod) && field.getAnnotation(GsonJsonProperty.class) == null) {
+ // private/protected - skip it unless explicitly exposed
+ return;
+ }
+
+ if (Modifier.isTransient(mod) && field.getAnnotation(GsonJsonProperty.class) == null) {
+ // transient - skip it unless explicitly exposed
+ return;
+ }
+
+ String name = Adapter.detmPropName(field);
+ if (name == null) {
+ // invalid name
+ return;
+ }
+
+ // if ignoring, then insert null into the map, otherwise insert the field
+ Field annotField = (field.getAnnotation(GsonJsonIgnore.class) != null ? null : field);
+
+ // a field can be both an input and an output
+
+ inProps.put(name, annotField);
+ outProps.put(name, annotField);
+ }
+
+ /**
+ * Examines a method for annotations.
+ *
+ * @param method method to be examined
+ */
+ protected void examine(Method method) {
+ if (method.isSynthetic()) {
+ return;
+ }
+
+ int mod = method.getModifiers();
+
+ if (Modifier.isStatic(mod)) {
+ // static methods are not exposed
+ return;
+ }
+
+ GsonJsonProperty prop = method.getAnnotation(GsonJsonProperty.class);
+ GsonJsonAnyGetter get = method.getAnnotation(GsonJsonAnyGetter.class);
+ GsonJsonAnySetter set = method.getAnnotation(GsonJsonAnySetter.class);
+
+ if (!Modifier.isPublic(mod) && prop == null && get == null && set == null) {
+ // private/protected methods are not exposed, unless annotated
+ return;
+ }
+
+
+ if (method.getReturnType() == void.class) {
+ // "void" return type - must be a "setter" method
+ if (set == null) {
+ examineSetter(method);
+
+ } else {
+ examineAnySetter(method);
+ }
+
+ } else {
+ // must be a "getter" method
+ if (get == null) {
+ examineGetter(method);
+
+ } else {
+ examineAnyGetter(method);
+ }
+ }
+ }
+
+ /**
+ * Examines a "setter" method.
+ *
+ * @param method method to be examined
+ */
+ private void examineSetter(Method method) {
+ String name = Adapter.detmSetterPropName(method);
+ if (name != null && method.getParameterCount() == 1) {
+ // remove old name mapping, if any
+ Method old = setters.get(method.getName());
+ if (old != null) {
+ inProps.remove(Adapter.detmSetterPropName(old));
+ }
+
+ setters.put(method.getName(), method);
+
+ // if ignoring, then insert null into the map, otherwise insert the method
+ inProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method));
+ }
+ }
+
+ /**
+ * Examines a "getter" method.
+ *
+ * @param method method to be examined
+ */
+ private void examineGetter(Method method) {
+ String name = Adapter.detmGetterPropName(method);
+ if (name != null && method.getParameterCount() == 0) {
+ // remove old name mapping, if any
+ Method old = getters.get(method.getName());
+ if (old != null) {
+ outProps.remove(Adapter.detmGetterPropName(old));
+ }
+
+ getters.put(method.getName(), method);
+
+ // if ignoring, then insert null into the map, otherwise insert the method
+ outProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method));
+ }
+ }
+
+ /**
+ * Examines a method having a {@link GsonJsonAnySetter} annotation.
+ *
+ * @param method method to be examined
+ */
+ private void examineAnySetter(Method method) {
+ if (method.getParameterCount() != 2) {
+ throw new JsonParseException(ANY_SETTER_MISMATCH_ERR + getFqdn(method));
+ }
+
+ if (method.getParameterTypes()[0] != String.class) {
+ throw new JsonParseException(ANY_SETTER_TYPE_ERR + getFqdn(method));
+ }
+
+ // if ignoring, then use null, otherwise use the method
+ anySetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method);
+ }
+
+ /**
+ * Examines a method having a {@link GsonJsonAnyGetter} annotation.
+ *
+ * @param method method to be examined
+ */
+ private void examineAnyGetter(Method method) {
+ if (method.getParameterCount() != 0) {
+ throw new JsonParseException(ANY_GETTER_MISMATCH_ERR + getFqdn(method));
+ }
+
+ // if ignoring, then use null, otherwise use the method
+ anyGetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method);
+ }
+
+ /**
+ * Gets the fully qualified name of a method.
+ *
+ * @param method method whose name is desired
+ * @return the fully qualified method name
+ */
+ private String getFqdn(Method method) {
+ return (method.getDeclaringClass().getName() + "." + method.getName());
+ }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java
new file mode 100644
index 00000000..f2975860
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java
@@ -0,0 +1,39 @@
+/*
+ * ============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.JsonObject;
+
+/**
+ * Super class of all de-serializers.
+ */
+public interface Deserializer {
+
+ String INVOKE_ERR = "cannot invoke method to deserialize: ";
+
+ /**
+ * Gets an value from a tree, converts it, and puts it into a target object.
+ *
+ * @param source tree from which to get the value
+ * @param target where to place the converted value
+ */
+ void getFromTree(JsonObject source, Object target);
+}
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<Object> 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<Object> 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 <i>are</i> managed.
+ */
+ public static interface Intfc {
+ int getId();
+ }
+
+ /**
+ * Used to verify that Maps are not managed.
+ */
+ public static class MyMap extends TreeMap<String, Data> {
+ 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<Data> {
+ 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<Data> 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<Data> lst2 = (List<Data>) adapter.fromJsonTree(tree);
+ assertTrue(dataAdapter.isDataRead());
+
+ assertEquals(listField.toString(), lst2.toString());
+
+ // decode it twice so it uses the cached converter
+ dataAdapter.reset();
+ lst2 = (List<Data>) 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<Data> lst2 = (List<Data>) 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<Data> getMyList() {
+ return listField;
+ }
+
+ // accepts a list
+ protected void setMyList(List<Data> 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<String> inFields = walker.getInProps(Field.class).stream().map(field -> field.getName())
+ .collect(Collectors.toList());
+ Collections.sort(inFields);
+ assertEquals("[exposedField, overriddenValue, transField]", inFields.toString());
+
+ List<String> 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<String> 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<String> 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<String> 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<String> classes = new ArrayList<>();
+ private List<String> 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<String, String> map;
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @GsonJsonAnyGetter
+ public Map<String, String> getTheMap() {
+ return map;
+ }
+
+ @GsonJsonIgnore
+ public void setMap(Map<String, String> 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<String, String> getTheMap(String arg) {
+ return new TreeMap<>();
+ }
+ }
+
+ /**
+ * Has {@link GsonJsonAnyGetter} method.
+ */
+ private static class AnyGetterOnly {
+ @GsonJsonAnyGetter
+ private Map<String, Integer> getOverride() {
+ return null;
+ }
+ }
+
+ /**
+ * Has {@link GsonJsonAnyGetter} method, but it's ignored.
+ */
+ private static class AnyGetterIgnored {
+ @GsonJsonAnyGetter
+ @GsonJsonIgnore
+ private Map<String, Integer> 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<String, Integer> overMap;
+
+ @GsonJsonAnyGetter
+ private Map<String, Integer> getOverride() {
+ return overMap;
+ }
+ }
+
+ /**
+ * Has {@link GsonJsonAnySetter} method that overrides the super class' method.
+ */
+ private static class AnySetterOverride extends DerivedFromData {
+ private Map<String, Integer> 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<Data> makeList() {
+ List<Data> 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<String, List<Data>> makeMap() {
+ Map<String, List<Data>> 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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ if (type.getRawType() == Data.class) {
+ return (TypeAdapter<T>) new DataTypeAdapter(gson.getDelegateAdapter(this, TypeToken.get(Data.class)),
+ gson.getAdapter(JsonElement.class));
+ }
+
+ if (type.getRawType() == DerivedData.class) {
+ return (TypeAdapter<T>) new DerivedDataTypeAdapter(
+ gson.getDelegateAdapter(this, TypeToken.get(DerivedData.class)),
+ gson.getAdapter(JsonElement.class));
+ }
+
+ return null;
+ }
+
+ /**
+ * Adapter for "Data".
+ */
+ private class DataTypeAdapter extends TypeAdapter<Data> {
+ private TypeAdapter<Data> delegate;
+ private TypeAdapter<JsonElement> elementAdapter;
+
+ /**
+ * Constructs the object.
+ *
+ * @param delegate delegate adapter
+ * @param elementAdapter element adapter
+ */
+ public DataTypeAdapter(TypeAdapter<Data> delegate, TypeAdapter<JsonElement> 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<DerivedData> {
+ private TypeAdapter<DerivedData> delegate;
+ private TypeAdapter<JsonElement> elementAdapter;
+
+ /**
+ * Constructs the object.
+ *
+ * @param delegate delegate adapter
+ * @param elementAdapter element adapter
+ */
+ public DerivedDataTypeAdapter(TypeAdapter<DerivedData> delegate, TypeAdapter<JsonElement> 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;
+ }
+ }
+}