From 4ec725ef0905cd5490ed71b6576fdc1ef8fef17e Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Wed, 6 Feb 2019 13:14:57 -0500 Subject: Add superclasses for gson-jackson migration Added common classes needed by other gson-jackson code. Modified some logic to make it more maintainable or perform better. Updated comments and spacing. Fix another comment. Moved gson classes from utils to a separate gson project. Added GsonXxx annotations to mirror jackson annotations. Removed unneeded dependencies from gson pom. Removed old GsonMessage class from policy-endpoints. Removed trailing spaces. Updated licenses. Removed more trailing spaces. Removed unneeded checkstyle suppression file from utils. Change-Id: I1a285500faeb0a0b6a1467d09b92ecd3cded713e Issue-ID: POLICY-1428 Signed-off-by: Jim Hahn --- .../policy/common/gson/GsonMessageBodyHandler.java | 124 +++++++ .../common/gson/JacksonExclusionStrategy.java | 104 ++++++ .../common/gson/annotation/GsonJsonAnyGetter.java | 38 ++ .../common/gson/annotation/GsonJsonAnySetter.java | 38 ++ .../common/gson/annotation/GsonJsonIgnore.java | 39 +++ .../common/gson/annotation/GsonJsonProperty.java | 44 +++ .../onap/policy/common/gson/internal/Adapter.java | 339 ++++++++++++++++++ .../policy/common/gson/internal/ClassWalker.java | 389 +++++++++++++++++++++ .../policy/common/gson/internal/Deserializer.java | 39 +++ 9 files changed, 1154 insertions(+) create mode 100644 gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java create mode 100644 gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java (limited to 'gson/src/main/java/org') 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, MessageBodyWriter { + + /** + * 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 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 type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap 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> 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> 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 + * not 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 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 conv = + (TypeAdapter) (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 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 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 outProps = new HashMap<>(); + + /** + * Maps a method name to a "get" method. Used when overriding properties associated + * with a method. + */ + private final Map getters = new HashMap<>(); + + /** + * Maps a method name to a "set" method. Used when overriding properties associated + * with a method. + */ + private final Map 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 getInNotIgnored() { + return getNonNull(inProps); + } + + /** + * Gets the names of output properties that are not being ignored. + * + * @return the non-ignored output property names + */ + public List 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 getNonNull(Map props) { + List lst = new ArrayList(props.size()); + + for (Entry 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 List getInProps(Class 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 List getOutProps(Class 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 List getProps(Class clazz, Collection values) { + List lst = new ArrayList(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); +} -- cgit 1.2.3-korg