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<T>.
This commit is contained in:
Inderjeet Singh 2014-03-08 20:08:13 +00:00
parent b2a9d872db
commit 7c97ac2944
6 changed files with 327 additions and 1 deletions

View File

@ -3,7 +3,7 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<packaging>jar</packaging>
<version>2.2.5-SNAPSHOT</version>
<version>2.3-SNAPSHOT</version>
<inceptionYear>2008</inceptionYear>
<name>Gson</name>
<parent>

View File

@ -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));

View File

@ -391,6 +391,22 @@ public final class $Gson$Types {
}
}
/**
* Given a parameterized type A&lt;B,C&gt;, 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);

View File

@ -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&lt;T&gt; 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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> 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<TypeVariable> 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;
}
}

View File

@ -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<ClassWithFieldAdapter> typeAdapter = new TypeAdapter<ClassWithFieldAdapter>() {
@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<ClassWithFieldAdapter> serializer = new JsonSerializer<ClassWithFieldAdapter>() {
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<ClassWithFieldAdapter> deserializer = new JsonDeserializer<ClassWithFieldAdapter>() {
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<ClassWithFieldAdapter> GSON_TYPE_ADAPTER =
new TypeAdapter<GsonFieldTypeAdapterTest.ClassWithFieldAdapter>() {
@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<ClassWithFieldAdapter> GSON_TYPE_ADAPTER = null;
}
private static final class ClassWithNonStaticFieldAdapter {
final String value;
ClassWithNonStaticFieldAdapter(String value) {
this.value = value;
}
private final TypeAdapter<ClassWithNonStaticFieldAdapter> GSON_TYPE_ADAPTER =
new TypeAdapter<ClassWithNonStaticFieldAdapter>() {
@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<ClassWithIncorrectFieldAdapter> so this
// field should be ignored.
private static final TypeAdapter<ClassWithFieldAdapter> GSON_TYPE_ADAPTER =
new TypeAdapter<GsonFieldTypeAdapterTest.ClassWithFieldAdapter>() {
@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");
}
};
}
}

View File

@ -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<A>. List is a top-level class
Type type = $Gson$Types.newParameterizedTypeWithOwner(null, List.class, A.class);
assertEquals(A.class, $Gson$Types.getFirstTypeArgument(type));
// A<B>. 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<A> is not allowed since D is not a static inner class
$Gson$Types.newParameterizedTypeWithOwner(null, D.class, A.class);
} catch (IllegalArgumentException expected) {}
// A<D> 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 {
}
}