mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Merge pull request #201 from square/jwilson.1015.fix_enclosed_types
Fix enclosed types with adapter methods.
This commit is contained in:
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user