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:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user