Fix enclosed types with adapter methods.

This was broken becase reflection was providing owner types but our
API didn't have a way to specify them.

Closes: https://github.com/square/moshi/issues/148
This commit is contained in:
jwilson
2016-10-15 10:02:04 -04:00
parent fce8fc6976
commit 2b5301f737
3 changed files with 69 additions and 17 deletions

View File

@@ -18,7 +18,6 @@ package com.squareup.moshi;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration; import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable; import java.lang.reflect.TypeVariable;
@@ -37,12 +36,22 @@ public final class Types {
} }
/** /**
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
* method if {@code rawType} is not enclosed in another type.
*/ */
public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) { public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
return new ParameterizedTypeImpl(null, rawType, typeArguments); return new ParameterizedTypeImpl(null, rawType, typeArguments);
} }
/**
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
* method if {@code rawType} is enclosed in {@code ownerType}.
*/
public static ParameterizedType newParameterizedTypeWithOwner(
Type ownerType, Type rawType, Type... typeArguments) {
return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
}
/** Returns an array type whose elements are all instances of {@code componentType}. */ /** Returns an array type whose elements are all instances of {@code componentType}. */
public static GenericArrayType arrayOf(Type componentType) { public static GenericArrayType arrayOf(Type componentType) {
return new GenericArrayTypeImpl(componentType); return new GenericArrayTypeImpl(componentType);
@@ -405,12 +414,11 @@ public final class Types {
final Type[] typeArguments; final Type[] typeArguments;
ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
// require an owner type if the raw type needs it // Require an owner type if the raw type needs it.
if (rawType instanceof Class<?>) { if (rawType instanceof Class<?>
Class<?> rawTypeAsClass = (Class<?>) rawType; && (ownerType == null) != (((Class<?>) rawType).getEnclosingClass() == null)) {
boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers()) throw new IllegalArgumentException(
|| rawTypeAsClass.getEnclosingClass() == null; "unexpected owner type for " + rawType + ": " + ownerType);
if (ownerType == null && !isStaticOrTopLevelClass) throw new IllegalArgumentException();
} }
this.ownerType = ownerType == null ? null : canonicalize(ownerType); this.ownerType = ownerType == null ? null : canonicalize(ownerType);

View File

@@ -22,6 +22,7 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -547,6 +548,44 @@ public final class AdapterMethodsTest {
} }
} }
@Test public void adaptedTypeIsEnclosedParameterizedType() throws Exception {
Moshi moshi = new Moshi.Builder()
.add(new EnclosedParameterizedTypeJsonAdapter())
.build();
JsonAdapter<Box<Point>> boxAdapter = moshi.adapter(Types.newParameterizedTypeWithOwner(
AdapterMethodsTest.class, Box.class, Point.class));
Box<Point> box = new Box<>(new Point(5, 8));
String json = "[{\"x\":5,\"y\":8}]";
assertThat(boxAdapter.toJson(box)).isEqualTo(json);
assertThat(boxAdapter.fromJson(json)).isEqualTo(box);
}
static class EnclosedParameterizedTypeJsonAdapter {
@FromJson Box<Point> boxFromJson(List<Point> points) {
return new Box<>(points.get(0));
}
@ToJson List<Point> boxToJson(Box<Point> box) throws Exception {
return Collections.singletonList(box.data);
}
}
static class Box<T> {
final T data;
public Box(T data) {
this.data = data;
}
@Override public boolean equals(Object o) {
return o instanceof Box && ((Box) o).data.equals(data);
}
@Override public int hashCode() {
return data.hashCode();
}
}
static class Point { static class Point {
final int x; final int x;
final int y; final int y;

View File

@@ -34,27 +34,32 @@ public final class TypesTest {
assertThat(getFirstTypeArgument(type)).isEqualTo(A.class); assertThat(getFirstTypeArgument(type)).isEqualTo(A.class);
// A<B>. A is a static inner class. // A<B>. A is a static inner class.
type = Types.newParameterizedType(A.class, B.class); type = Types.newParameterizedTypeWithOwner(TypesTest.class, A.class, B.class);
assertThat(getFirstTypeArgument(type)).isEqualTo(B.class); assertThat(getFirstTypeArgument(type)).isEqualTo(B.class);
final class D {
} }
@Test public void parameterizedTypeWithRequiredOwnerMissing() throws Exception {
try { try {
// D<A> is not allowed since D is not a static inner class. Types.newParameterizedType(A.class, B.class);
Types.newParameterizedType(D.class, A.class);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("unexpected owner type for " + A.class + ": null");
}
} }
// A<D> is allowed. @Test public void parameterizedTypeWithUnnecessaryOwnerProvided() throws Exception {
type = Types.newParameterizedType(A.class, D.class); try {
assertThat(getFirstTypeArgument(type)).isEqualTo(D.class); Types.newParameterizedTypeWithOwner(A.class, List.class, B.class);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("unexpected owner type for " + List.class + ": " + A.class);
}
} }
@Test public void getFirstTypeArgument() throws Exception { @Test public void getFirstTypeArgument() throws Exception {
assertThat(getFirstTypeArgument(A.class)).isNull(); assertThat(getFirstTypeArgument(A.class)).isNull();
Type type = Types.newParameterizedType(A.class, B.class, C.class); Type type = Types.newParameterizedTypeWithOwner(TypesTest.class, A.class, B.class, C.class);
assertThat(getFirstTypeArgument(type)).isEqualTo(B.class); assertThat(getFirstTypeArgument(type)).isEqualTo(B.class);
} }