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 super T> 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 {
+ }
+}