Register Gson 1.x tree-style adapters in the TypeAdapter.Factory list rather than in the ParameterizedTypeHandlerMap.

The motivation for this change is to give tree-style adapters precedence order in registration. This fixes the test I committed earlier today, where registration order was not honored.

This renamed ParameterizedTypeHandlerMap to the shorter 'TypeMap'. For type adapters, this is now only used for type hierarchy. We still need non-hierarchy support in TypeMap for instance creators; I'll be looking for workarounds to see if further simplification is possible here.
This commit is contained in:
Jesse Wilson 2011-11-20 19:55:01 +00:00
parent 777e17c723
commit d391584d48
7 changed files with 210 additions and 254 deletions

View File

@ -17,9 +17,9 @@
package com.google.gson; package com.google.gson;
import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.ParameterizedTypeHandlerMap;
import com.google.gson.internal.Primitives; import com.google.gson.internal.Primitives;
import com.google.gson.internal.Streams; 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.ArrayTypeAdapter;
import com.google.gson.internal.bind.BigDecimalTypeAdapter; import com.google.gson.internal.bind.BigDecimalTypeAdapter;
import com.google.gson.internal.bind.BigIntegerTypeAdapter; import com.google.gson.internal.bind.BigIntegerTypeAdapter;
@ -101,8 +101,7 @@ import java.util.Map;
*/ */
public final class Gson { public final class Gson {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
static final ParameterizedTypeHandlerMap EMPTY_MAP = static final TypeMap EMPTY_MAP = new TypeMap().makeUnmodifiable();
new ParameterizedTypeHandlerMap().makeUnmodifiable();
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
@ -140,16 +139,31 @@ public final class Gson {
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
/** Map containing Type or Class objects as keys */ /** Map containing Type or Class objects as keys */
private final ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers; private final TypeMap<JsonSerializer<?>> serializers;
/** Map containing Type or Class objects as keys */ /** Map containing Type or Class objects as keys */
private final ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers; private final TypeMap<JsonDeserializer<?>> deserializers;
private final boolean serializeNulls; private final boolean serializeNulls;
private final boolean htmlSafe; private final boolean htmlSafe;
private final boolean generateNonExecutableJson; private final boolean generateNonExecutableJson;
private final boolean prettyPrinting; private final boolean prettyPrinting;
final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() {
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
return (T) fromJson(json, typeOfT);
}
};
final JsonSerializationContext serializationContext = new JsonSerializationContext() {
public JsonElement serialize(Object src) {
return toJsonTree(src);
}
public JsonElement serialize(Object src, Type typeOfSrc) {
return toJsonTree(src, typeOfSrc);
}
};
/** /**
* Constructs a Gson object with default configuration. The default configuration has the * Constructs a Gson object with default configuration. The default configuration has the
* following settings: * following settings:
@ -195,9 +209,9 @@ public final class Gson {
Gson(final ExclusionStrategy deserializationExclusionStrategy, Gson(final ExclusionStrategy deserializationExclusionStrategy,
final ExclusionStrategy serializationExclusionStrategy, final ExclusionStrategy serializationExclusionStrategy,
final FieldNamingStrategy2 fieldNamingPolicy, final FieldNamingStrategy2 fieldNamingPolicy,
final ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators, boolean serializeNulls, final TypeMap<InstanceCreator<?>> instanceCreators, boolean serializeNulls,
final ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, final TypeMap<JsonSerializer<?>> serializers,
final ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers, final TypeMap<JsonDeserializer<?>> deserializers,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean serializeSpecialFloatingPointValues, boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, LongSerializationPolicy longSerializationPolicy,
@ -268,7 +282,7 @@ public final class Gson {
factories.add(factory); factories.add(factory);
} }
factories.add(new GsonToMiniGsonTypeAdapterFactory(this, serializers, deserializers)); factories.add(new TreeTypeAdapter.TypeHierarchyFactory(serializers, deserializers));
factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(TypeAdapters.URL_FACTORY); factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY); factories.add(TypeAdapters.URI_FACTORY);

View File

@ -18,8 +18,8 @@ package com.google.gson;
import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter; import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter;
import com.google.gson.internal.$Gson$Preconditions; import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.ParameterizedTypeHandlerMap;
import com.google.gson.internal.Primitives; import com.google.gson.internal.Primitives;
import com.google.gson.internal.TypeMap;
import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -87,9 +87,9 @@ public final class GsonBuilder {
private boolean excludeFieldsWithoutExposeAnnotation; private boolean excludeFieldsWithoutExposeAnnotation;
private LongSerializationPolicy longSerializationPolicy; private LongSerializationPolicy longSerializationPolicy;
private FieldNamingStrategy2 fieldNamingPolicy; private FieldNamingStrategy2 fieldNamingPolicy;
private final ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators; private final TypeMap<InstanceCreator<?>> instanceCreators;
private final ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers; private final TypeMap<JsonSerializer<?>> serializers;
private final ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers; private final TypeMap<JsonDeserializer<?>> deserializers;
private final List<TypeAdapter.Factory> typeAdapterFactories private final List<TypeAdapter.Factory> typeAdapterFactories
= new ArrayList<TypeAdapter.Factory>(); = new ArrayList<TypeAdapter.Factory>();
private boolean serializeNulls; private boolean serializeNulls;
@ -124,9 +124,9 @@ public final class GsonBuilder {
excludeFieldsWithoutExposeAnnotation = false; excludeFieldsWithoutExposeAnnotation = false;
longSerializationPolicy = LongSerializationPolicy.DEFAULT; longSerializationPolicy = LongSerializationPolicy.DEFAULT;
fieldNamingPolicy = Gson.DEFAULT_NAMING_POLICY; fieldNamingPolicy = Gson.DEFAULT_NAMING_POLICY;
instanceCreators = new ParameterizedTypeHandlerMap<InstanceCreator<?>>(); instanceCreators = new TypeMap<InstanceCreator<?>>();
serializers = new ParameterizedTypeHandlerMap<JsonSerializer<?>>(); serializers = new TypeMap<JsonSerializer<?>>();
deserializers = new ParameterizedTypeHandlerMap<JsonDeserializer<?>>(); deserializers = new TypeMap<JsonDeserializer<?>>();
serializeNulls = false; serializeNulls = false;
dateStyle = DateFormat.DEFAULT; dateStyle = DateFormat.DEFAULT;
timeStyle = DateFormat.DEFAULT; timeStyle = DateFormat.DEFAULT;
@ -529,11 +529,9 @@ public final class GsonBuilder {
if (typeAdapter instanceof InstanceCreator<?>) { if (typeAdapter instanceof InstanceCreator<?>) {
registerInstanceCreator(type, (InstanceCreator<?>) typeAdapter); registerInstanceCreator(type, (InstanceCreator<?>) typeAdapter);
} }
if (typeAdapter instanceof JsonSerializer<?>) { if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
registerSerializer(type, (JsonSerializer<?>) typeAdapter); TypeToken<?> typeToken = TypeToken.get(type);
} typeAdapterFactories.add(new TreeTypeAdapter.SingleTypeFactory(typeToken, typeAdapter));
if (typeAdapter instanceof JsonDeserializer<?>) {
registerDeserializer(type, (JsonDeserializer<?>) typeAdapter);
} }
if (typeAdapter instanceof TypeAdapter.Factory) { if (typeAdapter instanceof TypeAdapter.Factory) {
typeAdapterFactories.add((TypeAdapter.Factory) typeAdapter); typeAdapterFactories.add((TypeAdapter.Factory) typeAdapter);
@ -558,36 +556,6 @@ public final class GsonBuilder {
return this; return this;
} }
/**
* Configures Gson to use a custom JSON serializer for the specified type. You should use this
* method if you want to register different serializers for different generic types corresponding
* to a raw type.
*
*
* @param typeOfT The type definition for T
* @param serializer the custom serializer
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
private <T> GsonBuilder registerSerializer(Type typeOfT, JsonSerializer<T> serializer) {
serializers.register(typeOfT, serializer);
return this;
}
/**
* Configures Gson to use a custom JSON deserializer for the specified type. You should use this
* method if you want to register different deserializers for different generic types
* corresponding to a raw type.
*
*
* @param typeOfT The type definition for T
* @param deserializer the custom deserializer
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
private <T> GsonBuilder registerDeserializer(Type typeOfT, JsonDeserializer<T> deserializer) {
deserializers.register(typeOfT, new JsonDeserializerExceptionWrapper<T>(deserializer));
return this;
}
/** /**
* Configures Gson for custom serialization or deserialization for an inheritance type hierarchy. * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
* This method combines the registration of an {@link InstanceCreator}, {@link JsonSerializer}, * This method combines the registration of an {@link InstanceCreator}, {@link JsonSerializer},
@ -703,8 +671,7 @@ public final class GsonBuilder {
} }
private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, TypeMap<JsonSerializer<?>> serializers, TypeMap<JsonDeserializer<?>> deserializers) {
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) {
DefaultDateTypeAdapter dateTypeAdapter = null; DefaultDateTypeAdapter dateTypeAdapter = null;
if (datePattern != null && !"".equals(datePattern.trim())) { if (datePattern != null && !"".equals(datePattern.trim())) {
dateTypeAdapter = new DefaultDateTypeAdapter(datePattern); dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
@ -722,8 +689,7 @@ public final class GsonBuilder {
} }
} }
private static <T> void registerIfAbsent(Class<?> type, private static <T> void registerIfAbsent(Class<?> type, TypeMap<T> adapters, T adapter) {
ParameterizedTypeHandlerMap<T> adapters, T adapter) {
if (!adapters.hasSpecificHandlerFor(type)) { if (!adapters.hasSpecificHandlerFor(type)) {
adapters.register(type, adapter); adapters.register(type, adapter);
} }

View File

@ -1,107 +0,0 @@
/*
* Copyright (C) 2011 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.ParameterizedTypeHandlerMap;
import com.google.gson.internal.Streams;
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;
final class GsonToMiniGsonTypeAdapterFactory implements TypeAdapter.Factory {
private final ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers;
private final ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers;
private final JsonDeserializationContext deserializationContext;
private final JsonSerializationContext serializationContext;
public GsonToMiniGsonTypeAdapterFactory(final Gson gson,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) {
this.serializers = serializers;
this.deserializers = deserializers;
this.deserializationContext = new JsonDeserializationContext() {
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
return (T) gson.fromJson(json, typeOfT);
}
};
this.serializationContext = new JsonSerializationContext() {
public JsonElement serialize(Object src) {
return gson.toJsonTree(src);
}
public JsonElement serialize(Object src, Type typeOfSrc) {
return gson.toJsonTree(src, typeOfSrc);
}
};
}
public <T> TypeAdapter<T> create(final Gson context, final TypeToken<T> typeToken) {
final Type type = typeToken.getType();
@SuppressWarnings("unchecked") // guaranteed to match typeOfT
final JsonSerializer<T> serializer
= (JsonSerializer<T>) serializers.getHandlerFor(type, false);
@SuppressWarnings("unchecked") // guaranteed to match typeOfT
final JsonDeserializer<T> deserializer
= (JsonDeserializer<T>) deserializers.getHandlerFor(type, false);
if (serializer == null && deserializer == null) {
return null;
}
return new TypeAdapter<T>() {
/**
* The delegate is lazily created because it may not be needed, and
* creating it may fail.
*/
private TypeAdapter<T> delegate;
@Override public T read(JsonReader reader) throws IOException {
if (deserializer == null) {
return delegate().read(reader);
}
JsonElement value = Streams.parse(reader);
if (value.isJsonNull()) {
return null;
}
return deserializer.deserialize(value, type, deserializationContext);
}
@Override public void write(JsonWriter writer, T value) throws IOException {
if (serializer == null) {
delegate().write(writer, value);
return;
}
if (value == null) {
writer.nullValue();
return;
}
JsonElement element = serializer.serialize(value, type, serializationContext);
Streams.write(element, writer);
}
private TypeAdapter<T> delegate() {
TypeAdapter<T> d = delegate;
return d != null
? d
: (delegate = context.getNextAdapter(GsonToMiniGsonTypeAdapterFactory.this, typeToken));
}
};
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2011 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 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
* tree adapter may be serialization-only or deserialization-only, this class
* has a facility to lookup a delegate type adapter on demand.
*/
public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
private final JsonSerializer<T> serializer;
private final JsonDeserializer<T> deserializer;
private final Gson gson;
private final TypeToken<T> typeToken;
private final Factory skipPast;
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
private TypeAdapter<T> delegate;
private TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, Factory skipPast) {
this.serializer = serializer;
this.deserializer = deserializer;
this.gson = gson;
this.typeToken = typeToken;
this.skipPast = skipPast;
}
@Override public T read(JsonReader reader) throws IOException {
if (deserializer == null) {
return delegate().read(reader);
}
JsonElement value = Streams.parse(reader);
if (value.isJsonNull()) {
return null;
}
return deserializer.deserialize(value, typeToken.getType(), gson.deserializationContext);
}
@Override public void write(JsonWriter writer, T value) throws IOException {
if (serializer == null) {
delegate().write(writer, value);
return;
}
if (value == null) {
writer.nullValue();
return;
}
JsonElement tree = serializer.serialize(value, typeToken.getType(), gson.serializationContext);
Streams.write(tree,writer);
}
private TypeAdapter<T> delegate() {
TypeAdapter<T> d = delegate;
return d != null
? d
: (delegate = gson.getNextAdapter(skipPast, typeToken));
}
public static class SingleTypeFactory implements TypeAdapter.Factory {
private final TypeToken<?> typeToken;
private final JsonSerializer<?> serializer;
private final JsonDeserializer<?> deserializer;
public SingleTypeFactory(TypeToken<?> typeToken, Object typeAdapter) {
this.typeToken = typeToken;
serializer = typeAdapter instanceof JsonSerializer
? (JsonSerializer) typeAdapter
: null;
deserializer = typeAdapter instanceof JsonDeserializer
? (JsonDeserializer) typeAdapter
: null;
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
}
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call
public <T> TypeAdapter<T> create(Gson context, TypeToken<T> type) {
return typeToken.equals(type)
? 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

@ -36,14 +36,14 @@ import java.util.TreeSet;
* Returns a function that can construct an instance of a requested type. * Returns a function that can construct an instance of a requested type.
*/ */
public final class ConstructorConstructor { public final class ConstructorConstructor {
private final ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators; private final TypeMap<InstanceCreator<?>> instanceCreators;
public ConstructorConstructor(ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators) { public ConstructorConstructor(TypeMap<InstanceCreator<?>> instanceCreators) {
this.instanceCreators = instanceCreators; this.instanceCreators = instanceCreators;
} }
public ConstructorConstructor() { public ConstructorConstructor() {
this(new ParameterizedTypeHandlerMap<InstanceCreator<?>>()); this(new TypeMap<InstanceCreator<?>>());
} }
public <T> ObjectConstructor<T> getConstructor(TypeToken<T> typeToken) { public <T> ObjectConstructor<T> getConstructor(TypeToken<T> typeToken) {
@ -54,7 +54,7 @@ public final class ConstructorConstructor {
@SuppressWarnings("unchecked") // types must agree @SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> creator final InstanceCreator<T> creator
= (InstanceCreator<T>) instanceCreators.getHandlerFor(type, false); = (InstanceCreator<T>) instanceCreators.getHandlerFor(type);
if (creator != null) { if (creator != null) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
public T construct() { public T construct() {

View File

@ -33,16 +33,14 @@ import java.util.logging.Logger;
* *
* @param <T> The handler that will be looked up by type * @param <T> The handler that will be looked up by type
*/ */
public final class ParameterizedTypeHandlerMap<T> { public final class TypeMap<T> {
private static final Logger logger = Logger.getLogger(TypeMap.class.getName());
private static final Logger logger =
Logger.getLogger(ParameterizedTypeHandlerMap.class.getName());
/** Map that is meant for storing default type adapters */ /** Map that is meant for storing default type adapters */
private final Map<Type, T> userMap = new HashMap<Type, T>(); private final Map<Type, T> typeMap = new HashMap<Type, T>();
/** List of default type hierarchy adapters */ /** List of default type hierarchy adapters */
private final List<Pair<Class<?>, T>> userTypeHierarchyList = new ArrayList<Pair<Class<?>, T>>(); private final List<Pair<Class<?>, T>> typeHierarchyList = new ArrayList<Pair<Class<?>, T>>();
private boolean modifiable = true; private boolean modifiable = true;
public synchronized void registerForTypeHierarchy(Class<?> typeOfT, T value) { public synchronized void registerForTypeHierarchy(Class<?> typeOfT, T value) {
@ -54,14 +52,13 @@ public final class ParameterizedTypeHandlerMap<T> {
if (!modifiable) { if (!modifiable) {
throw new IllegalStateException("Attempted to modify an unmodifiable map."); throw new IllegalStateException("Attempted to modify an unmodifiable map.");
} }
List<Pair<Class<?>, T>> typeHierarchyList = userTypeHierarchyList;
int index = getIndexOfSpecificHandlerForTypeHierarchy(pair.first, typeHierarchyList); int index = getIndexOfSpecificHandlerForTypeHierarchy(pair.first, typeHierarchyList);
if (index >= 0) { if (index != -1) {
logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first); logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first);
typeHierarchyList.remove(index); typeHierarchyList.remove(index);
} }
index = getIndexOfAnOverriddenHandler(pair.first, typeHierarchyList); index = getIndexOfAnOverriddenHandler(pair.first, typeHierarchyList);
if (index >= 0) { if (index != -1) {
throw new IllegalArgumentException("The specified type handler for type " + pair.first throw new IllegalArgumentException("The specified type handler for type " + pair.first
+ " hides the previously registered type hierarchy handler for " + " hides the previously registered type hierarchy handler for "
+ typeHierarchyList.get(index).first + ". Gson does not allow this."); + typeHierarchyList.get(index).first + ". Gson does not allow this.");
@ -85,71 +82,41 @@ public final class ParameterizedTypeHandlerMap<T> {
if (!modifiable) { if (!modifiable) {
throw new IllegalStateException("Attempted to modify an unmodifiable map."); throw new IllegalStateException("Attempted to modify an unmodifiable map.");
} }
if (hasSpecificHandlerFor(typeOfT)) { typeMap.put(typeOfT, value);
logger.log(Level.WARNING, "Overriding the existing type handler for {0}", typeOfT);
}
Map<Type, T> map = userMap;
map.put(typeOfT, value);
} }
public synchronized void registerIfAbsent(ParameterizedTypeHandlerMap<T> other) { public synchronized TypeMap<T> makeUnmodifiable() {
if (!modifiable) {
throw new IllegalStateException("Attempted to modify an unmodifiable map.");
}
for (Map.Entry<Type, T> entry : other.userMap.entrySet()) {
if (!userMap.containsKey(entry.getKey())) {
register(entry.getKey(), entry.getValue());
}
}
// Quite important to traverse the typeHierarchyList from stack bottom first since
// we want to register the handlers in the same order to preserve priority order
for (int i = other.userTypeHierarchyList.size()-1; i >= 0; --i) {
Pair<Class<?>, T> entry = other.userTypeHierarchyList.get(i);
int index = getIndexOfSpecificHandlerForTypeHierarchy(entry.first, userTypeHierarchyList);
if (index < 0) {
registerForTypeHierarchy(entry);
}
}
}
public synchronized ParameterizedTypeHandlerMap<T> makeUnmodifiable() {
modifiable = false; modifiable = false;
return this; return this;
} }
public synchronized T getHandlerFor(Type type, boolean systemOnly) { public synchronized T getHandlerFor(Type type) {
T handler; T handler = typeMap.get(type);
if (!systemOnly) {
handler = userMap.get(type);
if (handler != null) { if (handler != null) {
return handler; return handler;
} }
}
Class<?> rawClass = $Gson$Types.getRawType(type); Class<?> rawClass = $Gson$Types.getRawType(type);
if (rawClass != type) { if (rawClass != type) {
handler = getHandlerFor(rawClass, systemOnly); handler = getHandlerFor(rawClass);
if (handler != null) { if (handler != null) {
return handler; return handler;
} }
} }
// check if something registered for type hierarchy // check if something registered for type hierarchy
handler = getHandlerForTypeHierarchy(rawClass, systemOnly); return getHandlerForTypeHierarchy(rawClass);
return handler;
} }
private T getHandlerForTypeHierarchy(Class<?> type, boolean systemOnly) { private T getHandlerForTypeHierarchy(Class<?> type) {
if (!systemOnly) { for (Pair<Class<?>, T> entry : typeHierarchyList) {
for (Pair<Class<?>, T> entry : userTypeHierarchyList) {
if (entry.first.isAssignableFrom(type)) { if (entry.first.isAssignableFrom(type)) {
return entry.second; return entry.second;
} }
} }
}
return null; return null;
} }
public synchronized boolean hasSpecificHandlerFor(Type type) { public synchronized boolean hasSpecificHandlerFor(Type type) {
return userMap.containsKey(type); return typeMap.containsKey(type);
} }
private static <T> int getIndexOfSpecificHandlerForTypeHierarchy( private static <T> int getIndexOfSpecificHandlerForTypeHierarchy(
@ -162,24 +129,21 @@ public final class ParameterizedTypeHandlerMap<T> {
return -1; return -1;
} }
public synchronized ParameterizedTypeHandlerMap<T> copyOf() { public synchronized TypeMap<T> copyOf() {
ParameterizedTypeHandlerMap<T> copy = new ParameterizedTypeHandlerMap<T>(); TypeMap<T> copy = new TypeMap<T>();
// Instead of individually registering entries in the map, make an efficient copy // Instead of individually registering entries in the map, make an efficient copy
// of the list and map // of the list and map
copy.typeMap.putAll(typeMap);
// TODO (inder): Performance optimization. We can probably just share the copy.typeHierarchyList.addAll(typeHierarchyList);
// systemMap and systemTypeHierarchyList instead of making copies
copy.userMap.putAll(userMap);
copy.userTypeHierarchyList.addAll(userTypeHierarchyList);
return copy; return copy;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder("{userTypeHierarchyList:{"); StringBuilder sb = new StringBuilder("{typeHierarchyList:{");
appendList(sb, userTypeHierarchyList); appendList(sb, typeHierarchyList);
sb.append("},userMap:{"); sb.append("},typeMap:{");
appendMap(sb, userMap); appendMap(sb, typeMap);
sb.append("}"); sb.append("}");
return sb.toString(); return sb.toString();
} }

View File

@ -24,23 +24,23 @@ import java.util.List;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
* Unit tests for the {@link com.google.gson.internal.ParameterizedTypeHandlerMap} class. * Unit tests for the {@link TypeMap} class.
* *
* @author Joel Leitch * @author Joel Leitch
*/ */
public class ParameterizedTypeHandlerMapTest extends TestCase { public class TypeMapTest extends TestCase {
private ParameterizedTypeHandlerMap<String> paramMap; private TypeMap<String> paramMap;
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
paramMap = new ParameterizedTypeHandlerMap<String>(); paramMap = new TypeMap<String>();
} }
public void testNullMap() throws Exception { public void testNullMap() throws Exception {
assertFalse(paramMap.hasSpecificHandlerFor(String.class)); assertFalse(paramMap.hasSpecificHandlerFor(String.class));
assertNull(paramMap.getHandlerFor(String.class, false)); assertNull(paramMap.getHandlerFor(String.class));
assertNull(paramMap.getHandlerFor(String.class, false)); assertNull(paramMap.getHandlerFor(String.class));
} }
public void testHasGenericButNotSpecific() throws Exception { public void testHasGenericButNotSpecific() throws Exception {
@ -50,9 +50,9 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
assertFalse(paramMap.hasSpecificHandlerFor(specificType)); assertFalse(paramMap.hasSpecificHandlerFor(specificType));
assertTrue(paramMap.hasSpecificHandlerFor(List.class)); assertTrue(paramMap.hasSpecificHandlerFor(List.class));
assertNotNull(paramMap.getHandlerFor(specificType, false)); assertNotNull(paramMap.getHandlerFor(specificType));
assertNotNull(paramMap.getHandlerFor(List.class, false)); assertNotNull(paramMap.getHandlerFor(List.class));
assertEquals(handler, paramMap.getHandlerFor(specificType, false)); assertEquals(handler, paramMap.getHandlerFor(specificType));
} }
public void testHasSpecificType() throws Exception { public void testHasSpecificType() throws Exception {
@ -62,9 +62,9 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
assertTrue(paramMap.hasSpecificHandlerFor(specificType)); assertTrue(paramMap.hasSpecificHandlerFor(specificType));
assertFalse(paramMap.hasSpecificHandlerFor(List.class)); assertFalse(paramMap.hasSpecificHandlerFor(List.class));
assertNotNull(paramMap.getHandlerFor(specificType, false)); assertNotNull(paramMap.getHandlerFor(specificType));
assertNull(paramMap.getHandlerFor(List.class, false)); assertNull(paramMap.getHandlerFor(List.class));
assertEquals(handler, paramMap.getHandlerFor(specificType, false)); assertEquals(handler, paramMap.getHandlerFor(specificType));
} }
public void testTypeOverridding() throws Exception { public void testTypeOverridding() throws Exception {
@ -74,7 +74,7 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
paramMap.register(String.class, handler2); paramMap.register(String.class, handler2);
assertTrue(paramMap.hasSpecificHandlerFor(String.class)); assertTrue(paramMap.hasSpecificHandlerFor(String.class));
assertEquals(handler2, paramMap.getHandlerFor(String.class, false)); assertEquals(handler2, paramMap.getHandlerFor(String.class));
} }
public void testMakeUnmodifiable() throws Exception { public void testMakeUnmodifiable() throws Exception {
@ -87,30 +87,21 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
public void testTypeHierarchy() { public void testTypeHierarchy() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler"); paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
String handler = paramMap.getHandlerFor(Sub.class, false); String handler = paramMap.getHandlerFor(Sub.class);
assertEquals("baseHandler", handler); assertEquals("baseHandler", handler);
} }
public void testTypeHierarchyMultipleHandlers() { public void testTypeHierarchyMultipleHandlers() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler"); paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
paramMap.registerForTypeHierarchy(Sub.class, "subHandler"); paramMap.registerForTypeHierarchy(Sub.class, "subHandler");
String handler = paramMap.getHandlerFor(SubOfSub.class, false); String handler = paramMap.getHandlerFor(SubOfSub.class);
assertEquals("subHandler", handler); assertEquals("subHandler", handler);
} }
public void testTypeHierarchyRegisterIfAbsent() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
ParameterizedTypeHandlerMap<String> otherMap = new ParameterizedTypeHandlerMap<String>();
otherMap.registerForTypeHierarchy(Base.class, "baseHandler2");
paramMap.registerIfAbsent(otherMap);
String handler = paramMap.getHandlerFor(Base.class, false);
assertEquals("baseHandler", handler);
}
public void testReplaceExistingTypeHierarchyHandler() { public void testReplaceExistingTypeHierarchyHandler() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler"); paramMap.registerForTypeHierarchy(Base.class, "baseHandler");
paramMap.registerForTypeHierarchy(Base.class, "base2Handler"); paramMap.registerForTypeHierarchy(Base.class, "base2Handler");
String handler = paramMap.getHandlerFor(Base.class, false); String handler = paramMap.getHandlerFor(Base.class);
assertEquals("base2Handler", handler); assertEquals("base2Handler", handler);
} }