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:
Jesse Wilson 2011-11-23 06:16:55 +00:00
parent 3cbe355cb6
commit 1794182a56
11 changed files with 88 additions and 600 deletions

6
gson/Gson 2.1 notes.txt Normal file
View 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

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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> {

View File

@ -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 {
}
}