Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Adapter Layering Feature #1471

Closed
wants to merge 12 commits into from
6 changes: 4 additions & 2 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public final class Gson {
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();

private final ConstructorConstructor constructorConstructor;
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;

final List<TypeAdapterFactory> factories;

Expand Down Expand Up @@ -895,7 +895,9 @@ public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
JsonReader jsonReader = newJsonReader(json);
T object = (T) fromJson(jsonReader, typeOfT);
assertFullConsumption(object, jsonReader);
if (!ReflectiveTypeAdapterFactory.Adapter.class.isAssignableFrom(getAdapter(TypeToken.get(typeOfT)).getClass())) {
assertFullConsumption(object, jsonReader);
}
return object;
}

Expand Down
76 changes: 61 additions & 15 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@

package com.google.gson;

import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS;
import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
import static com.google.gson.Gson.DEFAULT_LENIENT;
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;

import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.TreeTypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
Expand All @@ -25,21 +42,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.TreeTypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;

import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS;
import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
import static com.google.gson.Gson.DEFAULT_LENIENT;
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import java.util.function.Function;

/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
Expand Down Expand Up @@ -511,6 +514,49 @@ public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
return this;
}

/**
* Configures Gson for custom serialization and deserialization with Fill-in. Takes the type
* adapter, and wraps the result from the {@link TypeAdapter} into a {@link ReflectiveTypeAdapterFactory.Adapter}. Like
* {@link #registerTypeAdapter(Type, Object)}, it only applies to the type specified by
* the {@code type} parameter.
*
* @param baseType the type definition for the type adapter being registered
* @param objectAdapter This object must implement the {@link TypeAdapter} class
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder registerTypeAdapterWithFillIn(Type baseType, Object objectAdapter) {
$Gson$Preconditions.checkArgument(objectAdapter instanceof TypeAdapter<?>);
TypeAdapter<?> typeAdapter = (TypeAdapter<?>) objectAdapter;

factories.add(new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Function<JsonReader, T> function = new Function<JsonReader, T>() {
@Override
public T apply(JsonReader reader) {
try {
T returnedObject = (T) typeAdapter.read(reader);
reader.reset();
return returnedObject;
} catch (IOException e) {
throw new JsonIOException("Unable to mark stream: your JVM does not support stream marking.");
// Another cause for exception was that mark was not supported
}
}
};
ConstructorConstructor constructorConstructor = new ConstructorConstructor(type, function);
List<Type> typeList = new ArrayList<Type>();
typeList.add(baseType);
ReflectiveTypeAdapterFactory reflectiveTypeAdapterFactory =
new ReflectiveTypeAdapterFactory(constructorConstructor, gson.fieldNamingStrategy,
gson.excluder, gson.jsonAdapterFactory, typeList);
return reflectiveTypeAdapterFactory.create(gson, type);
}
});

return this;
}

/**
* Register a factory for type adapters. Registering a factory is useful when the type
* adapter needs to be configured based on the type of the field being processed. Gson
Expand Down
12 changes: 12 additions & 0 deletions gson/src/main/java/com/google/gson/InstanceCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gson;

import com.google.gson.stream.JsonReader;
import java.lang.reflect.Type;

/**
Expand Down Expand Up @@ -89,4 +90,15 @@ public interface InstanceCreator<T> {
* @return a default object instance of type T.
*/
public T createInstance(Type type);

