diff --git a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java index 3f75ad5..c3cf28a 100644 --- a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java @@ -228,7 +228,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory { List adapterMethods, Type type, Set annotations) { for (int i = 0, size = adapterMethods.size(); i < size; i++) { AdapterMethod adapterMethod = adapterMethods.get(i); - if (Types.equals(adapterMethod.type, type) && adapterMethod.annotations.equals(annotations)) { + if (adapterMethod.type.equals(type) && adapterMethod.annotations.equals(annotations)) { return adapterMethod; } } @@ -244,7 +244,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory { public AdapterMethod(Type type, Set annotations, Object adapter, Method method, boolean nullable) { - this.type = type; + this.type = Types.canonicalize(type); this.annotations = annotations; this.adapter = adapter; this.method = method; diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.java b/moshi/src/main/java/com/squareup/moshi/Moshi.java index ccbdb18..9ed05c1 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.java +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -63,6 +62,8 @@ public final class Moshi { @SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters. public JsonAdapter adapter(Type type, Set annotations) { + type = Types.canonicalize(type); + // If there's an equivalent adapter in the cache, we're done! Object cacheKey = cacheKey(type, annotations); synchronized (adapterCache) { @@ -111,6 +112,8 @@ public final class Moshi { @SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters. public JsonAdapter nextAdapter(JsonAdapter.Factory skipPast, Type type, Set annotations) { + type = Types.canonicalize(type); + int skipPastIndex = factories.indexOf(skipPast); if (skipPastIndex == -1) { throw new IllegalArgumentException("Unable to skip past unknown factory " + skipPast); diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index 56feb8c..1235232 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -76,15 +76,18 @@ public final class Types { return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; } else if (type instanceof ParameterizedType) { + if (type instanceof ParameterizedTypeImpl) return type; ParameterizedType p = (ParameterizedType) type; return new ParameterizedTypeImpl(p.getOwnerType(), p.getRawType(), p.getActualTypeArguments()); } else if (type instanceof GenericArrayType) { + if (type instanceof GenericArrayTypeImpl) return type; GenericArrayType g = (GenericArrayType) type; return new GenericArrayTypeImpl(g.getGenericComponentType()); } else if (type instanceof WildcardType) { + if (type instanceof WildcardTypeImpl) return type; WildcardType w = (WildcardType) type; return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); @@ -172,7 +175,7 @@ public final class Types { && va.getName().equals(vb.getName()); } else { - // This isn't a supported type. Could be a generic array type, wildcard type, etc. + // This isn't a supported type. return false; } } diff --git a/moshi/src/test/java/com/squareup/moshi/AdapterMethodsTest.java b/moshi/src/test/java/com/squareup/moshi/AdapterMethodsTest.java index 1271c27..3a3b2fc 100644 --- a/moshi/src/test/java/com/squareup/moshi/AdapterMethodsTest.java +++ b/moshi/src/test/java/com/squareup/moshi/AdapterMethodsTest.java @@ -369,19 +369,7 @@ public final class AdapterMethodsTest { .build(); // This class doesn't implement equals() and hashCode() as it should. - ParameterizedType listOfStringType = new ParameterizedType() { - @Override public Type[] getActualTypeArguments() { - return new Type[] { String.class }; - } - - @Override public Type getRawType() { - return List.class; - } - - @Override public Type getOwnerType() { - return null; - } - }; + ParameterizedType listOfStringType = brokenParameterizedType(0, List.class, String.class); JsonAdapter> jsonAdapter = moshi.adapter(listOfStringType); assertThat(jsonAdapter.toJson(Arrays.asList("a", "b", "c"))).isEqualTo("\"a|b|c\""); @@ -403,6 +391,21 @@ public final class AdapterMethodsTest { } } + /** + * Even when the types we use to look up JSON adapters are not equal, if they're equivalent they + * should return the same JsonAdapter instance. + */ + @Test public void parameterizedTypeCacheKey() throws Exception { + Moshi moshi = new Moshi.Builder().build(); + + Type a = brokenParameterizedType(0, List.class, String.class); + Type b = brokenParameterizedType(1, List.class, String.class); + Type c = brokenParameterizedType(2, List.class, String.class); + + assertThat(moshi.adapter(b)).isSameAs(moshi.adapter(a)); + assertThat(moshi.adapter(c)).isSameAs(moshi.adapter(a)); + } + static class Point { final int x; final int y; @@ -424,4 +427,34 @@ public final class AdapterMethodsTest { interface Shape { String draw(); } + + /** + * Returns a new parameterized type that doesn't implement {@link Object#equals} or {@link + * Object#hashCode} by value. These implementation defects are consistent with the parameterized + * type that shipped in some older versions of Android. + */ + ParameterizedType brokenParameterizedType( + final int hashCode, final Class rawType, final Type... typeArguments) { + return new ParameterizedType() { + @Override public Type[] getActualTypeArguments() { + return typeArguments; + } + + @Override public Type getRawType() { + return rawType; + } + + @Override public Type getOwnerType() { + return null; + } + + @Override public boolean equals(Object other) { + return other == this; + } + + @Override public int hashCode() { + return hashCode; + } + }; + } }