Skip to content

Commit

Permalink
Fix enclosed types with adapter methods.
Browse files Browse the repository at this point in the history
This was broken becase reflection was providing owner types but our
API didn't have a way to specify them.

Closes: #148
  • Loading branch information
swankjesse committed Oct 15, 2016
1 parent fce8fc6 commit 2b5301f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 17 deletions.
24 changes: 16 additions & 8 deletions moshi/src/main/java/com/squareup/moshi/Types.java
Expand Up @@ -18,7 +18,6 @@
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
Expand All @@ -37,12 +36,22 @@ private 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) {
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}. */
public static GenericArrayType arrayOf(Type componentType) {
return new GenericArrayTypeImpl(componentType);
Expand Down Expand Up @@ -405,12 +414,11 @@ private static final class ParameterizedTypeImpl implements ParameterizedType {
final Type[] typeArguments;

ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
// require an owner type if the raw type needs it
if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType;
boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers())
|| rawTypeAsClass.getEnclosingClass() == null;
if (ownerType == null && !isStaticOrTopLevelClass) throw new IllegalArgumentException();
// Require an owner type if the raw type needs it.
if (rawType instanceof Class<?>
&& (ownerType == null) != (((Class<?>) rawType).getEnclosingClass() == null)) {
throw new IllegalArgumentException(
"unexpected owner type for " + rawType + ": " + ownerType);
}

this.ownerType = ownerType == null ? null : canonicalize(ownerType);
Expand Down
39 changes: 39 additions & 0 deletions moshi/src/test/java/com/squareup/moshi/AdapterMethodsTest.java
Expand Up @@ -22,6 +22,7 @@
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -547,6 +548,44 @@ static class FromJsonAdapterTakingJsonAdapterParameter {
}
}

@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 {
final int x;
final int y;
Expand Down
23 changes: 14 additions & 9 deletions moshi/src/test/java/com/squareup/moshi/TypesTest.java
Expand Up @@ -34,27 +34,32 @@ public final class TypesTest {
assertThat(getFirstTypeArgument(type)).isEqualTo(A.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);
}

final class D {
}
@Test public void parameterizedTypeWithRequiredOwnerMissing() throws Exception {
try {
// D<A> is not allowed since D is not a static inner class.
Types.newParameterizedType(D.class, A.class);
Types.newParameterizedType(A.class, B.class);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("unexpected owner type for " + A.class + ": null");
}
}

// A<D> is allowed.
type = Types.newParameterizedType(A.class, D.class);
assertThat(getFirstTypeArgument(type)).isEqualTo(D.class);
@Test public void parameterizedTypeWithUnnecessaryOwnerProvided() throws Exception {
try {
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 {
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);
}

Expand Down

0 comments on commit 2b5301f

Please sign in to comment.