/* * Copyright 2002-2019 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 * * https://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.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; 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.AbstractCollection; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.Callable; import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.ResolvableType.VariableResolver; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * Tests for {@link ResolvableType}. * * @author Phillip Webb * @author Juergen Hoeller * @author Sebastien Deleuze */ @SuppressWarnings("rawtypes") @ExtendWith(MockitoExtension.class) class ResolvableTypeTests { @Captor private ArgumentCaptor> typeVariableCaptor; @Test void noneReturnValues() throws Exception { ResolvableType none = ResolvableType.NONE; assertThat(none.as(Object.class)).isEqualTo(ResolvableType.NONE); assertThat(none.asCollection()).isEqualTo(ResolvableType.NONE); assertThat(none.asMap()).isEqualTo(ResolvableType.NONE); assertThat(none.getComponentType()).isEqualTo(ResolvableType.NONE); assertThat(none.getGeneric(0)).isEqualTo(ResolvableType.NONE); assertThat(none.getGenerics().length).isEqualTo(0); assertThat(none.getInterfaces().length).isEqualTo(0); assertThat(none.getSuperType()).isEqualTo(ResolvableType.NONE); assertThat(none.getType()).isEqualTo(ResolvableType.EmptyType.INSTANCE); assertThat(none.hasGenerics()).isEqualTo(false); assertThat(none.isArray()).isEqualTo(false); assertThat(none.resolve()).isNull(); assertThat(none.resolve(String.class)).isEqualTo(String.class); assertThat(none.resolveGeneric(0)).isNull(); assertThat(none.resolveGenerics().length).isEqualTo(0); assertThat(none.toString()).isEqualTo("?"); assertThat(none.hasUnresolvableGenerics()).isEqualTo(false); assertThat(none.isAssignableFrom(ResolvableType.forClass(Object.class))).isEqualTo(false); } @Test void forClass() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.getType()).isEqualTo(ExtendsList.class); assertThat(type.getRawClass()).isEqualTo(ExtendsList.class); assertThat(type.isAssignableFrom(ExtendsList.class)).isTrue(); assertThat(type.isAssignableFrom(ArrayList.class)).isFalse(); } @Test void forClassWithNull() throws Exception { ResolvableType type = ResolvableType.forClass(null); assertThat(type.getType()).isEqualTo(Object.class); assertThat(type.getRawClass()).isEqualTo(Object.class); assertThat(type.isAssignableFrom(Object.class)).isTrue(); assertThat(type.isAssignableFrom(String.class)).isTrue(); } @Test void forRawClass() throws Exception { ResolvableType type = ResolvableType.forRawClass(ExtendsList.class); assertThat(type.getType()).isEqualTo(ExtendsList.class); assertThat(type.getRawClass()).isEqualTo(ExtendsList.class); assertThat(type.isAssignableFrom(ExtendsList.class)).isTrue(); assertThat(type.isAssignableFrom(ArrayList.class)).isFalse(); } @Test void forRawClassWithNull() throws Exception { ResolvableType type = ResolvableType.forRawClass(null); assertThat(type.getType()).isEqualTo(Object.class); assertThat(type.getRawClass()).isEqualTo(Object.class); assertThat(type.isAssignableFrom(Object.class)).isTrue(); assertThat(type.isAssignableFrom(String.class)).isTrue(); } @Test // gh-23321 void forRawClassAssignableFromTypeVariable() throws Exception { ResolvableType typeVariable = ResolvableType.forClass(ExtendsList.class).as(List.class).getGeneric(); ResolvableType raw = ResolvableType.forRawClass(CharSequence.class); assertThat(raw.resolve()).isEqualTo(CharSequence.class); assertThat(typeVariable.resolve()).isEqualTo(CharSequence.class); assertThat(raw.resolve().isAssignableFrom(typeVariable.resolve())).isTrue(); assertThat(typeVariable.resolve().isAssignableFrom(raw.resolve())).isTrue(); assertThat(raw.isAssignableFrom(typeVariable)).isTrue(); assertThat(typeVariable.isAssignableFrom(raw)).isTrue(); } @Test void forInstanceMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forInstance(null)) .withMessageContaining("Instance must not be null"); } @Test void forInstanceNoProvider() throws Exception { ResolvableType type = ResolvableType.forInstance(new Object()); assertThat(type.getType()).isEqualTo(Object.class); assertThat(type.resolve()).isEqualTo(Object.class); } @Test void forInstanceProvider() throws Exception { ResolvableType type = ResolvableType.forInstance(new MyGenericInterfaceType<>(String.class)); assertThat(type.getRawClass()).isEqualTo(MyGenericInterfaceType.class); assertThat(type.getGeneric().resolve()).isEqualTo(String.class); } @Test void forInstanceProviderNull() throws Exception { ResolvableType type = ResolvableType.forInstance(new MyGenericInterfaceType(null)); assertThat(type.getType()).isEqualTo(MyGenericInterfaceType.class); assertThat(type.resolve()).isEqualTo(MyGenericInterfaceType.class); } @Test void forField() throws Exception { Field field = Fields.class.getField("charSequenceList"); ResolvableType type = ResolvableType.forField(field); assertThat(type.getType()).isEqualTo(field.getGenericType()); } @Test void forPrivateField() throws Exception { Field field = Fields.class.getDeclaredField("privateField"); ResolvableType type = ResolvableType.forField(field); assertThat(type.getType()).isEqualTo(field.getGenericType()); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.getSource()).isSameAs(field); Field field2 = Fields.class.getDeclaredField("otherPrivateField"); ResolvableType type2 = ResolvableType.forField(field2); assertThat(type2.getType()).isEqualTo(field2.getGenericType()); assertThat(type2.resolve()).isEqualTo(List.class); assertThat(type2.getSource()).isSameAs(field2); assertThat(type2).isEqualTo(type); assertThat(type2.hashCode()).isEqualTo(type.hashCode()); } @Test void forFieldMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forField(null)) .withMessageContaining("Field must not be null"); } @Test void forConstructorParameter() throws Exception { Constructor constructor = Constructors.class.getConstructor(List.class); ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0); assertThat(type.getType()).isEqualTo(constructor.getGenericParameterTypes()[0]); } @Test void forConstructorParameterMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forConstructorParameter(null, 0)) .withMessageContaining("Constructor must not be null"); } @Test void forMethodParameterByIndex() throws Exception { Method method = Methods.class.getMethod("charSequenceParameter", List.class); ResolvableType type = ResolvableType.forMethodParameter(method, 0); assertThat(type.getType()).isEqualTo(method.getGenericParameterTypes()[0]); } @Test void forMethodParameterByIndexMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forMethodParameter(null, 0)) .withMessageContaining("Method must not be null"); } @Test void forMethodParameter() throws Exception { Method method = Methods.class.getMethod("charSequenceParameter", List.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.getType()).isEqualTo(method.getGenericParameterTypes()[0]); } @Test @SuppressWarnings("deprecation") void forMethodParameterWithNesting() throws Exception { Method method = Methods.class.getMethod("nested", Map.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.increaseNestingLevel(); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(Byte.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(Long.class); } @Test @SuppressWarnings("deprecation") void forMethodParameterWithNestingAndLevels() throws Exception { Method method = Methods.class.getMethod("nested", Map.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.increaseNestingLevel(); methodParameter.setTypeIndexForCurrentLevel(0); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(String.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(Integer.class); } @Test void forMethodParameterMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forMethodParameter(null)) .withMessageContaining("MethodParameter must not be null"); } @Test // SPR-16210 void forMethodParameterWithSameSignatureAndGenerics() throws Exception { Method method = Methods.class.getMethod("list1"); MethodParameter methodParameter = MethodParameter.forExecutable(method, -1); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(((MethodParameter)type.getSource()).getMethod()).isEqualTo(method); method = Methods.class.getMethod("list2"); methodParameter = MethodParameter.forExecutable(method, -1); type = ResolvableType.forMethodParameter(methodParameter); assertThat(((MethodParameter)type.getSource()).getMethod()).isEqualTo(method); } @Test void forMethodReturn() throws Exception { Method method = Methods.class.getMethod("charSequenceReturn"); ResolvableType type = ResolvableType.forMethodReturnType(method); assertThat(type.getType()).isEqualTo(method.getGenericReturnType()); } @Test void forMethodReturnMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forMethodReturnType(null)) .withMessageContaining("Method must not be null"); } @Test void classType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("classType")); assertThat(type.getType().getClass()).isEqualTo(Class.class); } @Test void paramaterizedType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType")); assertThat(type.getType()).isInstanceOf(ParameterizedType.class); } @Test void arrayClassType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("arrayClassType")); assertThat(type.getType()).isInstanceOf(Class.class); assertThat(((Class) type.getType()).isArray()).isEqualTo(true); } @Test void genericArrayType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType")); assertThat(type.getType()).isInstanceOf(GenericArrayType.class); } @Test void wildcardType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType")); assertThat(type.getType()).isInstanceOf(ParameterizedType.class); assertThat(type.getGeneric().getType()).isInstanceOf(WildcardType.class); } @Test void typeVariableType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("typeVariableType")); assertThat(type.getType()).isInstanceOf(TypeVariable.class); } @Test void getComponentTypeForClassArray() throws Exception { Field field = Fields.class.getField("arrayClassType"); ResolvableType type = ResolvableType.forField(field); assertThat(type.isArray()).isEqualTo(true); assertThat(type.getComponentType().getType()) .isEqualTo(((Class) field.getGenericType()).getComponentType()); } @Test void getComponentTypeForGenericArrayType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType")); assertThat(type.isArray()).isEqualTo(true); assertThat(type.getComponentType().getType()).isEqualTo( ((GenericArrayType) type.getType()).getGenericComponentType()); } @Test void getComponentTypeForVariableThatResolvesToGenericArray() throws Exception { ResolvableType type = ResolvableType.forClass(ListOfGenericArray.class).asCollection().getGeneric(); assertThat(type.isArray()).isEqualTo(true); assertThat(type.getType()).isInstanceOf(TypeVariable.class); assertThat(type.getComponentType().getType().toString()).isEqualTo( "java.util.List"); } @Test void getComponentTypeForNonArray() throws Exception { ResolvableType type = ResolvableType.forClass(String.class); assertThat(type.isArray()).isEqualTo(false); assertThat(type.getComponentType()).isEqualTo(ResolvableType.NONE); } @Test void asCollection() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).asCollection(); assertThat(type.resolve()).isEqualTo(Collection.class); assertThat(type.resolveGeneric()).isEqualTo(CharSequence.class); } @Test void asMap() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsMap.class).asMap(); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.resolveGeneric(0)).isEqualTo(String.class); assertThat(type.resolveGeneric(1)).isEqualTo(Integer.class); } @Test void asFromInterface() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(List.class); assertThat(type.getType().toString()).isEqualTo("java.util.List"); } @Test void asFromInheritedInterface() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(Collection.class); assertThat(type.getType().toString()).isEqualTo("java.util.Collection"); } @Test void asFromSuperType() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(ArrayList.class); assertThat(type.getType().toString()).isEqualTo("java.util.ArrayList"); } @Test void asFromInheritedSuperType() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(List.class); assertThat(type.getType().toString()).isEqualTo("java.util.List"); } @Test void asNotFound() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).as(Map.class); assertThat(type).isSameAs(ResolvableType.NONE); } @Test void asSelf() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.as(ExtendsList.class)).isEqualTo(type); } @Test void getSuperType() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class).getSuperType(); assertThat(type.resolve()).isEqualTo(ArrayList.class); type = type.getSuperType(); assertThat(type.resolve()).isEqualTo(AbstractList.class); type = type.getSuperType(); assertThat(type.resolve()).isEqualTo(AbstractCollection.class); type = type.getSuperType(); assertThat(type.resolve()).isEqualTo(Object.class); } @Test void getInterfaces() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.getInterfaces().length).isEqualTo(0); SortedSet interfaces = new TreeSet<>(); for (ResolvableType interfaceType : type.getSuperType().getInterfaces()) { interfaces.add(interfaceType.toString()); } assertThat(interfaces.toString()).isEqualTo( "[java.io.Serializable, java.lang.Cloneable, " + "java.util.List, java.util.RandomAccess]"); } @Test void noSuperType() throws Exception { assertThat(ResolvableType.forClass(Object.class).getSuperType()) .isEqualTo(ResolvableType.NONE); } @Test void noInterfaces() throws Exception { assertThat(ResolvableType.forClass(Object.class).getInterfaces()).isEmpty(); } @Test void nested() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("nested")); type = type.getNested(2); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(Byte.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(Long.class); } @Test void nestedWithIndexes() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("nested")); type = type.getNested(2, Collections.singletonMap(2, 0)); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(String.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(Integer.class); } @Test void nestedWithArray() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType")); type = type.getNested(2); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.resolveGeneric()).isEqualTo(String.class); } @Test void getGeneric() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); assertThat(type.getGeneric().getType()).isEqualTo(String.class); } @Test void getGenericByIndex() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringIntegerMultiValueMap")); assertThat(type.getGeneric(0).getType()).isEqualTo(String.class); assertThat(type.getGeneric(1).getType()).isEqualTo(Integer.class); } @Test void getGenericOfGeneric() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList")); assertThat(type.getGeneric().getType().toString()).isEqualTo("java.util.List"); assertThat(type.getGeneric().getGeneric().getType()).isEqualTo(String.class); } @Test void genericOfGenericWithAs() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList")).asCollection(); assertThat(type.toString()).isEqualTo("java.util.Collection>"); assertThat(type.getGeneric().asCollection().toString()).isEqualTo("java.util.Collection"); } @Test void getGenericOfGenericByIndexes() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList")); assertThat(type.getGeneric(0, 0).getType()).isEqualTo(String.class); } @Test void getGenericOutOfBounds() throws Exception { ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class); assertThat(type.getGeneric(0)).isNotEqualTo(ResolvableType.NONE); assertThat(type.getGeneric(1)).isEqualTo(ResolvableType.NONE); assertThat(type.getGeneric(0, 1)).isEqualTo(ResolvableType.NONE); } @Test void hasGenerics() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.hasGenerics()).isEqualTo(false); assertThat(type.asCollection().hasGenerics()).isEqualTo(true); } @Test void getGenericsFromParameterizedType() throws Exception { ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class); ResolvableType[] generics = type.getGenerics(); assertThat(generics.length).isEqualTo(1); assertThat(generics[0].resolve()).isEqualTo(CharSequence.class); } @Test void getGenericsFromClass() throws Exception { ResolvableType type = ResolvableType.forClass(List.class); ResolvableType[] generics = type.getGenerics(); assertThat(generics.length).isEqualTo(1); assertThat(generics[0].getType().toString()).isEqualTo("E"); } @Test void noGetGenerics() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); ResolvableType[] generics = type.getGenerics(); assertThat(generics.length).isEqualTo(0); } @Test void getResolvedGenerics() throws Exception { ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class); Class[] generics = type.resolveGenerics(); assertThat(generics.length).isEqualTo(1); assertThat(generics[0]).isEqualTo(CharSequence.class); } @Test void resolveClassType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("classType")); assertThat(type.resolve()).isEqualTo(List.class); } @Test void resolveParameterizedType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType")); assertThat(type.resolve()).isEqualTo(List.class); } @Test void resolveArrayClassType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("arrayClassType")); assertThat(type.resolve()).isEqualTo(List[].class); } @Test void resolveGenericArrayType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("genericArrayType")); assertThat(type.resolve()).isEqualTo(List[].class); assertThat(type.getComponentType().resolve()).isEqualTo(List.class); assertThat(type.getComponentType().getGeneric().resolve()).isEqualTo(String.class); } @Test void resolveGenericMultiArrayType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("genericMultiArrayType")); assertThat(type.resolve()).isEqualTo(List[][][].class); assertThat(type.getComponentType().resolve()).isEqualTo(List[][].class); } @Test void resolveGenericArrayFromGeneric() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringArrayList")); ResolvableType generic = type.asCollection().getGeneric(); assertThat(generic.getType().toString()).isEqualTo("E"); assertThat(generic.isArray()).isEqualTo(true); assertThat(generic.resolve()).isEqualTo(String[].class); } @Test void resolveVariableGenericArray() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("variableTypeGenericArray"), TypedFields.class); assertThat(type.getType().toString()).isEqualTo("T[]"); assertThat(type.isArray()).isEqualTo(true); assertThat(type.resolve()).isEqualTo(String[].class); } @Test void resolveVariableGenericArrayUnknown() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("variableTypeGenericArray")); assertThat(type.getType().toString()).isEqualTo("T[]"); assertThat(type.isArray()).isEqualTo(true); assertThat(type.resolve()).isNull(); } @Test void resolveVariableGenericArrayUnknownWithFallback() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("variableTypeGenericArray")); assertThat(type.getType().toString()).isEqualTo("T[]"); assertThat(type.isArray()).isEqualTo(true); assertThat(type.toClass()).isEqualTo(Object.class); } @Test void resolveWildcardTypeUpperBounds() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType")); assertThat(type.getGeneric().resolve()).isEqualTo(Number.class); } @Test void resolveWildcardLowerBounds() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardSuperType")); assertThat(type.getGeneric().resolve()).isEqualTo(Number.class); } @Test void resolveVariableFromFieldType() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.getGeneric().resolve()).isEqualTo(String.class); } @Test void resolveVariableFromFieldTypeUnknown() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType")); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.getGeneric().resolve()).isNull(); } @Test void resolveVariableFromInheritedField() throws Exception { ResolvableType type = ResolvableType.forField( Fields.class.getField("stringIntegerMultiValueMap")).as(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(String.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(List.class); assertThat(type.getGeneric(1, 0).resolve()).isEqualTo(Integer.class); } @Test void resolveVariableFromInheritedFieldSwitched() throws Exception { ResolvableType type = ResolvableType.forField( Fields.class.getField("stringIntegerMultiValueMapSwitched")).as(Map.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(String.class); assertThat(type.getGeneric(1).resolve()).isEqualTo(List.class); assertThat(type.getGeneric(1, 0).resolve()).isEqualTo(Integer.class); } @Test void doesResolveFromOuterOwner() throws Exception { ResolvableType type = ResolvableType.forField( Fields.class.getField("listOfListOfUnknown")).as(Collection.class); assertThat(type.getGeneric(0).resolve()).isEqualTo(List.class); assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve()).isNull(); } @Test void resolveBoundedTypeVariableResult() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVaraibleResult")); assertThat(type.resolve()).isEqualTo(CharSequence.class); } @Test void resolveVariableNotFound() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("typedReturn")); assertThat(type.resolve()).isNull(); } @Test void resolveTypeVaraibleFromMethodReturn() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("typedReturn")); assertThat(type.resolve()).isNull(); } @Test void resolveTypeVaraibleFromMethodReturnWithInstanceClass() throws Exception { ResolvableType type = ResolvableType.forMethodReturnType( Methods.class.getMethod("typedReturn"), TypedMethods.class); assertThat(type.resolve()).isEqualTo(String.class); } @Test void resolveTypeVaraibleFromSimpleInterfaceType() { ResolvableType type = ResolvableType.forClass( MySimpleInterfaceType.class).as(MyInterfaceType.class); assertThat(type.resolveGeneric()).isEqualTo(String.class); } @Test void resolveTypeVaraibleFromSimpleCollectionInterfaceType() { ResolvableType type = ResolvableType.forClass( MyCollectionInterfaceType.class).as(MyInterfaceType.class); assertThat(type.resolveGeneric()).isEqualTo(Collection.class); assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class); } @Test void resolveTypeVaraibleFromSimpleSuperclassType() { ResolvableType type = ResolvableType.forClass( MySimpleSuperclassType.class).as(MySuperclassType.class); assertThat(type.resolveGeneric()).isEqualTo(String.class); } @Test void resolveTypeVaraibleFromSimpleCollectionSuperclassType() { ResolvableType type = ResolvableType.forClass( MyCollectionSuperclassType.class).as(MySuperclassType.class); assertThat(type.resolveGeneric()).isEqualTo(Collection.class); assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class); } @Test void resolveTypeVariableFromFieldTypeWithImplementsClass() throws Exception { ResolvableType type = ResolvableType.forField( Fields.class.getField("parameterizedType"), TypedFields.class); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.getGeneric().resolve()).isEqualTo(String.class); } @Test void resolveTypeVariableFromFieldTypeWithImplementsType() throws Exception { ResolvableType implementationType = ResolvableType.forClassWithGenerics( Fields.class, Integer.class); ResolvableType type = ResolvableType.forField( Fields.class.getField("parameterizedType"), implementationType); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.getGeneric().resolve()).isEqualTo(Integer.class); } @Test void resolveTypeVariableFromSuperType() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsList.class); assertThat(type.resolve()).isEqualTo(ExtendsList.class); assertThat(type.asCollection().resolveGeneric()) .isEqualTo(CharSequence.class); } @Test void resolveTypeVariableFromClassWithImplementsClass() throws Exception { ResolvableType type = ResolvableType.forClass( MySuperclassType.class, MyCollectionSuperclassType.class); assertThat(type.resolveGeneric()).isEqualTo(Collection.class); assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class); } @Test void resolveTypeVariableFromConstructorParameter() throws Exception { Constructor constructor = Constructors.class.getConstructor(List.class); ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0); assertThat(type.resolve()).isEqualTo(List.class); assertThat(type.resolveGeneric(0)).isEqualTo(CharSequence.class); } @Test void resolveUnknownTypeVariableFromConstructorParameter() throws Exception { Constructor constructor = Constructors.class.getConstructor(Map.class); ResolvableType type = ResolvableType.forConstructorParameter(constructor, 0); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.resolveGeneric(0)).isNull(); } @Test void resolveTypeVariableFromConstructorParameterWithImplementsClass() throws Exception { Constructor constructor = Constructors.class.getConstructor(Map.class); ResolvableType type = ResolvableType.forConstructorParameter( constructor, 0, TypedConstructors.class); assertThat(type.resolve()).isEqualTo(Map.class); assertThat(type.resolveGeneric(0)).isEqualTo(String.class); } @Test void resolveTypeVariableFromMethodParameter() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); ResolvableType type = ResolvableType.forMethodParameter(method, 0); assertThat(type.resolve()).isNull(); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromMethodParameterWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); ResolvableType type = ResolvableType.forMethodParameter(method, 0, TypedMethods.class); assertThat(type.resolve()).isEqualTo(String.class); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromMethodParameterType() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve()).isNull(); assertThat(type.getType().toString()).isEqualTo("T"); } @Test @SuppressWarnings("deprecation") void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.setContainingClass(TypedMethods.class); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve()).isEqualTo(String.class); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromMethodParameterTypeWithImplementsType() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); ResolvableType implementationType = ResolvableType.forClassWithGenerics(Methods.class, Integer.class); ResolvableType type = ResolvableType.forMethodParameter(methodParameter, implementationType); assertThat(type.resolve()).isEqualTo(Integer.class); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromMethodReturn() throws Exception { Method method = Methods.class.getMethod("typedReturn"); ResolvableType type = ResolvableType.forMethodReturnType(method); assertThat(type.resolve()).isNull(); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromMethodReturnWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedReturn"); ResolvableType type = ResolvableType.forMethodReturnType(method, TypedMethods.class); assertThat(type.resolve()).isEqualTo(String.class); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromType() throws Exception { Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType(); ResolvableType type = ResolvableType.forType(sourceType); assertThat(type.resolve()).isNull(); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromTypeWithVariableResolver() throws Exception { Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType(); ResolvableType type = ResolvableType.forType( sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class).asVariableResolver()); assertThat(type.resolve()).isEqualTo(String.class); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeWithCustomVariableResolver() throws Exception { VariableResolver variableResolver = mock(VariableResolver.class); given(variableResolver.getSource()).willReturn(this); ResolvableType longType = ResolvableType.forClass(Long.class); given(variableResolver.resolveVariable(any())).willReturn(longType); ResolvableType variable = ResolvableType.forType( Fields.class.getField("typeVariableType").getGenericType(), variableResolver); ResolvableType parameterized = ResolvableType.forType( Fields.class.getField("parameterizedType").getGenericType(), variableResolver); assertThat(variable.resolve()).isEqualTo(Long.class); assertThat(parameterized.resolve()).isEqualTo(List.class); assertThat(parameterized.resolveGeneric()).isEqualTo(Long.class); verify(variableResolver, atLeastOnce()).resolveVariable(this.typeVariableCaptor.capture()); assertThat(this.typeVariableCaptor.getValue().getName()).isEqualTo("T"); } @Test void resolveTypeVariableFromReflectiveParameterizedTypeReference() throws Exception { Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType(); ResolvableType type = ResolvableType.forType(ParameterizedTypeReference.forType(sourceType)); assertThat(type.resolve()).isNull(); assertThat(type.getType().toString()).isEqualTo("T"); } @Test void resolveTypeVariableFromDeclaredParameterizedTypeReference() throws Exception { Type sourceType = Methods.class.getMethod("charSequenceReturn").getGenericReturnType(); ResolvableType reflectiveType = ResolvableType.forType(sourceType); ResolvableType declaredType = ResolvableType.forType(new ParameterizedTypeReference>() {}); assertThat(declaredType).isEqualTo(reflectiveType); } @Test void toStrings() throws Exception { assertThat(ResolvableType.NONE.toString()).isEqualTo("?"); assertThat(forField("classType")).hasToString("java.util.List"); assertThat(forField("typeVariableType")).hasToString("?"); assertThat(forField("parameterizedType")).hasToString("java.util.List"); assertThat(forField("arrayClassType")).hasToString("java.util.List[]"); assertThat(forField("genericArrayType")).hasToString("java.util.List[]"); assertThat(forField("genericMultiArrayType")).hasToString("java.util.List[][][]"); assertThat(forField("wildcardType")).hasToString("java.util.List"); assertThat(forField("wildcardSuperType")).hasToString("java.util.List"); assertThat(forField("charSequenceList")).hasToString("java.util.List"); assertThat(forField("stringList")).hasToString("java.util.List"); assertThat(forField("stringListList")).hasToString("java.util.List>"); assertThat(forField("stringArrayList")).hasToString("java.util.List"); assertThat(forField("stringIntegerMultiValueMap")).hasToString("org.springframework.util.MultiValueMap"); assertThat(forField("stringIntegerMultiValueMapSwitched")).hasToString(VariableNameSwitch.class.getName() + ""); assertThat(forField("listOfListOfUnknown")).hasToString("java.util.List>"); assertThat(forTypedField("typeVariableType")).hasToString("java.lang.String"); assertThat(forTypedField("parameterizedType")).hasToString("java.util.List"); assertThat(ResolvableType.forClass(ListOfGenericArray.class).toString()).isEqualTo(ListOfGenericArray.class.getName()); assertThat(ResolvableType.forClass(List.class, ListOfGenericArray.class).toString()).isEqualTo("java.util.List[]>"); } @Test void getSource() throws Exception { Class classType = MySimpleInterfaceType.class; Field basicField = Fields.class.getField("classType"); Field field = Fields.class.getField("charSequenceList"); Method method = Methods.class.getMethod("charSequenceParameter", List.class); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); assertThat(ResolvableType.forField(basicField).getSource()).isEqualTo(basicField); assertThat(ResolvableType.forField(field).getSource()).isEqualTo(field); assertThat(ResolvableType.forMethodParameter(methodParameter).getSource()).isEqualTo(methodParameter); assertThat(ResolvableType.forMethodParameter(method, 0).getSource()).isEqualTo(methodParameter); assertThat(ResolvableType.forClass(classType).getSource()).isEqualTo(classType); assertThat(ResolvableType.forClass(classType).getSuperType().getSource()).isEqualTo(classType.getGenericSuperclass()); } @Test void resolveFromOuterClass() throws Exception { Field field = EnclosedInParameterizedType.InnerTyped.class.getField("field"); ResolvableType type = ResolvableType.forField(field, TypedEnclosedInParameterizedType.TypedInnerTyped.class); assertThat(type.resolve()).isEqualTo(Integer.class); } @Test void resolveFromClassWithGenerics() throws Exception { ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ResolvableType.forClassWithGenerics(List.class, String.class)); assertThat(type.asCollection().toString()).isEqualTo("java.util.Collection>"); assertThat(type.asCollection().getGeneric().toString()).isEqualTo("java.util.List"); assertThat(type.asCollection().getGeneric().asCollection().toString()).isEqualTo("java.util.Collection"); assertThat(type.toString()).isEqualTo("java.util.List>"); assertThat(type.asCollection().getGeneric().getGeneric().resolve()).isEqualTo(String.class); } @Test void isAssignableFromMustNotBeNull() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forClass(Object.class).isAssignableFrom((ResolvableType) null)) .withMessageContaining("Type must not be null"); } @Test void isAssignableFromForNone() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); assertThat(objectType.isAssignableFrom(ResolvableType.NONE)).isEqualTo(false); assertThat(ResolvableType.NONE.isAssignableFrom(objectType)).isEqualTo(false); } @Test void isAssignableFromForClassAndClass() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); ResolvableType charSequenceType = ResolvableType.forClass(CharSequence.class); ResolvableType stringType = ResolvableType.forClass(String.class); assertThatResolvableType(objectType).isAssignableFrom(objectType, charSequenceType, stringType); assertThatResolvableType(charSequenceType).isAssignableFrom(charSequenceType, stringType).isNotAssignableFrom(objectType); assertThatResolvableType(stringType).isAssignableFrom(stringType).isNotAssignableFrom(objectType, charSequenceType); assertThat(objectType.isAssignableFrom(String.class)).isTrue(); assertThat(objectType.isAssignableFrom(StringBuilder.class)).isTrue(); assertThat(charSequenceType.isAssignableFrom(String.class)).isTrue(); assertThat(charSequenceType.isAssignableFrom(StringBuilder.class)).isTrue(); assertThat(stringType.isAssignableFrom(String.class)).isTrue(); assertThat(stringType.isAssignableFrom(StringBuilder.class)).isFalse(); assertThat(objectType.isInstance("a String")).isTrue(); assertThat(objectType.isInstance(new StringBuilder("a StringBuilder"))).isTrue(); assertThat(charSequenceType.isInstance("a String")).isTrue(); assertThat(charSequenceType.isInstance(new StringBuilder("a StringBuilder"))).isTrue(); assertThat(stringType.isInstance("a String")).isTrue(); assertThat(stringType.isInstance(new StringBuilder("a StringBuilder"))).isFalse(); } @Test void isAssignableFromCannotBeResolved() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o")); assertThat(unresolvableVariable.resolve()).isNull(); assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable); assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType); } @Test void isAssignableFromForClassAndSimpleVariable() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); ResolvableType charSequenceType = ResolvableType.forClass(CharSequence.class); ResolvableType stringType = ResolvableType.forClass(String.class); ResolvableType objectVariable = ResolvableType.forField(AssignmentBase.class.getField("o"), Assignment.class); ResolvableType charSequenceVariable = ResolvableType.forField(AssignmentBase.class.getField("c"), Assignment.class); ResolvableType stringVariable = ResolvableType.forField(AssignmentBase.class.getField("s"), Assignment.class); assertThatResolvableType(objectType).isAssignableFrom(objectVariable, charSequenceVariable, stringVariable); assertThatResolvableType(charSequenceType).isAssignableFrom(charSequenceVariable, stringVariable).isNotAssignableFrom(objectVariable); assertThatResolvableType(stringType).isAssignableFrom(stringVariable).isNotAssignableFrom(objectVariable, charSequenceVariable); assertThatResolvableType(objectVariable).isAssignableFrom(objectType, charSequenceType, stringType); assertThatResolvableType(charSequenceVariable).isAssignableFrom(charSequenceType, stringType).isNotAssignableFrom(objectType); assertThatResolvableType(stringVariable).isAssignableFrom(stringType).isNotAssignableFrom(objectType, charSequenceType); assertThatResolvableType(objectVariable).isAssignableFrom(objectVariable, charSequenceVariable, stringVariable); assertThatResolvableType(charSequenceVariable).isAssignableFrom(charSequenceVariable, stringVariable).isNotAssignableFrom(objectVariable); assertThatResolvableType(stringVariable).isAssignableFrom(stringVariable).isNotAssignableFrom(objectVariable, charSequenceVariable); } @Test void isAssignableFromForSameClassNonExtendsGenerics() throws Exception { ResolvableType objectList = ResolvableType.forField(AssignmentBase.class.getField("listo"), Assignment.class); ResolvableType stringList = ResolvableType.forField(AssignmentBase.class.getField("lists"), Assignment.class); assertThatResolvableType(stringList).isNotAssignableFrom(objectList); assertThatResolvableType(objectList).isNotAssignableFrom(stringList); assertThatResolvableType(stringList).isAssignableFrom(stringList); } @Test void isAssignableFromForSameClassExtendsGenerics() throws Exception { // Generic assignment can be a little confusing, given: // // List c1, List c2, List s; // // c2 = s; is allowed and is often used for argument input, for example // see List.addAll(). You can get items from c2 but you cannot add items without // getting a generic type 'is not applicable for the arguments' error. This makes // sense since if you added a StringBuffer to c2 it would break the rules on s. // // c1 = s; not allowed. Since there is no '? extends' to cause the generic // 'is not applicable for the arguments' error when adding (which would pollute // s). ResolvableType objectList = ResolvableType.forField(AssignmentBase.class.getField("listo"), Assignment.class); ResolvableType charSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listc"), Assignment.class); ResolvableType stringList = ResolvableType.forField(AssignmentBase.class.getField("lists"), Assignment.class); ResolvableType extendsObjectList = ResolvableType.forField(AssignmentBase.class.getField("listxo"), Assignment.class); ResolvableType extendsCharSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class); ResolvableType extendsStringList = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class); assertThatResolvableType(objectList).isNotAssignableFrom(extendsObjectList, extendsCharSequenceList, extendsStringList); assertThatResolvableType(charSequenceList).isNotAssignableFrom(extendsObjectList, extendsCharSequenceList, extendsStringList); assertThatResolvableType(stringList).isNotAssignableFrom(extendsObjectList, extendsCharSequenceList, extendsStringList); assertThatResolvableType(extendsObjectList).isAssignableFrom(objectList, charSequenceList, stringList); assertThatResolvableType(extendsObjectList).isAssignableFrom(extendsObjectList, extendsCharSequenceList, extendsStringList); assertThatResolvableType(extendsCharSequenceList).isAssignableFrom(extendsCharSequenceList, extendsStringList).isNotAssignableFrom(extendsObjectList); assertThatResolvableType(extendsCharSequenceList).isAssignableFrom(charSequenceList, stringList).isNotAssignableFrom(objectList); assertThatResolvableType(extendsStringList).isAssignableFrom(extendsStringList).isNotAssignableFrom(extendsObjectList, extendsCharSequenceList); assertThatResolvableType(extendsStringList).isAssignableFrom(stringList).isNotAssignableFrom(objectList, charSequenceList); } @Test void isAssignableFromForDifferentClassesWithGenerics() throws Exception { ResolvableType extendsCharSequenceCollection = ResolvableType.forField(AssignmentBase.class.getField("collectionxc"), Assignment.class); ResolvableType charSequenceCollection = ResolvableType.forField(AssignmentBase.class.getField("collectionc"), Assignment.class); ResolvableType charSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listc"), Assignment.class); ResolvableType extendsCharSequenceList = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class); ResolvableType extendsStringList = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class); assertThatResolvableType(extendsCharSequenceCollection).isAssignableFrom(charSequenceCollection, charSequenceList, extendsCharSequenceList, extendsStringList); assertThatResolvableType(charSequenceCollection).isAssignableFrom(charSequenceList).isNotAssignableFrom(extendsCharSequenceList, extendsStringList); assertThatResolvableType(charSequenceList).isNotAssignableFrom(extendsCharSequenceCollection, charSequenceCollection); assertThatResolvableType(extendsCharSequenceList).isNotAssignableFrom(extendsCharSequenceCollection, charSequenceCollection); assertThatResolvableType(extendsStringList).isNotAssignableFrom(charSequenceCollection, charSequenceList, extendsCharSequenceList); } @Test void isAssignableFromForArrays() throws Exception { ResolvableType object = ResolvableType.forField(AssignmentBase.class.getField("o"), Assignment.class); ResolvableType objectArray = ResolvableType.forField(AssignmentBase.class.getField("oarray"), Assignment.class); ResolvableType charSequenceArray = ResolvableType.forField(AssignmentBase.class.getField("carray"), Assignment.class); ResolvableType stringArray = ResolvableType.forField(AssignmentBase.class.getField("sarray"), Assignment.class); assertThatResolvableType(object).isAssignableFrom(objectArray, charSequenceArray, stringArray); assertThatResolvableType(objectArray).isAssignableFrom(objectArray, charSequenceArray, stringArray).isNotAssignableFrom(object); assertThatResolvableType(charSequenceArray).isAssignableFrom(charSequenceArray, stringArray).isNotAssignableFrom(object, objectArray); assertThatResolvableType(stringArray).isAssignableFrom(stringArray).isNotAssignableFrom(object, objectArray, charSequenceArray); } @Test void isAssignableFromForWildcards() throws Exception { ResolvableType object = ResolvableType.forClass(Object.class); ResolvableType charSequence = ResolvableType.forClass(CharSequence.class); ResolvableType string = ResolvableType.forClass(String.class); ResolvableType extendsAnon = ResolvableType.forField(AssignmentBase.class.getField("listAnon"), Assignment.class).getGeneric(); ResolvableType extendsObject = ResolvableType.forField(AssignmentBase.class.getField("listxo"), Assignment.class).getGeneric(); ResolvableType extendsCharSequence = ResolvableType.forField(AssignmentBase.class.getField("listxc"), Assignment.class).getGeneric(); ResolvableType extendsString = ResolvableType.forField(AssignmentBase.class.getField("listxs"), Assignment.class).getGeneric(); ResolvableType superObject = ResolvableType.forField(AssignmentBase.class.getField("listso"), Assignment.class).getGeneric(); ResolvableType superCharSequence = ResolvableType.forField(AssignmentBase.class.getField("listsc"), Assignment.class).getGeneric(); ResolvableType superString = ResolvableType.forField(AssignmentBase.class.getField("listss"), Assignment.class).getGeneric(); // Language Spec 4.5.1. Type Arguments and Wildcards // ? extends T <= ? extends S if T <: S assertThatResolvableType(extendsCharSequence).isAssignableFrom(extendsCharSequence, extendsString).isNotAssignableFrom(extendsObject); assertThatResolvableType(extendsCharSequence).isAssignableFrom(charSequence, string).isNotAssignableFrom(object); // ? super T <= ? super S if S <: T assertThatResolvableType(superCharSequence).isAssignableFrom(superObject, superCharSequence).isNotAssignableFrom(superString); assertThatResolvableType(superCharSequence).isAssignableFrom(object, charSequence).isNotAssignableFrom(string); // [Implied] super / extends cannot be mixed assertThatResolvableType(superCharSequence).isNotAssignableFrom(extendsObject, extendsCharSequence, extendsString); assertThatResolvableType(extendsCharSequence).isNotAssignableFrom(superObject, superCharSequence, superString); // T <= T assertThatResolvableType(charSequence).isAssignableFrom(charSequence, string).isNotAssignableFrom(object); // T <= ? extends T assertThatResolvableType(extendsCharSequence).isAssignableFrom(charSequence, string).isNotAssignableFrom(object); assertThatResolvableType(charSequence).isNotAssignableFrom(extendsObject, extendsCharSequence, extendsString); assertThatResolvableType(extendsAnon).isAssignableFrom(object, charSequence, string); // T <= ? super T assertThatResolvableType(superCharSequence).isAssignableFrom(object, charSequence).isNotAssignableFrom(string); assertThatResolvableType(charSequence).isNotAssignableFrom(superObject, superCharSequence, superString); } @Test void isAssignableFromForComplexWildcards() throws Exception { ResolvableType complex1 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard1")); ResolvableType complex2 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard2")); ResolvableType complex3 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard3")); ResolvableType complex4 = ResolvableType.forField(AssignmentBase.class.getField("complexWildcard4")); assertThatResolvableType(complex1).isAssignableFrom(complex2); assertThatResolvableType(complex2).isNotAssignableFrom(complex1); assertThatResolvableType(complex3).isAssignableFrom(complex4); assertThatResolvableType(complex4).isNotAssignableFrom(complex3); } @Test 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(), (VariableResolver) null); ResolvableType forFieldWithImplementation = ResolvableType.forField(Fields.class.getDeclaredField("stringList"), TypedFields.class); assertThat(forClass).isEqualTo(forClass); assertThat(forClass.hashCode()).isEqualTo(forClass.hashCode()); assertThat(forClass).isNotEqualTo(forFieldDirect); assertThat(forClass).isNotEqualTo(forFieldWithImplementation); assertThat(forFieldDirect).isEqualTo(forFieldDirect); assertThat(forFieldDirect).isNotEqualTo(forFieldViaType); assertThat(forFieldDirect).isNotEqualTo(forFieldWithImplementation); } @Test void javaDocSample() throws Exception { ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap")); assertThat(t.toString()).isEqualTo("java.util.HashMap>"); assertThat(t.getType().getTypeName()).isEqualTo("java.util.HashMap>"); assertThat(t.getType().toString()).isEqualTo("java.util.HashMap>"); assertThat(t.getSuperType().toString()).isEqualTo("java.util.AbstractMap>"); assertThat(t.asMap().toString()).isEqualTo("java.util.Map>"); assertThat(t.getGeneric(0).resolve()).isEqualTo(Integer.class); assertThat(t.getGeneric(1).resolve()).isEqualTo(List.class); assertThat(t.getGeneric(1).toString()).isEqualTo("java.util.List"); assertThat(t.resolveGeneric(1, 0)).isEqualTo(String.class); } @Test void forClassWithGenerics() throws Exception { ResolvableType elementType = ResolvableType.forClassWithGenerics(Map.class, Integer.class, String.class); ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType); assertThat(listType.toString()).isEqualTo("java.util.List>"); assertThat(listType.getType().getTypeName()).isEqualTo("java.util.List>"); assertThat(listType.getType().toString()).isEqualTo("java.util.List>"); } @Test void classWithGenericsAs() throws Exception { ResolvableType type = ResolvableType.forClassWithGenerics(MultiValueMap.class, Integer.class, String.class); assertThat(type.asMap().toString()).isEqualTo("java.util.Map>"); } @Test void forClassWithMismatchedGenerics() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> ResolvableType.forClassWithGenerics(Map.class, Integer.class)) .withMessageContaining("Mismatched number of generics specified"); } @Test void forArrayComponent() throws Exception { ResolvableType elementType = ResolvableType.forField(Fields.class.getField("stringList")); ResolvableType type = ResolvableType.forArrayComponent(elementType); assertThat(type.toString()).isEqualTo("java.util.List[]"); assertThat(type.resolve()).isEqualTo(List[].class); } @Test 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()); ResolvableType deserializedNone = testSerialization(ResolvableType.NONE); assertThat(deserializedNone).isSameAs(ResolvableType.NONE); } @Test void canResolveVoid() throws Exception { ResolvableType type = ResolvableType.forClass(void.class); assertThat(type.resolve()).isEqualTo(void.class); } @Test void narrow() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); ResolvableType narrow = ResolvableType.forType(ArrayList.class, type); assertThat(narrow.getGeneric().resolve()).isEqualTo(String.class); } @Test void hasUnresolvableGenerics() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); assertThat(type.hasUnresolvableGenerics()).isEqualTo(false); } @Test void hasUnresolvableGenericsBasedOnOwnGenerics() throws Exception { ResolvableType type = ResolvableType.forClass(List.class); assertThat(type.hasUnresolvableGenerics()).isEqualTo(true); } @Test void hasUnresolvableGenericsWhenSelfNotResolvable() throws Exception { ResolvableType type = ResolvableType.forClass(List.class).getGeneric(); assertThat(type.hasUnresolvableGenerics()).isEqualTo(false); } @Test void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception { ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class); for (ResolvableType generic : type.getGenerics()) { assertThat(generic.resolve()).isNotNull(); } assertThat(type.hasUnresolvableGenerics()).isEqualTo(true); } @Test void hasUnresolvableGenericsWhenExtends() throws Exception { ResolvableType type = ResolvableType.forClass(ExtendsMySimpleInterfaceTypeWithImplementsRaw.class); for (ResolvableType generic : type.getGenerics()) { assertThat(generic.resolve()).isNotNull(); } assertThat(type.hasUnresolvableGenerics()).isEqualTo(true); } @Test void spr11219() throws Exception { ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class); assertThat(type.getNested(2).isAssignableFrom(ResolvableType.forClass(BaseImplementation.class))).isTrue(); assertThat(type.toString()).isEqualTo("java.util.Collection>"); } @Test void spr12701() throws Exception { ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Callable.class, String.class); Type type = resolvableType.getType(); assertThat(type).isInstanceOf(ParameterizedType.class); assertThat(((ParameterizedType) type).getRawType()).isEqualTo(Callable.class); assertThat(((ParameterizedType) type).getActualTypeArguments().length).isEqualTo(1); assertThat(((ParameterizedType) type).getActualTypeArguments()[0]).isEqualTo(String.class); } @Test void spr14648() throws Exception { ResolvableType collectionClass = ResolvableType.forRawClass(Collection.class); ResolvableType setClass = ResolvableType.forRawClass(Set.class); ResolvableType fromReturnType = ResolvableType.forMethodReturnType(Methods.class.getMethod("wildcardSet")); assertThat(collectionClass.isAssignableFrom(fromReturnType)).isTrue(); assertThat(setClass.isAssignableFrom(fromReturnType)).isTrue(); } @Test void spr16456() throws Exception { ResolvableType genericType = ResolvableType.forField( UnresolvedWithGenerics.class.getDeclaredField("set")).asCollection(); ResolvableType type = ResolvableType.forClassWithGenerics(ArrayList.class, genericType.getGeneric()); assertThat(type.resolveGeneric()).isEqualTo(Integer.class); } 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).isEqualTo(type); assertThat(read.getType()).isEqualTo(type.getType()); assertThat(read.resolve()).isEqualTo(type.resolve()); return read; } private ResolvableType forField(String field) throws NoSuchFieldException { return ResolvableType.forField(Fields.class.getField(field)); } private ResolvableType forTypedField(String field) throws NoSuchFieldException { return ResolvableType.forField(Fields.class.getField(field), TypedFields.class); } private static ResolvableTypeAssert assertThatResolvableType(ResolvableType type) { return new ResolvableTypeAssert(type); } @SuppressWarnings("unused") private HashMap> myMap; @SuppressWarnings("serial") static class ExtendsList extends ArrayList { } @SuppressWarnings("serial") static class ExtendsMap extends HashMap { } static class Fields { public List classType; public T typeVariableType; public List parameterizedType; public List[] arrayClassType; public List[] genericArrayType; public List[][][] genericMultiArrayType; public List wildcardType; public List wildcardSuperType = new ArrayList(); public List charSequenceList; public List stringList; public List> stringListList; public List stringArrayList; public MultiValueMap stringIntegerMultiValueMap; public VariableNameSwitch stringIntegerMultiValueMapSwitched; public List listOfListOfUnknown; @SuppressWarnings("unused") private List privateField; @SuppressWarnings("unused") private List otherPrivateField; public Map, Map> nested; public T[] variableTypeGenericArray; } static class TypedFields extends Fields { } interface Methods { List charSequenceReturn(); void charSequenceParameter(List cs); R boundedTypeVaraibleResult(); void nested(Map, Map> p); void typedParameter(T p); T typedReturn(); Set wildcardSet(); List list1(); List list2(); } static class AssignmentBase { public O o; public C c; public S s; public List listo; public List listc; public List lists; public List listAnon; public List listxo; public List listxc; public List listxs; public List listso; public List listsc; public List listss; public O[] oarray; public C[] carray; public S[] sarray; public Collection collectionc; public Collection collectionxc; public Map> complexWildcard1; public MultiValueMap complexWildcard2; public Collection> complexWildcard3; public List> complexWildcard4; } static class Assignment extends AssignmentBase { } interface TypedMethods extends Methods { } static class Constructors { public Constructors(List p) { } public Constructors(Map p) { } } static class TypedConstructors extends Constructors { public TypedConstructors(List p) { super(p); } public TypedConstructors(Map p) { super(p); } } public interface MyInterfaceType { } public class MyGenericInterfaceType implements MyInterfaceType, ResolvableTypeProvider { private final Class type; public MyGenericInterfaceType(Class type) { this.type = type; } @Override public ResolvableType getResolvableType() { if (this.type == null) { return null; } return ResolvableType.forClassWithGenerics(getClass(), this.type); } } public class MySimpleInterfaceType implements MyInterfaceType { } public abstract class MySimpleInterfaceTypeWithImplementsRaw implements MyInterfaceType, List { } public abstract class ExtendsMySimpleInterfaceTypeWithImplementsRaw extends MySimpleInterfaceTypeWithImplementsRaw { } public class MyCollectionInterfaceType implements MyInterfaceType> { } public abstract class MySuperclassType { } public class MySimpleSuperclassType extends MySuperclassType { } public class MyCollectionSuperclassType extends MySuperclassType> { } interface Wildcard extends List { } interface RawExtendsWildcard extends Wildcard { } interface VariableNameSwitch extends MultiValueMap { } interface ListOfGenericArray extends List[]> { } static class EnclosedInParameterizedType { static class InnerRaw { } class InnerTyped { public T field; } } static class TypedEnclosedInParameterizedType extends EnclosedInParameterizedType { class TypedInnerTyped extends InnerTyped { } } public interface IProvider

{ } public interface IBase> { } public abstract class AbstractBase> implements IBase { } public class BaseImplementation extends AbstractBase { } public class BaseProvider> implements IProvider> { public Collection> stuff; } public abstract class UnresolvedWithGenerics { Set set; } private static class ResolvableTypeAssert extends AbstractAssert{ public ResolvableTypeAssert(ResolvableType actual) { super(actual, ResolvableTypeAssert.class); } public ResolvableTypeAssert isAssignableFrom(ResolvableType... types) { for (ResolvableType type : types) { if (!actual.isAssignableFrom(type)) { throw new AssertionError("Expecting " + decribe(actual) + " to be assignable from " + decribe(type)); } } return this; } public ResolvableTypeAssert isNotAssignableFrom(ResolvableType... types) { for (ResolvableType type : types) { if (actual.isAssignableFrom(type)) { throw new AssertionError("Expecting " + decribe(actual) + " to not be assignable from " + decribe(type)); } } return this; } private String decribe(ResolvableType type) { if (type == ResolvableType.NONE) { return "NONE"; } if (type.getType().getClass().equals(Class.class)) { return type.toString(); } return type.getType() + ":" + type; } } }