diff options
author | Jim Hahn <jrh3@att.com> | 2019-02-12 13:17:09 -0500 |
---|---|---|
committer | Jim Hahn <jrh3@att.com> | 2019-02-12 13:45:41 -0500 |
commit | 3cc17f964e46578f8ff6275072d1c77dafa551dd (patch) | |
tree | 6d6184907913d03686fecf4c41b93e49b0a6b2b6 /gson/src/main/java/org | |
parent | 13ca63c40a379f95539cb4882ec2b645cd0c2a48 (diff) |
Add jackson behavior for methods
Added classes to make gson treat methods as jackson does.
Remove unneeded "forGetter" parameter from constructors.
Fixed sonar issue with too long "if" test.
Corrected comment about annotation names.
Adjusted error message.
Removed unused constant.
Removed trailing spaces.
Change-Id: I3a4fc92193737a0dc89f1218e70f093aedbdb152
Issue-ID: POLICY-1428
Signed-off-by: Jim Hahn <jrh3@att.com>
Diffstat (limited to 'gson/src/main/java/org')
8 files changed, 577 insertions, 4 deletions
diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java new file mode 100644 index 00000000..1c3039f7 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java @@ -0,0 +1,202 @@ +/* + * ============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 com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.onap.policy.common.gson.internal.AnyGetterSerializer; +import org.onap.policy.common.gson.internal.AnySetterDeserializer; +import org.onap.policy.common.gson.internal.ClassWalker; +import org.onap.policy.common.gson.internal.Deserializer; +import org.onap.policy.common.gson.internal.MethodDeserializer; +import org.onap.policy.common.gson.internal.MethodSerializer; +import org.onap.policy.common.gson.internal.Serializer; + +/** + * Factory that serializes/deserializes class methods following the normal behavior of + * jackson. Supports the following annotations: + * <ul> + * <li>GsonJsonIgnore</li> + * <li>GsonJsonProperty</li> + * <li>GsonJsonAnyGetter</li> + * <li>GsonJsonAnySetter</li> + * </ul> + */ +public class JacksonMethodAdapterFactory implements TypeAdapterFactory { + + @Override + public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { + Class<? super T> clazz = type.getRawType(); + + if (!JacksonExclusionStrategy.isManaged(clazz)) { + return null; + } + + ClassWalker data = new ClassWalker(); + data.walkClassHierarchy(clazz); + + if (data.getInProps(Method.class).isEmpty() && data.getOutProps(Method.class).isEmpty()) { + if (data.getAnyGetter() == null && data.getAnySetter() == null) { + // no methods to serialize + return null; + } + } + + return new JacksonMethodAdapter<>(gson, data, gson.getDelegateAdapter(this, type)); + } + + /** + * Adapter for a single class. + * + * @param <T> type of class on which the adapter works + */ + private static class JacksonMethodAdapter<T> extends TypeAdapter<T> { + + /** + * Used to create an object of the given class. + */ + private final TypeAdapter<T> delegate; + + /** + * Used to serialize/deserialize a JsonElement. + */ + private final TypeAdapter<JsonElement> elementAdapter; + + /** + * Serializers for each item within the object. + */ + private final Serializer[] serializers; + + /** + * Deserializers for each item within the object. + */ + private final Deserializer[] deserializers; + + /** + * Constructs the object. + * + * @param gson the associated gson object + * @param data data used to configure the adapter + * @param delegate default constructor for the type + */ + public JacksonMethodAdapter(Gson gson, ClassWalker data, TypeAdapter<T> delegate) { + this.delegate = delegate; + + this.elementAdapter = gson.getAdapter(JsonElement.class); + + Set<String> unliftedProps = new HashSet<>(); + unliftedProps.addAll(data.getInNotIgnored()); + unliftedProps.addAll(data.getOutNotIgnored()); + + // create serializers + this.serializers = makeSerializers(gson, data, unliftedProps).toArray(new Serializer[0]); + + // create deserializers + this.deserializers = makeDeserializers(gson, data, unliftedProps).toArray(new Deserializer[0]); + } + + /** + * Creates a complete list of serializers. + * + * @param gson the associated gson object + * @param data data used to configure the serializers + * @param unliftedProps properties that should not be lowered by "any-getters" + * @return a list of all serializers + */ + private List<Serializer> makeSerializers(Gson gson, ClassWalker data, Set<String> unliftedProps) { + List<Serializer> ser = new ArrayList<Serializer>(); + + if (data.getAnyGetter() != null) { + ser.add(new AnyGetterSerializer(gson, unliftedProps, data.getAnyGetter())); + } + + data.getOutProps(Method.class).forEach(method -> ser.add(new MethodSerializer(gson, method))); + + return ser; + } + + /** + * Creates a complete list of deserializers. + * + * @param gson the associated gson object + * @param data data used to configure the deserializers + * @param unliftedProps properties that should not be lifted by "any-setters" + * @return a list of all deserializers + */ + private List<Deserializer> makeDeserializers(Gson gson, ClassWalker data, Set<String> unliftedProps) { + List<Deserializer> deser = new ArrayList<Deserializer>(); + + if (data.getAnySetter() != null) { + deser.add(new AnySetterDeserializer(gson, unliftedProps, data.getAnySetter())); + } + + data.getInProps(Method.class).forEach(method -> deser.add(new MethodDeserializer(gson, method))); + + return deser; + } + + @Override + public void write(JsonWriter out, T value) throws IOException { + JsonElement tree = delegate.toJsonTree(value); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + + // serialize each item from the value into the target tree + for (Serializer serializer : serializers) { + serializer.addToTree(value, jsonObj); + } + } + + elementAdapter.write(out, tree); + } + + @Override + public T read(JsonReader in) throws IOException { + JsonElement tree = elementAdapter.read(in); + + T object = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + + // deserialize each item from the tree into the target object + for (Deserializer dser : deserializers) { + dser.getFromTree(jsonObj, object); + } + } + + return object; + } + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java index b4ef53f7..bb8672bb 100644 --- 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 @@ -86,12 +86,11 @@ public class Adapter { * * @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)); + public Adapter(Gson gson, Method accessor, Type valueType) { + boolean forSetter = (accessor.getReturnType() == void.class); + this.propName = (forSetter ? detmSetterPropName(accessor) : detmGetterPropName(accessor)); this.reader = new ConvInfo(TypeToken.get(valueType)); this.gson = gson; this.fullName = getQualifiedName(accessor); diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/AnyGetterSerializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/AnyGetterSerializer.java new file mode 100644 index 00000000..da9ad173 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/AnyGetterSerializer.java @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.lang.reflect.Method; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Serializer for methods having a JsonAnyGetter annotation. + */ +public class AnyGetterSerializer extends Lifter implements Serializer { + + public static final String NOT_AN_OBJECT_ERR = "expecting a JsonObject for "; + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param propName property name associated with the lifted field + * @param unliftedProps property names that should not be lifted + * @param getter method used to get the item from within an object + */ + public AnyGetterSerializer(Gson gson, Set<String> unliftedProps, Method getter) { + super(gson, unliftedProps, getter, getter.getGenericReturnType()); + } + + @Override + public void addToTree(Object source, JsonObject target) { + // get the value from the object + Object value = invoke(source); + if (value == null) { + // nothing to lift + return; + } + + JsonElement inner = toJsonTree(value); + if (!inner.isJsonObject()) { + throw new JsonParseException(makeError(NOT_AN_OBJECT_ERR)); + } + + // lift items from inner into the target + copyLiftedItems(inner.getAsJsonObject(), target); + } + + /** + * Copies lifted items from one tree into another, without removing them from the + * source tree. + * + * @param source tree from which items are to be copied + * @param target tree into which items are to be copied + */ + private void copyLiftedItems(JsonObject source, JsonObject target) { + for (Entry<String, JsonElement> ent : source.entrySet()) { + String name = ent.getKey(); + if (shouldLift(name)) { + target.add(name, ent.getValue()); + } + } + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/AnySetterDeserializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/AnySetterDeserializer.java new file mode 100644 index 00000000..85d42dfa --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/AnySetterDeserializer.java @@ -0,0 +1,58 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.lang.reflect.Method; +import java.util.Map.Entry; +import java.util.Set; + +/** + * De-serializer for methods having a JsonAnySetter annotation. + */ +public class AnySetterDeserializer extends Lifter implements Deserializer { + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param unliftedProps property names that should not be lifted + * @param getter method used to get the item from within an object + */ + public AnySetterDeserializer(Gson gson, Set<String> unliftedProps, Method setter) { + super(gson, unliftedProps, setter, setter.getGenericParameterTypes()[1]); + } + + @Override + public void getFromTree(JsonObject source, Object target) { + for (Entry<String, JsonElement> ent : source.entrySet()) { + String name = ent.getKey(); + + if (shouldLift(name)) { + Object value = fromJsonTree(ent.getValue()); + invoke(target, name, value); + } + } + } + +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Lifter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Lifter.java new file mode 100644 index 00000000..bb8ec321 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Lifter.java @@ -0,0 +1,62 @@ +/* + * ============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 java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Set; + +/** + * Super class of serializers and de-serializers that deal with "lifted" data, that is, + * data that is lifted from a nested json object into the containing object. + */ +public class Lifter extends MethodAdapter { + + /** + * Names of the properties that are <i>not</i> to be lifted. + */ + private final Set<String> unliftedProps; + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param unliftedProps property names that should not be lifted + * @param accessor method used to access the item from within an object + * @param type the class of value on which this operates + */ + public Lifter(Gson gson, Set<String> unliftedProps, Method accessor, Type type) { + super(gson, accessor, type); + + this.unliftedProps = unliftedProps; + } + + /** + * Determines if a property should be lifted. + * + * @param propName the name of the property + * @return {@code true} if the property should be lifted, {@code false} otherwise + */ + public boolean shouldLift(String propName) { + return !unliftedProps.contains(propName); + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/MethodAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodAdapter.java new file mode 100644 index 00000000..579864da --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodAdapter.java @@ -0,0 +1,69 @@ +/* + * ============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.JsonParseException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Super class of adapters used to serialize and de-serialize a method. + */ +public class MethodAdapter extends Adapter { + + public static final String INVOKE_ERR = "cannot invoke method to serialize/deserialize: "; + + /** + * Method used to access the item within an object. + */ + private final Method accessor; + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param accessor method used to access the item from within an object + * @param type the class of value on which this operates + */ + public MethodAdapter(Gson gson, Method accessor, Type type) { + super(gson, accessor, type); + + this.accessor = accessor; + } + + /** + * Invokes the accessor method. + * + * @param self object on which to invoke the method + * @param args arguments to be passed to the method + * @return the method's result + */ + public Object invoke(Object self, Object... args) { + try { + return accessor.invoke(self, args); + + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new JsonParseException(makeError(INVOKE_ERR), e); + } + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/MethodDeserializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodDeserializer.java new file mode 100644 index 00000000..bb0724eb --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodDeserializer.java @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.lang.reflect.Method; + +/** + * De-serializer for methods that are exposed. + */ +public class MethodDeserializer extends MethodAdapter implements Deserializer { + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param setter method used to set the item within an object + */ + public MethodDeserializer(Gson gson, Method setter) { + super(gson, setter, setter.getGenericParameterTypes()[0]); + } + + @Override + public void getFromTree(JsonObject source, Object target) { + JsonElement jsonEl = source.get(getPropName()); + if (jsonEl == null || jsonEl.isJsonNull()) { + return; + } + + invoke(target, fromJsonTree(jsonEl)); + } + +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/MethodSerializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodSerializer.java new file mode 100644 index 00000000..ab544614 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/MethodSerializer.java @@ -0,0 +1,47 @@ +/* + * ============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.JsonObject; +import java.lang.reflect.Method; + +/** + * Serializer for methods that are exposed. + */ +public class MethodSerializer extends MethodAdapter implements Serializer { + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param getter method used to get the item from within an object + */ + public MethodSerializer(Gson gson, Method getter) { + super(gson, getter, getter.getGenericReturnType()); + } + + @Override + public void addToTree(Object source, JsonObject target) { + Object value = invoke(source); + target.add(getPropName(), (value == null ? null : toJsonTree(value))); + } +} |