Added a new GsonBuilder option to register a type adapter for a type hierarchy instead of a single type. Upgraded Gson version to 1.5 since a new API call is being added.
diff --git a/gson/pom.xml b/gson/pom.xml index e781e58..c92ca28 100644 --- a/gson/pom.xml +++ b/gson/pom.xml
@@ -4,7 +4,7 @@ <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <packaging>jar</packaging> - <version>1.4</version> + <version>1.5</version> <inceptionYear>2008</inceptionYear> <name>Gson</name> <url>http://code.google.com/p/google-gson/</url>
diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index b34d8eb..695b89a 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java
@@ -36,11 +36,9 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; -import java.util.Queue; import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; @@ -106,13 +104,14 @@ ParameterizedTypeHandlerMap<JsonSerializer<?>> map = new ParameterizedTypeHandlerMap<JsonSerializer<?>>(); - map.register(Enum.class, ENUM_TYPE_ADAPTER); + map.registerForTypeHierarchy(Enum.class, ENUM_TYPE_ADAPTER); map.register(URL.class, URL_TYPE_ADAPTER); map.register(URI.class, URI_TYPE_ADAPTER); map.register(UUID.class, UUUID_TYPE_ADAPTER); map.register(Locale.class, LOCALE_TYPE_ADAPTER); - map.register(Collection.class, COLLECTION_TYPE_ADAPTER); - map.register(Map.class, MAP_TYPE_ADAPTER); + map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER); + map.registerForTypeHierarchy(Set.class, COLLECTION_TYPE_ADAPTER); + map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER); map.register(Date.class, DATE_TYPE_ADAPTER); map.register(java.sql.Date.class, JAVA_SQL_DATE_TYPE_ADAPTER); map.register(Timestamp.class, DATE_TYPE_ADAPTER); @@ -143,13 +142,14 @@ private static ParameterizedTypeHandlerMap<JsonDeserializer<?>> createDefaultDeserializers() { ParameterizedTypeHandlerMap<JsonDeserializer<?>> map = new ParameterizedTypeHandlerMap<JsonDeserializer<?>>(); - map.register(Enum.class, wrapDeserializer(ENUM_TYPE_ADAPTER)); + map.registerForTypeHierarchy(Enum.class, wrapDeserializer(ENUM_TYPE_ADAPTER)); map.register(URL.class, wrapDeserializer(URL_TYPE_ADAPTER)); map.register(URI.class, wrapDeserializer(URI_TYPE_ADAPTER)); map.register(UUID.class, wrapDeserializer(UUUID_TYPE_ADAPTER)); map.register(Locale.class, wrapDeserializer(LOCALE_TYPE_ADAPTER)); - map.register(Collection.class, wrapDeserializer(COLLECTION_TYPE_ADAPTER)); - map.register(Map.class, wrapDeserializer(MAP_TYPE_ADAPTER)); + map.registerForTypeHierarchy(Collection.class, wrapDeserializer(COLLECTION_TYPE_ADAPTER)); + map.registerForTypeHierarchy(Set.class, wrapDeserializer(COLLECTION_TYPE_ADAPTER)); + map.registerForTypeHierarchy(Map.class, wrapDeserializer(MAP_TYPE_ADAPTER)); map.register(Date.class, wrapDeserializer(DATE_TYPE_ADAPTER)); map.register(java.sql.Date.class, wrapDeserializer(JAVA_SQL_DATE_TYPE_ADAPTER)); map.register(Timestamp.class, wrapDeserializer(TIMESTAMP_DESERIALIZER)); @@ -189,9 +189,7 @@ map.register(Map.class, MAP_TYPE_ADAPTER); // Add Collection type instance creators - map.register(Collection.class, COLLECTION_TYPE_ADAPTER); - map.register(List.class, COLLECTION_TYPE_ADAPTER); - map.register(Queue.class, COLLECTION_TYPE_ADAPTER); + map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER); map.register(Set.class, HASH_SET_CREATOR); map.register(SortedSet.class, TREE_SET_CREATOR);
diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index c0ad98e..996d715 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java
@@ -418,6 +418,57 @@ } /** + * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy. + * This method combines the registration of an {@link InstanceCreator}, {@link JsonSerializer}, + * and a {@link JsonDeserializer}. It is best used when a single object {@code typeAdapter} + * implements all the required interfaces for custom serialization with Gson. + * If an instance creator, serializer or deserializer was previously registered for the specified + * type hierarchy, it is overwritten. If an instance creator, serializer or deserializer is + * registered for a specific type in the type hierarchy, it will be invoked instead of the one + * registered for the type hierarchy. + * + * @param baseType the class definition for the type adapter being registered for the base class + * or interface + * @param typeAdapter This object must implement at least one of the {@link InstanceCreator}, + * {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.5 + */ + public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) { + Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?> + || typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof InstanceCreator<?>); + if (typeAdapter instanceof InstanceCreator<?>) { + registerInstanceCreatorForTypeHierarchy(baseType, (InstanceCreator<?>) typeAdapter); + } + if (typeAdapter instanceof JsonSerializer<?>) { + registerSerializerForTypeHierarchy(baseType, (JsonSerializer<?>) typeAdapter); + } + if (typeAdapter instanceof JsonDeserializer<?>) { + registerDeserializerForTypeHierarchy(baseType, (JsonDeserializer<?>) 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, + final 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)); + return this; + } + + /** * Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows * special double values (NaN, Infinity, -Infinity). However, * <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
diff --git a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java index 388ccdc..e7cab3f 100644 --- a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java +++ b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java
@@ -17,10 +17,10 @@ package com.google.gson; import java.lang.reflect.Type; -import java.util.Collection; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,19 +31,37 @@ * @author Inderjeet Singh * @author Joel Leitch * - * @param <T> - * The handler that will be looked up by type + * @param <T> The handler that will be looked up by type */ final class ParameterizedTypeHandlerMap<T> { private static final Logger logger = Logger.getLogger(ParameterizedTypeHandlerMap.class.getName()); private final Map<Type, T> map = new HashMap<Type, T>(); + 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); + if (index >= 0) { + logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first); + typeHierarchyList.remove(index); + } + // We want stack behavior for adding to this list. A type adapter added subsequently should + // override a previously registered one. + typeHierarchyList.add(0, pair); + } + public synchronized void register(Type typeOfT, T value) { if (!modifiable) { - throw new IllegalStateException( - "Attempted to modify an unmodifiable map."); + throw new IllegalStateException("Attempted to modify an unmodifiable map."); } if (hasSpecificHandlerFor(typeOfT)) { logger.log(Level.WARNING, "Overriding the existing type handler for {0}", typeOfT); @@ -53,20 +71,24 @@ public synchronized void registerIfAbsent(ParameterizedTypeHandlerMap<T> other) { if (!modifiable) { - throw new IllegalStateException( - "Attempted to modify an unmodifiable map."); + throw new IllegalStateException("Attempted to modify an unmodifiable map."); } - for (Map.Entry<Type, T> entry : other.entrySet()) { + for (Map.Entry<Type, T> entry : other.map.entrySet()) { if (!map.containsKey(entry.getKey())) { register(entry.getKey(), entry.getValue()); } } + for (Pair<Class<?>, T> entry : other.typeHierarchyList) { + int index = getIndexOfSpecificHandlerForTypeHierarchy(entry.first); + if (index < 0) { + registerForTypeHierarchy(entry); + } + } } public synchronized void registerIfAbsent(Type typeOfT, T value) { if (!modifiable) { - throw new IllegalStateException( - "Attempted to modify an unmodifiable map."); + throw new IllegalStateException("Attempted to modify an unmodifiable map."); } if (!map.containsKey(typeOfT)) { register(typeOfT, value); @@ -84,41 +106,63 @@ if (rawClass != type) { handler = getHandlerFor(rawClass); } - - // Check for map or collection if (handler == null) { - if (Map.class.isAssignableFrom(rawClass)) { - handler = map.get(Map.class); - } else if (Collection.class.isAssignableFrom(rawClass)) { - handler = map.get(Collection.class); - } else if (Enum.class.isAssignableFrom(rawClass)) { - handler = map.get(Enum.class); - } + // check if something registered for type hierarchy + handler = getHandlerForTypeHierarchy(rawClass); } } return handler; } + @SuppressWarnings("unchecked") + 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 map.containsKey(type); } + private synchronized int getIndexOfSpecificHandlerForTypeHierarchy(Class<?> type) { + for (int i = typeHierarchyList.size()-1; i >= 0; --i) { + if (type.equals(typeHierarchyList.get(i).first)) { + return i; + } + } + return -1; + } + public synchronized ParameterizedTypeHandlerMap<T> copyOf() { ParameterizedTypeHandlerMap<T> copy = new ParameterizedTypeHandlerMap<T>(); for (Map.Entry<Type, T> entry : map.entrySet()) { copy.register(entry.getKey(), entry.getValue()); } + for (Pair<Class<?>, T> entry : typeHierarchyList) { + copy.registerForTypeHierarchy(entry); + } return copy; } - public synchronized Set<Map.Entry<Type, T>> entrySet() { - return map.entrySet(); - } - @Override public String toString() { - StringBuilder sb = new StringBuilder("{"); + StringBuilder sb = new StringBuilder("{mapForTypeHierarchy:{"); boolean first = true; + for (Pair<Class<?>, T> entry : typeHierarchyList) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(typeToString(entry.first)).append(':'); + sb.append(entry.second); + } + sb.append("},map:{"); + first = true; for (Map.Entry<Type, T> entry : map.entrySet()) { if (first) { first = false; @@ -128,6 +172,7 @@ sb.append(typeToString(entry.getKey())).append(':'); sb.append(entry.getValue()); } + sb.append("}"); return sb.toString(); }
diff --git a/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java b/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java index cd05b1c..82c19ae 100644 --- a/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java +++ b/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java
@@ -16,6 +16,8 @@ package com.google.gson; +import com.google.gson.common.TestTypes.Base; +import com.google.gson.common.TestTypes.Sub; import com.google.gson.reflect.TypeToken; import junit.framework.TestCase; @@ -84,4 +86,29 @@ 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 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); + assertEquals("baseHandler", handler); + } + + private static class SubOfSub extends Sub { + } }
diff --git a/gson/src/test/java/com/google/gson/functional/MapTest.java b/gson/src/test/java/com/google/gson/functional/MapTest.java index 6e084e0..909641f 100755 --- a/gson/src/test/java/com/google/gson/functional/MapTest.java +++ b/gson/src/test/java/com/google/gson/functional/MapTest.java
@@ -183,6 +183,14 @@ assertTrue(json.contains("\"a\":\"b\"")); } + public void testMapStandardSubclassDeserialization() { + String json = "{a:'1',b:'2'}"; + Type type = new TypeToken<LinkedHashMap<String, String>>() {}.getType(); + LinkedHashMap<String, Integer> map = gson.fromJson(json, type); + assertEquals("1", map.get("a")); + assertEquals("2", map.get("b")); + } + public void disable_testMapSubclassDeserialization() { Gson gson = new GsonBuilder().registerTypeAdapter(MyMap.class, new InstanceCreator<MyMap>() { public MyMap createInstance(Type type) {