Commit to factories as the mechanism to lookup type adapters. This uses factories for type hierarchy adapters. We keep a separate list of factories for tree-style adapters registered with registerTypeHierarchyAdapter to guarantee that these come after the non-hierarchy adapters.
This drops support for type hierarchy instance creators. I don't expect this to be a problem. We'll also detect fewer errors where multiple type adapters can serialize the same type. With APIs like getNextTypeAdapter, I think this might actually be an improvement!
This commit is contained in:
parent
3cbe355cb6
commit
1794182a56
6
gson/Gson 2.1 notes.txt
Normal file
6
gson/Gson 2.1 notes.txt
Normal file
@ -0,0 +1,6 @@
|
||||
Dropped support for GsonBuilder.registerTypeHierarchyAdapter+InstanceCreator
|
||||
|
||||
Relax registerTypeHierarchyAdapter order
|
||||
Gson 2.0 failed if you registered Manager then Employee would fail
|
||||
Gson 2.1 it isn't a problem
|
||||
com.google.gson.functional.TypeHierarchyAdapterTest#testRegisterSubTypeFirstNotAllowed
|
@ -20,7 +20,6 @@ import com.google.gson.internal.ConstructorConstructor;
|
||||
import com.google.gson.internal.Excluder;
|
||||
import com.google.gson.internal.Primitives;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.internal.TypeMap;
|
||||
import com.google.gson.internal.bind.ArrayTypeAdapter;
|
||||
import com.google.gson.internal.bind.BigDecimalTypeAdapter;
|
||||
import com.google.gson.internal.bind.BigIntegerTypeAdapter;
|
||||
@ -97,9 +96,6 @@ import java.util.Map;
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
public final class Gson {
|
||||
@SuppressWarnings("rawtypes")
|
||||
static final TypeMap EMPTY_MAP = new TypeMap().makeUnmodifiable();
|
||||
|
||||
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
|
||||
|
||||
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
|
||||
@ -121,12 +117,6 @@ public final class Gson {
|
||||
private final List<TypeAdapter.Factory> factories;
|
||||
private final ConstructorConstructor constructorConstructor;
|
||||
|
||||
/** Map containing Type or Class objects as keys */
|
||||
private final TypeMap<JsonSerializer<?>> serializers;
|
||||
|
||||
/** Map containing Type or Class objects as keys */
|
||||
private final TypeMap<JsonDeserializer<?>> deserializers;
|
||||
|
||||
private final boolean serializeNulls;
|
||||
private final boolean htmlSafe;
|
||||
private final boolean generateNonExecutableJson;
|
||||
@ -184,23 +174,19 @@ public final class Gson {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Gson() {
|
||||
this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY,
|
||||
EMPTY_MAP, false, EMPTY_MAP, EMPTY_MAP, false, DEFAULT_JSON_NON_EXECUTABLE, true,
|
||||
false, false, LongSerializationPolicy.DEFAULT,
|
||||
Collections.<Type, InstanceCreator<?>>emptyMap(), false, false, DEFAULT_JSON_NON_EXECUTABLE,
|
||||
true, false, false, LongSerializationPolicy.DEFAULT,
|
||||
Collections.<TypeAdapter.Factory>emptyList());
|
||||
}
|
||||
|
||||
Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy,
|
||||
final TypeMap<InstanceCreator<?>> instanceCreators, boolean serializeNulls,
|
||||
final TypeMap<JsonSerializer<?>> serializers,
|
||||
final TypeMap<JsonDeserializer<?>> deserializers,
|
||||
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
|
||||
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
|
||||
boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
|
||||
LongSerializationPolicy longSerializationPolicy,
|
||||
List<TypeAdapter.Factory> typeAdapterFactories) {
|
||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
|
||||
this.serializeNulls = serializeNulls;
|
||||
this.serializers = serializers;
|
||||
this.deserializers = deserializers;
|
||||
this.generateNonExecutableJson = generateNonExecutableGson;
|
||||
this.htmlSafe = htmlSafe;
|
||||
this.prettyPrinting = prettyPrinting;
|
||||
@ -235,7 +221,6 @@ public final class Gson {
|
||||
factories.add(factory);
|
||||
}
|
||||
|
||||
factories.add(new TreeTypeAdapter.TypeHierarchyFactory(serializers, deserializers));
|
||||
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
|
||||
factories.add(TypeAdapters.URL_FACTORY);
|
||||
factories.add(TypeAdapters.URI_FACTORY);
|
||||
@ -852,9 +837,7 @@ public final class Gson {
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("{")
|
||||
.append("serializeNulls:").append(serializeNulls)
|
||||
.append(",serializers:").append(serializers)
|
||||
.append(",deserializers:").append(deserializers)
|
||||
|
||||
.append("factories:").append(factories)
|
||||
// using the name instanceCreator instead of ObjectConstructor since the users of Gson are
|
||||
// more familiar with the concept of Instance Creators. Moreover, the objectConstructor is
|
||||
// just a utility class around instance creators, and its toString() only displays them.
|
||||
|
@ -19,7 +19,6 @@ package com.google.gson;
|
||||
import com.google.gson.internal.$Gson$Preconditions;
|
||||
import com.google.gson.internal.Excluder;
|
||||
import com.google.gson.internal.Primitives;
|
||||
import com.google.gson.internal.TypeMap;
|
||||
import com.google.gson.internal.bind.TypeAdapters;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
@ -27,7 +26,9 @@ import java.sql.Timestamp;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
|
||||
@ -64,21 +65,22 @@ import java.util.List;
|
||||
*/
|
||||
public final class GsonBuilder {
|
||||
private Excluder excluder = Excluder.DEFAULT;
|
||||
|
||||
private LongSerializationPolicy longSerializationPolicy;
|
||||
private FieldNamingStrategy fieldNamingPolicy;
|
||||
private final TypeMap<InstanceCreator<?>> instanceCreators;
|
||||
private final TypeMap<JsonSerializer<?>> serializers;
|
||||
private final TypeMap<JsonDeserializer<?>> deserializers;
|
||||
private final List<TypeAdapter.Factory> typeAdapterFactories
|
||||
private LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT;
|
||||
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
|
||||
private final Map<Type, InstanceCreator<?>> instanceCreators
|
||||
= new HashMap<Type, InstanceCreator<?>>();
|
||||
private final List<TypeAdapter.Factory> factories
|
||||
= new ArrayList<TypeAdapter.Factory>();
|
||||
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
|
||||
private final List<TypeAdapter.Factory> hierarchyFactories
|
||||
= new ArrayList<TypeAdapter.Factory>();
|
||||
private boolean serializeNulls;
|
||||
private String datePattern;
|
||||
private int dateStyle;
|
||||
private int timeStyle;
|
||||
private boolean complexMapKeySerialization = false;
|
||||
private int dateStyle = DateFormat.DEFAULT;
|
||||
private int timeStyle = DateFormat.DEFAULT;
|
||||
private boolean complexMapKeySerialization;
|
||||
private boolean serializeSpecialFloatingPointValues;
|
||||
private boolean escapeHtmlChars;
|
||||
private boolean escapeHtmlChars = true;
|
||||
private boolean prettyPrinting;
|
||||
private boolean generateNonExecutableJson;
|
||||
|
||||
@ -89,24 +91,11 @@ public final class GsonBuilder {
|
||||
* {@link #create()}.
|
||||
*/
|
||||
public GsonBuilder() {
|
||||
// setup default values
|
||||
prettyPrinting = false;
|
||||
escapeHtmlChars = true;
|
||||
longSerializationPolicy = LongSerializationPolicy.DEFAULT;
|
||||
fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
|
||||
instanceCreators = new TypeMap<InstanceCreator<?>>();
|
||||
serializers = new TypeMap<JsonSerializer<?>>();
|
||||
deserializers = new TypeMap<JsonDeserializer<?>>();
|
||||
serializeNulls = false;
|
||||
dateStyle = DateFormat.DEFAULT;
|
||||
timeStyle = DateFormat.DEFAULT;
|
||||
serializeSpecialFloatingPointValues = false;
|
||||
generateNonExecutableJson = false;
|
||||
}
|
||||
|
||||
// TODO: nice documentation
|
||||
public GsonBuilder registerTypeAdapterFactory(TypeAdapter.Factory factory) {
|
||||
typeAdapterFactories.add(factory);
|
||||
factories.add(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -472,7 +461,7 @@ public final class GsonBuilder {
|
||||
}
|
||||
if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
|
||||
TypeToken<?> typeToken = TypeToken.get(type);
|
||||
typeAdapterFactories.add(new TreeTypeAdapter.SingleTypeFactory(typeToken, typeAdapter));
|
||||
factories.add(TreeTypeAdapter.newFactory(typeToken, typeAdapter));
|
||||
}
|
||||
if (typeAdapter instanceof TypeAdapter<?>) {
|
||||
typeAdapter(TypeToken.get(type), (TypeAdapter)typeAdapter);
|
||||
@ -482,7 +471,7 @@ public final class GsonBuilder {
|
||||
|
||||
// TODO: inline this method?
|
||||
private <T> GsonBuilder typeAdapter(TypeToken<T> type, TypeAdapter<T> typeAdapter) {
|
||||
typeAdapterFactories.add(TypeAdapters.newFactory(type, typeAdapter));
|
||||
factories.add(TypeAdapters.newFactory(type, typeAdapter));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -499,7 +488,7 @@ public final class GsonBuilder {
|
||||
*/
|
||||
private <T> GsonBuilder registerInstanceCreator(Type typeOfT,
|
||||
InstanceCreator<? extends T> instanceCreator) {
|
||||
instanceCreators.register(typeOfT, instanceCreator);
|
||||
instanceCreators.put(typeOfT, instanceCreator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -523,16 +512,11 @@ public final class GsonBuilder {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
|
||||
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|
||||
|| typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof InstanceCreator<?>
|
||||
|| typeAdapter instanceof JsonDeserializer<?>
|
||||
|| typeAdapter instanceof TypeAdapter<?>);
|
||||
if (typeAdapter instanceof InstanceCreator<?>) {
|
||||
registerInstanceCreatorForTypeHierarchy(baseType, (InstanceCreator<?>) typeAdapter);
|
||||
}
|
||||
if (typeAdapter instanceof JsonSerializer<?>) {
|
||||
registerSerializerForTypeHierarchy(baseType, (JsonSerializer<?>) typeAdapter);
|
||||
}
|
||||
if (typeAdapter instanceof JsonDeserializer<?>) {
|
||||
registerDeserializerForTypeHierarchy(baseType, (JsonDeserializer<?>) typeAdapter);
|
||||
if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
|
||||
hierarchyFactories.add(0,
|
||||
TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
|
||||
}
|
||||
if (typeAdapter instanceof TypeAdapter<?>) {
|
||||
typeHierarchyAdapter(baseType, (TypeAdapter)typeAdapter);
|
||||
@ -542,26 +526,7 @@ public final class GsonBuilder {
|
||||
|
||||
// TODO: inline this method?
|
||||
private <T> GsonBuilder typeHierarchyAdapter(Class<T> type, TypeAdapter<T> typeAdapter) {
|
||||
typeAdapterFactories.add(TypeAdapters.newTypeHierarchyFactory(type, typeAdapter));
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T> GsonBuilder registerInstanceCreatorForTypeHierarchy(Class<?> classOfT,
|
||||
InstanceCreator<? extends T> instanceCreator) {
|
||||
instanceCreators.registerForTypeHierarchy(classOfT, instanceCreator);
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T> GsonBuilder registerSerializerForTypeHierarchy(Class<?> classOfT,
|
||||
JsonSerializer<T> serializer) {
|
||||
serializers.registerForTypeHierarchy(classOfT, serializer);
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T> GsonBuilder registerDeserializerForTypeHierarchy(Class<?> classOfT,
|
||||
JsonDeserializer<T> deserializer) {
|
||||
deserializers.registerForTypeHierarchy(classOfT,
|
||||
new JsonDeserializerExceptionWrapper<T>(deserializer));
|
||||
factories.add(TypeAdapters.newTypeHierarchyFactory(type, typeAdapter));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -597,37 +562,30 @@ public final class GsonBuilder {
|
||||
* @return an instance of Gson configured with the options currently set in this builder
|
||||
*/
|
||||
public Gson create() {
|
||||
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, serializers, deserializers);
|
||||
List<TypeAdapter.Factory> factories = new ArrayList<TypeAdapter.Factory>();
|
||||
factories.addAll(this.factories);
|
||||
factories.addAll(this.hierarchyFactories);
|
||||
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
||||
|
||||
return new Gson(excluder, fieldNamingPolicy, instanceCreators.copyOf().makeUnmodifiable(),
|
||||
serializeNulls, serializers.copyOf().makeUnmodifiable(),
|
||||
deserializers.copyOf().makeUnmodifiable(), complexMapKeySerialization,
|
||||
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
|
||||
serializeNulls, complexMapKeySerialization,
|
||||
generateNonExecutableJson, escapeHtmlChars, prettyPrinting,
|
||||
serializeSpecialFloatingPointValues, longSerializationPolicy, typeAdapterFactories);
|
||||
serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
|
||||
}
|
||||
|
||||
private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
|
||||
TypeMap<JsonSerializer<?>> serializers, TypeMap<JsonDeserializer<?>> deserializers) {
|
||||
DefaultDateTypeAdapter dateTypeAdapter = null;
|
||||
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
|
||||
List<TypeAdapter.Factory> factories) {
|
||||
DefaultDateTypeAdapter dateTypeAdapter;
|
||||
if (datePattern != null && !"".equals(datePattern.trim())) {
|
||||
dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
|
||||
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
|
||||
dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dateTypeAdapter != null) {
|
||||
registerIfAbsent(Date.class, serializers, dateTypeAdapter);
|
||||
registerIfAbsent(Date.class, deserializers, dateTypeAdapter);
|
||||
registerIfAbsent(Timestamp.class, serializers, dateTypeAdapter);
|
||||
registerIfAbsent(Timestamp.class, deserializers, dateTypeAdapter);
|
||||
registerIfAbsent(java.sql.Date.class, serializers, dateTypeAdapter);
|
||||
registerIfAbsent(java.sql.Date.class, deserializers, dateTypeAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void registerIfAbsent(Class<?> type, TypeMap<T> adapters, T adapter) {
|
||||
if (!adapters.hasSpecificHandlerFor(type)) {
|
||||
adapters.register(type, adapter);
|
||||
}
|
||||
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter));
|
||||
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter));
|
||||
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter));
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import com.google.gson.internal.$Gson$Preconditions;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Decorators a {@code JsonDeserializer} instance with exception handling. This wrapper class
|
||||
* ensures that a {@code JsonDeserializer} will not propagate any exception other than a
|
||||
* {@link JsonParseException}.
|
||||
*
|
||||
* @param <T> type of the deserializer being wrapped.
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
final class JsonDeserializerExceptionWrapper<T> implements JsonDeserializer<T> {
|
||||
private final JsonDeserializer<T> delegate;
|
||||
|
||||
/**
|
||||
* Returns a wrapped {@link JsonDeserializer} object that has been decorated with
|
||||
* {@link JsonParseException} handling.
|
||||
*
|
||||
* @param delegate the {@code JsonDeserializer} instance to be wrapped.
|
||||
* @throws IllegalArgumentException if {@code delegate} is {@code null}.
|
||||
*/
|
||||
JsonDeserializerExceptionWrapper(JsonDeserializer<T> delegate) {
|
||||
this.delegate = $Gson$Preconditions.checkNotNull(delegate);
|
||||
}
|
||||
|
||||
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
try {
|
||||
return delegate.deserialize(json, typeOfT, context);
|
||||
} catch (JsonParseException e) {
|
||||
// just rethrow the exception
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
// rethrow as a JsonParseException
|
||||
StringBuilder errorMsg = new StringBuilder()
|
||||
.append("The JsonDeserializer ")
|
||||
.append(delegate)
|
||||
.append(" failed to deserialize json object ")
|
||||
.append(json)
|
||||
.append(" given the type ")
|
||||
.append(typeOfT);
|
||||
throw new JsonParseException(errorMsg.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
@ -18,12 +18,10 @@ package com.google.gson;
|
||||
|
||||
import com.google.gson.internal.$Gson$Preconditions;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.internal.TypeMap;
|
||||
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.Type;
|
||||
|
||||
/**
|
||||
* Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the
|
||||
@ -80,13 +78,21 @@ final class TreeTypeAdapter<T> extends TypeAdapter<T> {
|
||||
: (delegate = gson.getNextAdapter(skipPast, typeToken));
|
||||
}
|
||||
|
||||
public static class SingleTypeFactory implements TypeAdapter.Factory {
|
||||
private final TypeToken<?> typeToken;
|
||||
public static Factory newFactory(TypeToken<?> exactType, Object typeAdapter) {
|
||||
return new SingleTypeFactory(typeAdapter, exactType, null);
|
||||
}
|
||||
|
||||
public static Factory newTypeHierarchyFactory(Class<?> hierarchyType, Object typeAdapter) {
|
||||
return new SingleTypeFactory(typeAdapter, null, hierarchyType);
|
||||
}
|
||||
|
||||
private static class SingleTypeFactory implements TypeAdapter.Factory {
|
||||
private final TypeToken<?> exactType;
|
||||
private final Class<?> hierarchyType;
|
||||
private final JsonSerializer<?> serializer;
|
||||
private final JsonDeserializer<?> deserializer;
|
||||
|
||||
public SingleTypeFactory(TypeToken<?> typeToken, Object typeAdapter) {
|
||||
this.typeToken = typeToken;
|
||||
private SingleTypeFactory(Object typeAdapter, TypeToken<?> exactType, Class<?> hierarchyType) {
|
||||
serializer = typeAdapter instanceof JsonSerializer
|
||||
? (JsonSerializer) typeAdapter
|
||||
: null;
|
||||
@ -94,35 +100,19 @@ final class TreeTypeAdapter<T> extends TypeAdapter<T> {
|
||||
? (JsonDeserializer) typeAdapter
|
||||
: null;
|
||||
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
|
||||
this.exactType = exactType;
|
||||
this.hierarchyType = hierarchyType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call
|
||||
public <T> TypeAdapter<T> create(Gson context, TypeToken<T> type) {
|
||||
return typeToken.equals(type)
|
||||
boolean matches = exactType != null
|
||||
? exactType.equals(type)
|
||||
: hierarchyType.isAssignableFrom(type.getRawType());
|
||||
return matches
|
||||
? new TreeTypeAdapter<T>((JsonSerializer<T>) serializer,
|
||||
(JsonDeserializer<T>) deserializer, context, type, this)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TypeHierarchyFactory implements TypeAdapter.Factory {
|
||||
private final TypeMap<JsonSerializer<?>> serializers;
|
||||
private final TypeMap<JsonDeserializer<?>> deserializers;
|
||||
|
||||
public TypeHierarchyFactory(TypeMap<JsonSerializer<?>> serializers,
|
||||
TypeMap<JsonDeserializer<?>> deserializers) {
|
||||
this.serializers = serializers;
|
||||
this.deserializers = deserializers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // guaranteed by serializers lookup matching type
|
||||
public <T> TypeAdapter<T> create(Gson context, TypeToken<T> typeToken) {
|
||||
Type type = typeToken.getType();
|
||||
JsonSerializer<T> serializer = (JsonSerializer<T>) serializers.getHandlerFor(type);
|
||||
JsonDeserializer<T> deserializer = (JsonDeserializer<T>) deserializers.getHandlerFor(type);
|
||||
return (serializer != null || deserializer != null)
|
||||
? new TreeTypeAdapter<T>(serializer, deserializer, context, typeToken, this)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
@ -36,14 +37,14 @@ import java.util.TreeSet;
|
||||
* Returns a function that can construct an instance of a requested type.
|
||||
*/
|
||||
public final class ConstructorConstructor {
|
||||
private final TypeMap<InstanceCreator<?>> instanceCreators;
|
||||
private final Map<Type, InstanceCreator<?>> instanceCreators;
|
||||
|
||||
public ConstructorConstructor(TypeMap<InstanceCreator<?>> instanceCreators) {
|
||||
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
|
||||
this.instanceCreators = instanceCreators;
|
||||
}
|
||||
|
||||
public ConstructorConstructor() {
|
||||
this(new TypeMap<InstanceCreator<?>>());
|
||||
this(Collections.<Type, InstanceCreator<?>>emptyMap());
|
||||
}
|
||||
|
||||
public <T> ObjectConstructor<T> getConstructor(TypeToken<T> typeToken) {
|
||||
@ -53,8 +54,7 @@ public final class ConstructorConstructor {
|
||||
// first try an instance creator
|
||||
|
||||
@SuppressWarnings("unchecked") // types must agree
|
||||
final InstanceCreator<T> creator
|
||||
= (InstanceCreator<T>) instanceCreators.getHandlerFor(type);
|
||||
final InstanceCreator<T> creator = (InstanceCreator<T>) instanceCreators.get(type);
|
||||
if (creator != null) {
|
||||
return new ObjectConstructor<T>() {
|
||||
public T construct() {
|
||||
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A map that provides ability to associate handlers for a specific type or all
|
||||
* of its sub-types
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
* @author Joel Leitch
|
||||
*
|
||||
* @param <T> The handler that will be looked up by type
|
||||
*/
|
||||
public final class TypeMap<T> {
|
||||
private static final Logger logger = Logger.getLogger(TypeMap.class.getName());
|
||||
|
||||
/** Map that is meant for storing default type adapters */
|
||||
private final Map<Type, T> typeMap = new HashMap<Type, T>();
|
||||
|
||||
/** List of default type hierarchy adapters */
|
||||
private final List<Pair<Class<?>, T>> typeHierarchyList = new ArrayList<Pair<Class<?>, T>>();
|
||||
private boolean modifiable = true;
|
||||
|
||||
public synchronized void registerForTypeHierarchy(Class<?> typeOfT, T value) {
|
||||
Pair<Class<?>, T> pair = new Pair<Class<?>, T>(typeOfT, value);
|
||||
registerForTypeHierarchy(pair);
|
||||
}
|
||||
|
||||
public synchronized void registerForTypeHierarchy(Pair<Class<?>, T> pair) {
|
||||
if (!modifiable) {
|
||||
throw new IllegalStateException("Attempted to modify an unmodifiable map.");
|
||||
}
|
||||
int index = getIndexOfSpecificHandlerForTypeHierarchy(pair.first, typeHierarchyList);
|
||||
if (index != -1) {
|
||||
logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first);
|
||||
typeHierarchyList.remove(index);
|
||||
}
|
||||
index = getIndexOfAnOverriddenHandler(pair.first, typeHierarchyList);
|
||||
if (index != -1) {
|
||||
throw new IllegalArgumentException("The specified type handler for type " + pair.first
|
||||
+ " hides the previously registered type hierarchy handler for "
|
||||
+ typeHierarchyList.get(index).first + ". Gson does not allow this.");
|
||||
}
|
||||
// We want stack behavior for adding to this list. A type adapter added subsequently should
|
||||
// override a previously registered one.
|
||||
typeHierarchyList.add(0, pair);
|
||||
}
|
||||
|
||||
private static <T> int getIndexOfAnOverriddenHandler(Class<?> type, List<Pair<Class<?>, T>> typeHierarchyList) {
|
||||
for (int i = typeHierarchyList.size()-1; i >= 0; --i) {
|
||||
Pair<Class<?>, T> entry = typeHierarchyList.get(i);
|
||||
if (type.isAssignableFrom(entry.first)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public synchronized void register(Type typeOfT, T value) {
|
||||
if (!modifiable) {
|
||||
throw new IllegalStateException("Attempted to modify an unmodifiable map.");
|
||||
}
|
||||
typeMap.put(typeOfT, value);
|
||||
}
|
||||
|
||||
public synchronized TypeMap<T> makeUnmodifiable() {
|
||||
modifiable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized T getHandlerFor(Type type) {
|
||||
T handler = typeMap.get(type);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
Class<?> rawClass = $Gson$Types.getRawType(type);
|
||||
if (rawClass != type) {
|
||||
handler = getHandlerFor(rawClass);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
// check if something registered for type hierarchy
|
||||
return getHandlerForTypeHierarchy(rawClass);
|
||||
}
|
||||
|
||||
private T getHandlerForTypeHierarchy(Class<?> type) {
|
||||
for (Pair<Class<?>, T> entry : typeHierarchyList) {
|
||||
if (entry.first.isAssignableFrom(type)) {
|
||||
return entry.second;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized boolean hasSpecificHandlerFor(Type type) {
|
||||
return typeMap.containsKey(type);
|
||||
}
|
||||
|
||||
private static <T> int getIndexOfSpecificHandlerForTypeHierarchy(
|
||||
Class<?> type, List<Pair<Class<?>, T>> typeHierarchyList) {
|
||||
for (int i = typeHierarchyList.size()-1; i >= 0; --i) {
|
||||
if (type.equals(typeHierarchyList.get(i).first)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public synchronized TypeMap<T> copyOf() {
|
||||
TypeMap<T> copy = new TypeMap<T>();
|
||||
// Instead of individually registering entries in the map, make an efficient copy
|
||||
// of the list and map
|
||||
copy.typeMap.putAll(typeMap);
|
||||
copy.typeHierarchyList.addAll(typeHierarchyList);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("{typeHierarchyList:{");
|
||||
appendList(sb, typeHierarchyList);
|
||||
sb.append("},typeMap:{");
|
||||
appendMap(sb, typeMap);
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendList(StringBuilder sb, List<Pair<Class<?>,T>> list) {
|
||||
boolean first = true;
|
||||
for (Pair<Class<?>, T> entry : list) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(typeToString(entry.first)).append(':');
|
||||
sb.append(entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendMap(StringBuilder sb, Map<Type, T> map) {
|
||||
boolean first = true;
|
||||
for (Map.Entry<Type, T> entry : map.entrySet()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(typeToString(entry.getKey())).append(':');
|
||||
sb.append(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private String typeToString(Type type) {
|
||||
return $Gson$Types.getRawType(type).getSimpleName();
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Simple unit tests for the {@link JsonDeserializerExceptionWrapper} class.
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
public class JsonDeserializerExceptionWrapperTest extends TestCase {
|
||||
|
||||
private static final String DATE_STRING =
|
||||
DateFormat.getDateInstance(DateFormat.LONG).format(new Date());
|
||||
private static final JsonPrimitive PRIMITIVE_ELEMENT = new JsonPrimitive(DATE_STRING);
|
||||
|
||||
public void testRethrowJsonParseException() throws Exception {
|
||||
String errorMsg = "please rethrow me";
|
||||
JsonDeserializerExceptionWrapper<String> wrappedJsonSerializer =
|
||||
new JsonDeserializerExceptionWrapper<String>(
|
||||
new ExceptionJsonDeserializer(new JsonParseException(errorMsg)));
|
||||
|
||||
try {
|
||||
wrappedJsonSerializer.deserialize(PRIMITIVE_ELEMENT, String.class, null);
|
||||
fail("JsonParseException should have been thrown");
|
||||
} catch (JsonParseException expected) {
|
||||
assertNull(expected.getCause());
|
||||
assertEquals(errorMsg, expected.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testWrappedExceptionPropagation() throws Exception {
|
||||
IllegalArgumentException exceptionToThrow = new IllegalArgumentException();
|
||||
JsonDeserializerExceptionWrapper<String> wrappedJsonSerializer =
|
||||
new JsonDeserializerExceptionWrapper<String>(
|
||||
new ExceptionJsonDeserializer(exceptionToThrow));
|
||||
|
||||
try {
|
||||
wrappedJsonSerializer.deserialize(PRIMITIVE_ELEMENT, String.class, null);
|
||||
fail("JsonParseException should have been thrown");
|
||||
} catch (JsonParseException expected) {
|
||||
assertEquals(exceptionToThrow, expected.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
public void testProperSerialization() throws Exception {
|
||||
DefaultDateTypeAdapter dateSerializer = new DefaultDateTypeAdapter(DateFormat.LONG);
|
||||
JsonDeserializerExceptionWrapper<Date> wrappedJsonSerializer =
|
||||
new JsonDeserializerExceptionWrapper<Date>(dateSerializer);
|
||||
|
||||
Date expected = dateSerializer.deserialize(PRIMITIVE_ELEMENT, Date.class, null);
|
||||
Date actual = wrappedJsonSerializer.deserialize(PRIMITIVE_ELEMENT, Date.class, null);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private static class ExceptionJsonDeserializer implements JsonDeserializer<String> {
|
||||
private final RuntimeException exceptionToThrow;
|
||||
|
||||
public ExceptionJsonDeserializer(RuntimeException exceptionToThrow) {
|
||||
this.exceptionToThrow = exceptionToThrow;
|
||||
}
|
||||
|
||||
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
throw exceptionToThrow;
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,17 @@ public final class TypeAdapterPrecedenceTest extends TestCase {
|
||||
assertEquals("foo via type adapter", gson.fromJson("foo", Foo.class).name);
|
||||
}
|
||||
|
||||
public void testNonstreamingHierarchicalFollowedByNonstreaming() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Foo.class, newSerializer("hierarchical"))
|
||||
.registerTypeHierarchyAdapter(Foo.class, newDeserializer("hierarchical"))
|
||||
.registerTypeAdapter(Foo.class, newSerializer("non hierarchical"))
|
||||
.registerTypeAdapter(Foo.class, newDeserializer("non hierarchical"))
|
||||
.create();
|
||||
assertEquals("\"foo via non hierarchical\"", gson.toJson(new Foo("foo")));
|
||||
assertEquals("foo via non hierarchical", gson.fromJson("foo", Foo.class).name);
|
||||
}
|
||||
|
||||
private static class Foo {
|
||||
final String name;
|
||||
private Foo(String name) {
|
||||
|
@ -131,15 +131,12 @@ public final class TypeHierarchyAdapterTest extends TestCase {
|
||||
assertEquals(manager.userid, copied.userid);
|
||||
}
|
||||
|
||||
public void testRegisterSubTypeFirstNotAllowed() {
|
||||
try {
|
||||
new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
|
||||
.registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
|
||||
.create();
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
/** This behaviour changed in Gson 2.1; it used to throw. */
|
||||
public void testRegisterSubTypeFirstAllowed() {
|
||||
new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
|
||||
.registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
|
||||
.create();
|
||||
}
|
||||
|
||||
static class ManagerAdapter implements JsonSerializer<Manager>, JsonDeserializer<Manager> {
|
||||
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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 com.google.gson.common.TestTypes.Base;
|
||||
import com.google.gson.common.TestTypes.Sub;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link TypeMap} class.
|
||||
*
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
public class TypeMapTest extends TestCase {
|
||||
private TypeMap<String> paramMap;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
paramMap = new TypeMap<String>();
|
||||
}
|
||||
|
||||
public void testNullMap() throws Exception {
|
||||
assertFalse(paramMap.hasSpecificHandlerFor(String.class));
|
||||
assertNull(paramMap.getHandlerFor(String.class));
|
||||
assertNull(paramMap.getHandlerFor(String.class));
|
||||
}
|
||||
|
||||
public void testHasGenericButNotSpecific() throws Exception {
|
||||
Type specificType = new TypeToken<List<String>>() {}.getType();
|
||||
String handler = "blah";
|
||||
paramMap.register(List.class, handler);
|
||||
|
||||
assertFalse(paramMap.hasSpecificHandlerFor(specificType));
|
||||
assertTrue(paramMap.hasSpecificHandlerFor(List.class));
|
||||
assertNotNull(paramMap.getHandlerFor(specificType));
|
||||
assertNotNull(paramMap.getHandlerFor(List.class));
|
||||
assertEquals(handler, paramMap.getHandlerFor(specificType));
|
||||
}
|
||||
|
||||
public void testHasSpecificType() throws Exception {
|
||||
Type specificType = new TypeToken<List<String>>() {}.getType();
|
||||
String handler = "blah";
|
||||
paramMap.register(specificType, handler);
|
||||
|
||||
assertTrue(paramMap.hasSpecificHandlerFor(specificType));
|
||||
assertFalse(paramMap.hasSpecificHandlerFor(List.class));
|
||||
assertNotNull(paramMap.getHandlerFor(specificType));
|
||||
assertNull(paramMap.getHandlerFor(List.class));
|
||||
assertEquals(handler, paramMap.getHandlerFor(specificType));
|
||||
}
|
||||
|
||||
public void testTypeOverridding() throws Exception {
|
||||
String handler1 = "blah1";
|
||||
String handler2 = "blah2";
|
||||
paramMap.register(String.class, handler1);
|
||||
paramMap.register(String.class, handler2);
|
||||
|
||||
assertTrue(paramMap.hasSpecificHandlerFor(String.class));
|
||||
assertEquals(handler2, paramMap.getHandlerFor(String.class));
|
||||
}
|
||||
|
||||
public void testMakeUnmodifiable() throws Exception {
|
||||
paramMap.makeUnmodifiable();
|
||||
try {
|
||||
paramMap.register(String.class, "blah");
|
||||
fail("Can not register handlers when map is unmodifiable");
|
||||
} catch (IllegalStateException expected) { }
|
||||
}
|
||||
|
||||
public void testTypeHierarchy() {
|
||||
paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
|
||||
String handler = paramMap.getHandlerFor(Sub.class);
|
||||
assertEquals("baseHandler", handler);
|
||||
}
|
||||
|
||||
public void testTypeHierarchyMultipleHandlers() {
|
||||
paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
|
||||
paramMap.registerForTypeHierarchy(Sub.class, "subHandler");
|
||||
String handler = paramMap.getHandlerFor(SubOfSub.class);
|
||||
assertEquals("subHandler", handler);
|
||||
}
|
||||
|
||||
public void testReplaceExistingTypeHierarchyHandler() {
|
||||
paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
|
||||
paramMap.registerForTypeHierarchy(Base.class, "base2Handler");
|
||||
String handler = paramMap.getHandlerFor(Base.class);
|
||||
assertEquals("base2Handler", handler);
|
||||
}
|
||||
|
||||
public void testHidingExistingTypeHierarchyHandlerIsDisallowed() {
|
||||
paramMap.registerForTypeHierarchy(Sub.class, "subHandler");
|
||||
try {
|
||||
paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
|
||||
fail("A handler that hides an existing type hierarchy handler is not allowed");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
private static class SubOfSub extends Sub {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user