diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 695b89a0..6699664c 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -110,7 +110,6 @@ final class DefaultTypeAdapters { map.register(UUID.class, UUUID_TYPE_ADAPTER); map.register(Locale.class, LOCALE_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); @@ -148,7 +147,6 @@ final class DefaultTypeAdapters { map.register(UUID.class, wrapDeserializer(UUUID_TYPE_ADAPTER)); map.register(Locale.class, wrapDeserializer(LOCALE_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)); @@ -186,14 +184,13 @@ final class DefaultTypeAdapters { private static ParameterizedTypeHandlerMap> createDefaultInstanceCreators() { ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>(); - map.register(Map.class, MAP_TYPE_ADAPTER); + map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER); // Add Collection type instance creators map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER); - map.register(Set.class, HASH_SET_CREATOR); - map.register(SortedSet.class, TREE_SET_CREATOR); - map.register(TreeSet.class, TREE_SET_CREATOR); + map.registerForTypeHierarchy(Set.class, HASH_SET_CREATOR); + map.registerForTypeHierarchy(SortedSet.class, TREE_SET_CREATOR); map.register(Properties.class, PROPERTIES_CREATOR); map.makeUnmodifiable(); return map; diff --git a/gson/src/main/java/com/google/gson/Pair.java b/gson/src/main/java/com/google/gson/Pair.java index 66799380..66e746c9 100644 --- a/gson/src/main/java/com/google/gson/Pair.java +++ b/gson/src/main/java/com/google/gson/Pair.java @@ -54,4 +54,9 @@ final class Pair { private static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } + + @Override + public String toString() { + return String.format("{%s,%s}", first, second); + } } \ No newline at end of file diff --git a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java index e7cab3f8..2f67a7a5 100644 --- a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java +++ b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java @@ -54,11 +54,27 @@ final class ParameterizedTypeHandlerMap { logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first); typeHierarchyList.remove(index); } + index = getIndexOfAnOverriddenHandler(pair.first); + if (index >= 0) { + 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 int getIndexOfAnOverriddenHandler(Class type) { + for (int i = typeHierarchyList.size()-1; i >= 0; --i) { + Pair, 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."); @@ -78,7 +94,10 @@ final class ParameterizedTypeHandlerMap { register(entry.getKey(), entry.getValue()); } } - for (Pair, T> entry : other.typeHierarchyList) { + // 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.typeHierarchyList.size()-1; i >= 0; --i) { + Pair, T> entry = other.typeHierarchyList.get(i); int index = getIndexOfSpecificHandlerForTypeHierarchy(entry.first); if (index < 0) { registerForTypeHierarchy(entry); diff --git a/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java b/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java index 82c19ae1..dbd00899 100644 --- a/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java +++ b/gson/src/test/java/com/google/gson/ParameterizedTypeHandlerMapTest.java @@ -109,6 +109,21 @@ public class ParameterizedTypeHandlerMapTest extends TestCase { assertEquals("baseHandler", 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 { } }