Refine ResolvableType class

- Support for serialization
- Allow programmatic creation of an array from a given component type
- Allow programmatic creation with given generics
- Extract generics from Class types using Class.getTypeParameters()
- Move TypeVariableResolver to an inner class (and make method private)
- Refine 'resolve()' algorithm

Issue: SPR-10973
This commit is contained in:
Phillip Webb
2013-10-16 23:17:22 -07:00
parent 7ffd05a8e3
commit 3337fd32cb
6 changed files with 901 additions and 200 deletions

View File

@@ -16,6 +16,10 @@
package org.springframework.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -44,12 +48,14 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.ResolvableType.VariableResolver;
import org.springframework.util.MultiValueMap;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Tests for {@link ResolvableType}.
@@ -86,7 +92,6 @@ public class ResolvableTypeTests {
assertThat(none.resolve(String.class), equalTo((Class) String.class));
assertThat(none.resolveGeneric(0), nullValue());
assertThat(none.resolveGenerics().length, equalTo(0));
assertThat(none.resolveVariable(mock(TypeVariable.class)), nullValue());
assertThat(none.toString(), equalTo("?"));
assertThat(none.isAssignableFrom(ResolvableType.forClass(Object.class)), equalTo(false));
}
@@ -415,6 +420,13 @@ public class ResolvableTypeTests {
assertThat(type.getGeneric().getGeneric().getType(), equalTo((Type) String.class));
}
@Test
public void genericOfGenericWithAs() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList")).asCollection();
assertThat(type.toString(), equalTo("java.util.Collection<java.util.List<java.lang.String>>"));
assertThat(type.getGeneric().asCollection().toString(), equalTo("java.util.Collection<java.lang.String>"));
}
@Test
public void getGenericOfGenericByIndexes() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList"));
@@ -437,13 +449,21 @@ public class ResolvableTypeTests {
}
@Test
public void getGenerics() throws Exception {
public void getGenericsFromParameterizedType() throws Exception {
ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class);
ResolvableType[] generics = type.getGenerics();
assertThat(generics.length, equalTo(1));
assertThat(generics[0].resolve(), equalTo((Class) CharSequence.class));
}
@Test
public void getGenericsFromClass() throws Exception {
ResolvableType type = ResolvableType.forClass(List.class);
ResolvableType[] generics = type.getGenerics();
assertThat(generics.length, equalTo(1));
assertThat(generics[0].getType().toString(), equalTo("E"));
}
@Test
public void noGetGenerics() throws Exception {
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
@@ -549,9 +569,8 @@ public class ResolvableTypeTests {
public void doesResolveFromOuterOwner() throws Exception {
ResolvableType type = ResolvableType.forField(
Fields.class.getField("listOfListOfUnknown")).as(Collection.class);
ResolvableType generic = type.getGeneric(0);
assertThat(generic.resolve(), equalTo((Class) List.class));
assertThat(generic.as(Collection.class).getGeneric(0).as(Collection.class).resolve(), nullValue());
assertThat(type.getGeneric(0).resolve(), equalTo((Class) List.class));
assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve(), nullValue());
}
@Test
@@ -721,15 +740,16 @@ public class ResolvableTypeTests {
public void resolveTypeVariableFromTypeWithVariableResolver() throws Exception {
Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType();
ResolvableType type = ResolvableType.forType(
sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class));
sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class).asVariableResolver());
assertThat(type.resolve(), equalTo((Class) String.class));
assertThat(type.getType().toString(), equalTo("T"));
}
@Test
public void resolveTypeWithCustomVariableResolver() throws Exception {
TypeVariableResolver variableResolver = mock(TypeVariableResolver.class);
given(variableResolver.resolveVariable((TypeVariable<?>) anyObject())).willReturn(Long.class);
VariableResolver variableResolver = mock(VariableResolver.class);
ResolvableType longType = ResolvableType.forClass(Long.class);
given(variableResolver.resolveVariable((TypeVariable<?>) anyObject())).willReturn(longType);
ResolvableType variable = ResolvableType.forType(
Fields.class.getField("typeVariableType").getGenericType(), variableResolver);
@@ -747,10 +767,10 @@ public class ResolvableTypeTests {
public void toStrings() throws Exception {
assertThat(ResolvableType.NONE.toString(), equalTo("?"));
assertFieldToStringValue("classType", "java.util.List");
assertFieldToStringValue("classType", "java.util.List<?>");
assertFieldToStringValue("typeVariableType", "?");
assertFieldToStringValue("parameterizedType", "java.util.List<?>");
assertFieldToStringValue("arrayClassType", "java.util.List[]");
assertFieldToStringValue("arrayClassType", "java.util.List<?>[]");
assertFieldToStringValue("genericArrayType", "java.util.List<java.lang.String>[]");
assertFieldToStringValue("genericMultiArrayType", "java.util.List<java.lang.String>[][][]");
assertFieldToStringValue("wildcardType", "java.util.List<java.lang.Number>");
@@ -761,7 +781,7 @@ public class ResolvableTypeTests {
assertFieldToStringValue("stringArrayList", "java.util.List<java.lang.String[]>");
assertFieldToStringValue("stringIntegerMultiValueMap", "org.springframework.util.MultiValueMap<java.lang.String, java.lang.Integer>");
assertFieldToStringValue("stringIntegerMultiValueMapSwitched", VariableNameSwitch.class.getName() + "<java.lang.Integer, java.lang.String>");
assertFieldToStringValue("listOfListOfUnknown", "java.util.List<java.util.List>");
assertFieldToStringValue("listOfListOfUnknown", "java.util.List<java.util.List<?>>");
assertTypedFieldToStringValue("typeVariableType", "java.lang.String");
assertTypedFieldToStringValue("parameterizedType", "java.util.List<java.lang.String>");
@@ -789,6 +809,17 @@ public class ResolvableTypeTests {
assertThat(type.resolve(), equalTo((Type) Integer.class));
}
@Test
public void resolveFromClassWithGenerics() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ResolvableType.forClassWithGenerics(List.class, String.class));
assertThat(type.asCollection().toString(), equalTo("java.util.Collection<java.util.List<java.lang.String>>"));
assertThat(type.asCollection().getGeneric().toString(), equalTo("java.util.List<java.lang.String>"));
assertThat(type.asCollection().getGeneric().asCollection().toString(), equalTo("java.util.Collection<java.lang.String>"));
assertThat(type.toString(), equalTo("java.util.List<java.util.List<java.lang.String>>"));
assertThat(type.asCollection().getGeneric().getGeneric().resolve(), equalTo((Type) String.class));
}
@Test
public void isAssignableFromMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
@@ -997,7 +1028,7 @@ public class ResolvableTypeTests {
public void hashCodeAndEquals() throws Exception {
ResolvableType forClass = ResolvableType.forClass(List.class);
ResolvableType forFieldDirect = ResolvableType.forField(Fields.class.getDeclaredField("stringList"));
ResolvableType forFieldViaType = ResolvableType.forType(Fields.class.getDeclaredField("stringList").getGenericType());
ResolvableType forFieldViaType = ResolvableType.forType(Fields.class.getDeclaredField("stringList").getGenericType(), (VariableResolver) null);
ResolvableType forFieldWithImplementation = ResolvableType.forField(Fields.class.getDeclaredField("stringList"), TypedFields.class);
assertThat(forClass, equalTo(forClass));
@@ -1024,6 +1055,60 @@ public class ResolvableTypeTests {
assertThat(t.resolveGeneric(1, 0), equalTo((Class) String.class));
}
@Test
public void forClassWithGenerics() throws Exception {
ResolvableType elementType = ResolvableType.forClassWithGenerics(Map.class, Integer.class, String.class);
ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
assertThat(listType.toString(), equalTo("java.util.List<java.util.Map<java.lang.Integer, java.lang.String>>"));
}
@Test
public void classWithGenericsAs() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(MultiValueMap.class, Integer.class, String.class);
assertThat(type.asMap().toString(), equalTo("java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>"));
}
@Test
public void forClassWithMismatchedGenerics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Missmatched number of generics specified");
ResolvableType.forClassWithGenerics(Map.class, Integer.class);
}
@Test
public void forArrayComponent() throws Exception {
ResolvableType elementType = ResolvableType.forField(Fields.class.getField("stringList"));
ResolvableType type = ResolvableType.forArrayComponent(elementType);
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
assertThat(type.resolve(), equalTo((Class) List[].class));
}
@Test
public void serialize() throws Exception {
testSerialization(ResolvableType.forClass(List.class));
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")));
testSerialization(ResolvableType.forMethodParameter(Methods.class.getMethod("charSequenceParameter", List.class), 0));
testSerialization(ResolvableType.forMethodReturnType(Methods.class.getMethod("charSequenceReturn")));
testSerialization(ResolvableType.forConstructorParameter(Constructors.class.getConstructor(List.class), 0));
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")).getGeneric());
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")).asCollection());
testSerialization(ResolvableType.forClass(ExtendsMap.class).getSuperType());
ResolvableType deserializedNone = testSerialization(ResolvableType.NONE);
assertThat(deserializedNone, sameInstance(ResolvableType.NONE));
}
private ResolvableType testSerialization(ResolvableType type) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(type);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ResolvableType read = (ResolvableType) ois.readObject();
assertThat(read, equalTo(type));
assertThat(read.getType(), equalTo(type.getType()));
assertThat(read.resolve(), equalTo((Class) type.resolve()));
return read;
}
private static AssertAssignbleMatcher assertAssignable(final ResolvableType type,
final ResolvableType... fromTypes) {

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Tests for {@link SerializableTypeWrapper}.
*
* @author Phillip Webb
*/
public class SerializableTypeWrapperTests {
@Test
public void forField() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
}
@Test
public void forMethodParameter() throws Exception {
Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(method, 0));
assertThat(type.toString(), equalTo("java.lang.Class<T>"));
assertSerialzable(type);
}
@Test
public void forConstructor() throws Exception {
Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, 0));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
}
@Test
public void forGenericSuperClass() throws Exception {
Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class);
assertThat(type.toString(), equalTo("java.util.AbstractList<E>"));
assertSerialzable(type);
}
@Test
public void forGenericInterfaces() throws Exception {
Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0];
assertThat(type.toString(), equalTo("java.util.Collection<E>"));
assertSerialzable(type);
}
@Test
public void forTypeParamters() throws Exception {
Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0];
assertThat(type.toString(), equalTo("E"));
assertSerialzable(type);
}
@Test
public void classType() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType"));
assertThat(type.toString(), equalTo("class java.lang.String"));
assertSerialzable(type);
}
@Test
public void genericArrayType() throws Exception {
GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
assertSerialzable(type);
assertSerialzable(type.getGenericComponentType());
}
@Test
public void parameterizedType() throws Exception {
ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
assertSerialzable(type.getOwnerType());
assertSerialzable(type.getRawType());
assertSerialzable(type.getActualTypeArguments());
assertSerialzable(type.getActualTypeArguments()[0]);
}
@Test
public void typeVariableType() throws Exception {
TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType"));
assertThat(type.toString(), equalTo("T"));
assertSerialzable(type);
assertSerialzable(type.getBounds());
}
@Test
public void wildcardType() throws Exception {
ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType"));
WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0];
assertThat(type.toString(), equalTo("? extends java.lang.CharSequence"));
assertSerialzable(type);
assertSerialzable(type.getLowerBounds());
assertSerialzable(type.getUpperBounds());
}
private void assertSerialzable(Object source) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(source);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
assertThat(ois.readObject(), equalTo(source));
}
static class Fields<T> {
public String classType;
public List<String>[] genericArrayType;
public List<String> parameterizedType;
public T typeVariableType;
public List<? extends CharSequence> wildcardType;
}
static interface Methods {
<T> List<T> method(Class<T> p1, T p2);
}
static class Constructors {
public Constructors(List<String> p) {
}
}
}