/**
* If not defined, defaults to returning the result of {@link #createInstance}. This method is
* designed to help with creating Adapters with Fill-In. See {@link GsonBuilder#registerTypeAdapterFactory}.
* @param type the parameterized T represented as a {@link Type}.
* @param in the JsonReader from which to create the instance.
* @return a default object instance of type T.
*/
default T createInstance(Type type, JsonReader in) {
return createInstance(type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gson.internal;

import com.google.gson.stream.JsonReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
Expand All @@ -24,6 +25,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
Expand All @@ -42,6 +44,7 @@
import com.google.gson.JsonIOException;
import com.google.gson.internal.reflect.ReflectionAccessor;
import com.google.gson.reflect.TypeToken;
import java.util.function.Function;

/**
* Returns a function that can construct an instance of a requested type.
Expand All @@ -54,6 +57,30 @@ public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
this.instanceCreators = instanceCreators;
}

/** Constructor for the Fill-In mechanic implemented with {@link com.google.gson.GsonBuilder#registerTypeAdapterWithFillIn}.
* @param typeToken represents the type of the object to be returned.
* @param objectCreator function that returns the object that is to be contstructed.
* @param <T> returns an instance of T that is returned by the {@code objectCreator}.
*/
public <T> ConstructorConstructor(TypeToken<T> typeToken, Function<JsonReader, T> objectCreator) {
InstanceCreator<?> instanceCreator = new InstanceCreator<T>() {
@Override
public T createInstance(Type type) {
return null;
}

@Override
public T createInstance(Type type, JsonReader in) {
return objectCreator.apply(in);
}
};
// Using HashMap for a concrete implementation of the Map Abstract class
Map<Type, InstanceCreator<?>> instanceCreatorMap = new HashMap<Type, InstanceCreator<?>>();
instanceCreatorMap.put(typeToken.getType(), instanceCreator);
this.instanceCreators = instanceCreatorMap;
}


public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
final Type type = typeToken.getType();
final Class<? super T> rawType = typeToken.getRawType();
Expand All @@ -67,6 +94,10 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
@Override public T construct() {
return typeCreator.createInstance(type);
}

@Override public T construct(JsonReader in) {
return typeCreator.createInstance(type, in);
}
};
}

Expand All @@ -79,6 +110,10 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
@Override public T construct() {
return rawTypeCreator.createInstance(type);
}

@Override public T construct(JsonReader in) {
return rawTypeCreator.createInstance(type, in);
}
};
}

Expand Down
12 changes: 12 additions & 0 deletions gson/src/main/java/com/google/gson/internal/ObjectConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.google.gson.internal;

import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonReader;

/**
* Defines a generic object construction factory. The purpose of this class
* is to construct a default instance of a class that can be used for object
Expand All @@ -30,4 +33,13 @@ public interface ObjectConstructor<T> {
* Returns a new instance.
*/
public T construct();

/** If not defined, returns the result of the {@link #construct} method. This method is designed
* to create an object of class T to suport the Fill-In mechanic {@link GsonBuilder#registerTypeAdapterFactory}.
* @param in is the JsonReader from which the object should be constructed.
* @return returns the object of class T that was constructed based on the {@code in} stream.
*/
default T construct(JsonReader in) {
return construct();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final Excluder excluder;
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();
// Helps with restricting the Adapter only to certain types for the Fill-In mechanic.
private final List<Type> typeIncluder;

public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
Expand All @@ -59,6 +61,18 @@ public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructo
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
this.jsonAdapterFactory = jsonAdapterFactory;
this.typeIncluder = null;
}

public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
List<Type> typeIncluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
this.jsonAdapterFactory = jsonAdapterFactory;
this.typeIncluder = typeIncluder;
}

public boolean excludeField(Field f, boolean serialize) {
Expand Down Expand Up @@ -95,11 +109,22 @@ private List<String> getFieldNames(Field f) {
Class<? super T> raw = type.getRawType();

if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
return null; // it's a primitive!
}

if (typeIncluder == null) {
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}

for (Type t : typeIncluder) {
if (t.equals(type.getType())) {
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
}

ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
return null;
}

private ReflectiveTypeAdapterFactory.BoundField createBoundField(
Expand Down Expand Up @@ -209,7 +234,7 @@ public static final class Adapter<T> extends TypeAdapter<T> {
return null;
}

T instance = constructor.construct();
T instance = constructor.construct(in);

try {
in.beginObject();
Expand Down
26 changes: 26 additions & 0 deletions gson/src/main/java/com/google/gson/stream/JsonReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ public JsonReader(Reader in) {
throw new NullPointerException("in == null");
}
this.in = in;
try {
in.mark(Integer.MAX_VALUE);
} catch (Exception e) {
// Do nothing
}
}

/**
Expand Down Expand Up @@ -1491,6 +1496,27 @@ public String getPath() {
return result.toString();
}


/** Resets the {@code JsonReader} to the beginning of the stream. To be used to restore the
* previous mark at the beginning of the stream.
*
* @throws IOException
*/
public void reset() throws IOException {
in.reset();

peeked = 0;

pos = 0;
lineNumber = 0;
lineStart = 0;

stack = new int[32];
stackSize = 1;

fillBuffer(limit);
}

/**
* Unescapes the character identified by the character or characters that
* immediately follow a backslash. The backslash '\' should have already
Expand Down