From 7c97ac2944748ed2d609821f41e3f3e33e0c73b5 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Sat, 8 Mar 2014 20:08:13 +0000 Subject: [PATCH] Updated Gson version to 2.3-SNAPSHOT since this is a new feature. Added support for a magic field GSON_TYPE_ADAPTER in a class. This adapter is automatically invoked if present. The field must be present in the class (not in any super-type), and must be strongly typed as TypeAdapter. --- gson/pom.xml | 2 +- gson/src/main/java/com/google/gson/Gson.java | 2 + .../com/google/gson/internal/$Gson$Types.java | 16 ++ .../bind/FieldTypeAdapterFactory.java | 60 ++++++ .../functional/GsonFieldTypeAdapterTest.java | 188 ++++++++++++++++++ .../google/gson/internal/GsonTypesTest.java | 60 ++++++ 6 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 gson/src/main/java/com/google/gson/internal/bind/FieldTypeAdapterFactory.java create mode 100644 gson/src/test/java/com/google/gson/functional/GsonFieldTypeAdapterTest.java create mode 100644 gson/src/test/java/com/google/gson/internal/GsonTypesTest.java diff --git a/gson/pom.xml b/gson/pom.xml index bac78611..3006c793 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -3,7 +3,7 @@ com.google.code.gson gson jar - 2.2.5-SNAPSHOT + 2.3-SNAPSHOT 2008 Gson diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index a54188a6..b918f123 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -23,6 +23,7 @@ import com.google.gson.internal.Streams; import com.google.gson.internal.bind.ArrayTypeAdapter; import com.google.gson.internal.bind.CollectionTypeAdapterFactory; import com.google.gson.internal.bind.DateTypeAdapter; +import com.google.gson.internal.bind.FieldTypeAdapterFactory; import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeWriter; import com.google.gson.internal.bind.MapTypeAdapterFactory; @@ -237,6 +238,7 @@ public final class Gson { // type adapters for composite and user-defined types factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization)); + factories.add(new FieldTypeAdapterFactory()); factories.add(new ReflectiveTypeAdapterFactory( constructorConstructor, fieldNamingPolicy, excluder)); diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index 9365be74..4adfe166 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -391,6 +391,22 @@ public final class $Gson$Types { } } + /** + * Given a parameterized type A<B,C>, returns B. If the specified type is not + * a generic type, returns null. + */ + public static Type getFirstTypeArgument(Type type) { + try { + if (!(type instanceof ParameterizedType)) return null; + ParameterizedType ptype = (ParameterizedType) type; + Type[] actualTypeArguments = ptype.getActualTypeArguments(); + if (actualTypeArguments.length == 0) return null; + return canonicalize(actualTypeArguments[0]); + } catch (Exception e) { + return null; + } + } + static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { Class declaredByRaw = declaringClassOf(unknown); diff --git a/gson/src/main/java/com/google/gson/internal/bind/FieldTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/FieldTypeAdapterFactory.java new file mode 100644 index 00000000..f83ea77b --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/bind/FieldTypeAdapterFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Google Inc. + * + * 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. + */ + +package com.google.gson.internal.bind; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.reflect.TypeToken; + +/** + * Given a type T, looks for the magic static field named GSON_TYPE_ADAPTER of type + * TypeAdapter<T> and uses it as the default type adapter. + * + * @since 2.3 + */ +public final class FieldTypeAdapterFactory implements TypeAdapterFactory { + private static final String FIELD_ADAPTER_NAME = "GSON_TYPE_ADAPTER"; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public TypeAdapter create(Gson gson, TypeToken targetType) { + Class clazz = targetType.getRawType(); + try { + Field typeAdapterField = clazz.getDeclaredField(FIELD_ADAPTER_NAME); + typeAdapterField.setAccessible(true); + if (!Modifier.isStatic(typeAdapterField.getModifiers())) return null; + Object fieldAdapterValue = typeAdapterField.get(null); + if (fieldAdapterValue != null && fieldAdapterValue instanceof TypeAdapter) { + // We know that the GSON_TYPE_ADAPTER field is of type TypeAdapter. + // However, we need to assert that its type variable TypeAdapter matches + // the target type + Type fieldTypeVariable = $Gson$Types.getFirstTypeArgument(typeAdapterField.getGenericType()); + if (targetType.getType().equals(fieldTypeVariable)) { + return (TypeAdapter) fieldAdapterValue; + } + } + } catch (Exception e) { // ignore1 + } + return null; + } + +} diff --git a/gson/src/test/java/com/google/gson/functional/GsonFieldTypeAdapterTest.java b/gson/src/test/java/com/google/gson/functional/GsonFieldTypeAdapterTest.java new file mode 100644 index 00000000..b208b895 --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/GsonFieldTypeAdapterTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2014 Google Inc. + * + * 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. + */ + +package com.google.gson.functional; + +import java.io.IOException; +import java.lang.reflect.Type; + +import junit.framework.TestCase; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Functional tests for the magic field GSON_TYPE_ADAPTER present in a class. + */ +@SuppressWarnings("unused") +public final class GsonFieldTypeAdapterTest extends TestCase { + + public void testFieldAdapterInvoked() { + String json = new Gson().toJson(new ClassWithFieldAdapter("bar")); + assertEquals("\"fieldAdapter\"", json); + } + + public void testRegisteredAdapterOverridesFieldAdapter() { + TypeAdapter typeAdapter = new TypeAdapter() { + @Override public void write(JsonWriter out, ClassWithFieldAdapter value) throws IOException { + out.value("registeredAdapter"); + } + @Override public ClassWithFieldAdapter read(JsonReader in) throws IOException { + return new ClassWithFieldAdapter(in.nextString()); + } + }; + Gson gson = new GsonBuilder() + .registerTypeAdapter(ClassWithFieldAdapter.class, typeAdapter) + .create(); + String json = gson.toJson(new ClassWithFieldAdapter("abcd")); + assertEquals("\"registeredAdapter\"", json); + } + + /** + * The serializer overrides field adapter, but for deserializer the fieldAdapter is used. + */ + public void testRegisteredSerializerOverridesFieldAdapter() { + JsonSerializer serializer = new JsonSerializer() { + public JsonElement serialize(ClassWithFieldAdapter src, Type typeOfSrc, + JsonSerializationContext context) { + return new JsonPrimitive("registeredSerializer"); + } + }; + Gson gson = new GsonBuilder() + .registerTypeAdapter(ClassWithFieldAdapter.class, serializer) + .create(); + String json = gson.toJson(new ClassWithFieldAdapter("abcd")); + assertEquals("\"registeredSerializer\"", json); + ClassWithFieldAdapter target = gson.fromJson("abcd", ClassWithFieldAdapter.class); + assertEquals("fieldAdapter", target.value); + } + + /** + * The deserializer overrides field adapter, but for serializer the fieldAdapter is used. + */ + public void testRegisteredDeserializerOverridesFieldAdapter() { + JsonDeserializer deserializer = new JsonDeserializer() { + public ClassWithFieldAdapter deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + return new ClassWithFieldAdapter("registeredDeserializer"); + } + }; + Gson gson = new GsonBuilder() + .registerTypeAdapter(ClassWithFieldAdapter.class, deserializer) + .create(); + String json = gson.toJson(new ClassWithFieldAdapter("abcd")); + assertEquals("\"fieldAdapter\"", json); + ClassWithFieldAdapter target = gson.fromJson("abcd", ClassWithFieldAdapter.class); + assertEquals("registeredDeserializer", target.value); + } + + public void testFieldAdapterNotInvokedIfNull() { + String json = new Gson().toJson(new ClassWithNullFieldAdapter("bar")); + assertEquals("{\"value\":\"bar\"}", json); + } + + public void testNonStaticFieldAdapterNotInvoked() { + String json = new Gson().toJson(new ClassWithNonStaticFieldAdapter("bar")); + assertFalse(json.contains("fieldAdapter")); + } + + public void testIncorrectTypeAdapterNotInvoked() { + String json = new Gson().toJson(new ClassWithIncorrectFieldAdapter("bar")); + assertFalse(json.contains("fieldAdapter")); + } + + public void testSuperclassTypeAdapterNotInvoked() { + String json = new Gson().toJson(new ClassWithSuperClassFieldAdapter("bar")); + assertFalse(json.contains("fieldAdapter")); + } + + private static class ClassWithFieldAdapter { + final String value; + ClassWithFieldAdapter(String value) { + this.value = value; + } + private static final TypeAdapter GSON_TYPE_ADAPTER = + new TypeAdapter() { + @Override public void write(JsonWriter out, ClassWithFieldAdapter value) throws IOException { + out.value("fieldAdapter"); + } + @Override public ClassWithFieldAdapter read(JsonReader in) throws IOException { + in.nextString(); + return new ClassWithFieldAdapter("fieldAdapter"); + } + }; + } + + private static final class ClassWithSuperClassFieldAdapter extends ClassWithFieldAdapter { + ClassWithSuperClassFieldAdapter(String value) { + super(value); + } + } + + private static final class ClassWithNullFieldAdapter { + final String value; + ClassWithNullFieldAdapter(String value) { + this.value = value; + } + private static final TypeAdapter GSON_TYPE_ADAPTER = null; + } + + private static final class ClassWithNonStaticFieldAdapter { + final String value; + ClassWithNonStaticFieldAdapter(String value) { + this.value = value; + } + private final TypeAdapter GSON_TYPE_ADAPTER = + new TypeAdapter() { + @Override public void write(JsonWriter out, ClassWithNonStaticFieldAdapter value) throws IOException { + out.value("fieldAdapter"); + } + @Override public ClassWithNonStaticFieldAdapter read(JsonReader in) throws IOException { + in.nextString(); + return new ClassWithNonStaticFieldAdapter("fieldAdapter"); + } + }; + } + + private static final class ClassWithIncorrectFieldAdapter { + final String value; + ClassWithIncorrectFieldAdapter(String value) { + this.value = value; + } + // Note that the type is NOT TypeAdapter so this + // field should be ignored. + private static final TypeAdapter GSON_TYPE_ADAPTER = + new TypeAdapter() { + @Override public void write(JsonWriter out, ClassWithFieldAdapter value) throws IOException { + out.value("fieldAdapter"); + } + @Override public ClassWithFieldAdapter read(JsonReader in) throws IOException { + in.nextString(); + return new ClassWithFieldAdapter("fieldAdapter"); + } + }; + } +} diff --git a/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java b/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java new file mode 100644 index 00000000..8087909a --- /dev/null +++ b/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Google Inc. + * + * 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. + */ + +package com.google.gson.internal; + +import java.lang.reflect.Type; +import java.util.List; + +import junit.framework.TestCase; + +public final class GsonTypesTest extends TestCase { + + public void testNewParameterizedTypeWithoutOwner() { + // List. List is a top-level class + Type type = $Gson$Types.newParameterizedTypeWithOwner(null, List.class, A.class); + assertEquals(A.class, $Gson$Types.getFirstTypeArgument(type)); + + // A. A is a static inner class. + type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, B.class); + assertEquals(B.class, $Gson$Types.getFirstTypeArgument(type)); + + final class D { + } + try { + // D is not allowed since D is not a static inner class + $Gson$Types.newParameterizedTypeWithOwner(null, D.class, A.class); + } catch (IllegalArgumentException expected) {} + + // A is allowed. + type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, D.class); + assertEquals(D.class, $Gson$Types.getFirstTypeArgument(type)); + } + + public void testGetFirstTypeArgument() { + assertNull($Gson$Types.getFirstTypeArgument(A.class)); + + Type type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, B.class, C.class); + assertEquals(B.class, $Gson$Types.getFirstTypeArgument(type)); + } + + private static final class A { + } + private static final class B { + } + private static final class C { + } +}