/* * Copyright 2011-2017 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.data.util; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.util.ClassTypeInformation.*; import javaslang.collection.Traversable; import java.lang.reflect.Method; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import org.assertj.core.api.OptionalAssert; import org.junit.Test; import org.springframework.data.mapping.Person; /** * Unit tests for {@link ClassTypeInformation}. * * @author Oliver Gierke */ public class ClassTypeInformationUnitTests { @Test public void discoversTypeForSimpleGenericField() { TypeInformation<ConcreteType> discoverer = ClassTypeInformation.from(ConcreteType.class); assertThat(discoverer.getType()).isEqualTo(ConcreteType.class); OptionalAssert<TypeInformation<?>> assertThat = assertThat(discoverer.getProperty("content")); assertThat.hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); assertThat.flatMap(TypeInformation::getComponentType).isNotPresent(); assertThat.flatMap(TypeInformation::getMapValueType).isNotPresent(); } @Test public void discoversTypeForNestedGenericField() { TypeInformation<ConcreteWrapper> discoverer = ClassTypeInformation.from(ConcreteWrapper.class); assertThat(discoverer.getType()).isEqualTo(ConcreteWrapper.class); assertThat(discoverer.getProperty("wrapped")).hasValueSatisfying(it -> { assertThat(it.getType()).isEqualTo(GenericType.class); assertThat(it.getProperty("content")) .hasValueSatisfying(nested -> assertThat(nested.getType()).isEqualTo(String.class)); }); assertThat(discoverer.getProperty("wrapped.content")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); } @Test @SuppressWarnings("rawtypes") public void discoversBoundType() { TypeInformation<GenericTypeWithBound> information = ClassTypeInformation.from(GenericTypeWithBound.class); assertThat(information.getProperty("person")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Person.class)); } @Test public void discoversBoundTypeForSpecialization() { TypeInformation<SpecialGenericTypeWithBound> information = ClassTypeInformation .from(SpecialGenericTypeWithBound.class); assertThat(information.getProperty("person")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(SpecialPerson.class)); } @Test @SuppressWarnings("rawtypes") public void discoversBoundTypeForNested() { TypeInformation<AnotherGenericType> information = ClassTypeInformation.from(AnotherGenericType.class); assertThat(information.getProperty("nested")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(GenericTypeWithBound.class)); assertThat(information.getProperty("nested.person")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Person.class)); } @Test public void discoversArraysAndCollections() { TypeInformation<StringCollectionContainer> information = ClassTypeInformation.from(StringCollectionContainer.class); OptionalAssert<TypeInformation<?>> optional = assertThat(information.getProperty("array")); optional.flatMap(TypeInformation::getComponentType) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); optional.map(TypeInformation::getType).hasValueSatisfying(it -> { assertThat(it).isEqualTo(String[].class); assertThat(it.isArray()).isTrue(); }); optional = assertThat(information.getProperty("foo")); optional.hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Collection[].class)); optional.flatMap(TypeInformation::getComponentType).hasValueSatisfying(it -> { assertThat(it.getType()).isEqualTo(Collection.class); assertThat(it.getComponentType()) .hasValueSatisfying(nested -> assertThat(nested.getType()).isEqualTo(String.class)); }); optional = assertThat(information.getProperty("rawSet")); optional.hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Set.class)); optional.flatMap(TypeInformation::getComponentType) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Object.class)); optional.flatMap(TypeInformation::getMapValueType).isNotPresent(); } @Test public void discoversMapValueType() { TypeInformation<StringMapContainer> information = ClassTypeInformation.from(StringMapContainer.class); OptionalAssert<TypeInformation<?>> assertion = assertThat(information.getProperty("genericMap")); assertion.hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Map.class)); assertion.flatMap(TypeInformation::getMapValueType) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); assertion = assertThat(information.getProperty("map")); assertion.hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Map.class)); assertion.flatMap(TypeInformation::getMapValueType) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Calendar.class)); } @Test public void typeInfoDoesNotEqualForGenericTypesWithDifferentParent() { TypeInformation<ConcreteWrapper> first = ClassTypeInformation.from(ConcreteWrapper.class); TypeInformation<AnotherConcreteWrapper> second = ClassTypeInformation.from(AnotherConcreteWrapper.class); assertThat(first.getProperty("wrapped").equals(second.getProperty("wrapped"))).isFalse(); } @Test public void handlesPropertyFieldMismatchCorrectly() { TypeInformation<PropertyGetter> from = ClassTypeInformation.from(PropertyGetter.class); assertThat(from.getProperty("_name")).hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); assertThat(from.getProperty("name")).hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(byte[].class)); } @Test // DATACMNS-77 public void returnsSameInstanceForCachedClass() { TypeInformation<PropertyGetter> info = ClassTypeInformation.from(PropertyGetter.class); assertThat(ClassTypeInformation.from(PropertyGetter.class)).isSameAs(info); } @Test // DATACMNS-39 public void resolvesWildCardTypeCorrectly() { TypeInformation<ClassWithWildCardBound> information = ClassTypeInformation.from(ClassWithWildCardBound.class); OptionalAssert<TypeInformation<?>> assertion = assertThat(information.getProperty("wildcard")); assertion.map(TypeInformation::isCollectionLike).hasValue(true); assertion.flatMap(TypeInformation::getComponentType) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); assertion = assertThat(information.getProperty("complexWildcard")); assertion.map(TypeInformation::isCollectionLike).hasValue(true); assertion.flatMap(TypeInformation::getComponentType).hasValueSatisfying(it -> { assertThat(it.isCollectionLike()).isEqualTo(true); assertThat(it.getComponentType()) .hasValueSatisfying(nested -> assertThat(nested.getType()).isEqualTo(String.class)); }); } @Test public void resolvesTypeParametersCorrectly() { TypeInformation<ConcreteType> information = ClassTypeInformation.from(ConcreteType.class); TypeInformation<?> superTypeInformation = information.getSuperTypeInformation(GenericType.class); List<TypeInformation<?>> parameters = superTypeInformation.getTypeArguments(); assertThat(parameters).hasSize(2); assertThat(parameters.get(0).getType()).isEqualTo(String.class); assertThat(parameters.get(1).getType()).isEqualTo(Object.class); } @Test public void resolvesNestedInheritedTypeParameters() { TypeInformation<SecondExtension> information = ClassTypeInformation.from(SecondExtension.class); TypeInformation<?> superTypeInformation = information.getSuperTypeInformation(Base.class); List<TypeInformation<?>> parameters = superTypeInformation.getTypeArguments(); assertThat(parameters).hasSize(1); assertThat(parameters.get(0).getType()).isEqualTo(String.class); } @Test public void discoveresMethodParameterTypesCorrectly() throws Exception { TypeInformation<SecondExtension> information = ClassTypeInformation.from(SecondExtension.class); Method method = SecondExtension.class.getMethod("foo", Base.class); List<TypeInformation<?>> informations = information.getParameterTypes(method); TypeInformation<?> returnTypeInformation = information.getReturnType(method); assertThat(informations).hasSize(1); assertThat(informations.get(0).getType()).isEqualTo(Base.class); assertThat(informations.get(0)).isEqualTo(returnTypeInformation); } @Test public void discoversImplementationBindingCorrectlyForString() throws Exception { TypeInformation<TypedClient> information = from(TypedClient.class); Method method = TypedClient.class.getMethod("stringMethod", GenericInterface.class); TypeInformation<?> parameterType = information.getParameterTypes(method).get(0); TypeInformation<StringImplementation> stringInfo = from(StringImplementation.class); assertThat(parameterType.isAssignableFrom(stringInfo)).isTrue(); assertThat(stringInfo.getSuperTypeInformation(GenericInterface.class)).isEqualTo(parameterType); assertThat(parameterType.isAssignableFrom(from(LongImplementation.class))).isFalse(); assertThat(parameterType .isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class))).isTrue(); } @Test public void discoversImplementationBindingCorrectlyForLong() throws Exception { TypeInformation<TypedClient> information = from(TypedClient.class); Method method = TypedClient.class.getMethod("longMethod", GenericInterface.class); TypeInformation<?> parameterType = information.getParameterTypes(method).get(0); assertThat(parameterType.isAssignableFrom(from(StringImplementation.class))).isFalse(); assertThat(parameterType.isAssignableFrom(from(LongImplementation.class))).isTrue(); assertThat(parameterType .isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class))).isFalse(); } @Test public void discoversImplementationBindingCorrectlyForNumber() throws Exception { TypeInformation<TypedClient> information = from(TypedClient.class); Method method = TypedClient.class.getMethod("boundToNumberMethod", GenericInterface.class); TypeInformation<?> parameterType = information.getParameterTypes(method).get(0); assertThat(parameterType.isAssignableFrom(from(StringImplementation.class))).isFalse(); assertThat(parameterType.isAssignableFrom(from(LongImplementation.class))).isTrue(); assertThat(parameterType .isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class))).isFalse(); } @Test public void returnsComponentTypeForMultiDimensionalArrayCorrectly() { TypeInformation<?> information = from(String[][].class); assertThat(information.getType()).isEqualTo(String[][].class); assertThat(information.getComponentType()) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String[].class)); assertThat(information.getActualType().getActualType().getType()).isEqualTo(String.class); } @Test // DATACMNS-309 public void findsGetterOnInterface() { TypeInformation<Product> information = from(Product.class); assertThat(information.getProperty("category.id")).hasValue(from(Long.class)); } @Test(expected = IllegalArgumentException.class) // DATACMNS-387 public void rejectsNullClass() { from(null); } @Test // DATACMNS-422 public void returnsEmptyOptionalForRawTypesOnly() { assertThat(from(MyRawIterable.class).getComponentType()).isNotPresent(); assertThat(from(MyIterable.class).getComponentType()).isPresent(); } @Test // DATACMNS-440 public void detectsSpecialMapAsMapValueType() { OptionalAssert<TypeInformation<?>> assertion = assertThat( ClassTypeInformation.from(SuperGenerics.class).getProperty("seriously")); assertion// // Type .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(SortedMap.class))// // Map value type .flatMap(TypeInformation::getMapValueType).hasValueSatisfying(value -> { assertThat(value.getType()).isEqualTo(SortedMap.class); assertThat(value.getComponentType()) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(String.class)); }).flatMap(TypeInformation::getMapValueType).hasValueSatisfying(nestedValue -> { assertThat(nestedValue.getType()).isEqualTo(List.class); assertThat(nestedValue.getComponentType()) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Person.class)); }); } @Test // DATACMNS-446 public void createsToStringRepresentation() { assertThat(from(SpecialPerson.class).toString()) .isEqualTo("org.springframework.data.util.ClassTypeInformationUnitTests$SpecialPerson"); } @Test // DATACMNS-590 public void resolvesNestedGenericsToConcreteType() { ClassTypeInformation<ConcreteRoot> rootType = from(ConcreteRoot.class); assertThat(rootType.getProperty("subs"))// .map(TypeInformation::getActualType)// .flatMap(it -> it.getProperty("subSub"))// .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(ConcreteSubSub.class)); } @Test // DATACMNS-594 public void considersGenericsOfTypeBounds() { assertThat(ClassTypeInformation.from(ConcreteRootIntermediate.class) .getProperty("intermediate.content.intermediate.content"))// .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Leaf.class)); } @Test // DATACMNS-783, DATACMNS-853 public void specializesTypeUsingTypeVariableContext() { ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class); assertThat(root.getProperty("abstractBar"))// .map(it -> it.specialize(ClassTypeInformation.from(Bar.class)))// .hasValueSatisfying(it -> { assertThat(it.getType()).isEqualTo(Bar.class); assertThat(it.getProperty("field")) .hasValueSatisfying(nested -> assertThat(nested.getType()).isEqualTo(Character.class)); assertThat(it.getProperty("anotherField")) .hasValueSatisfying(nested -> assertThat(nested.getType()).isEqualTo(Integer.class)); }); } @Test // DATACMNS-783 public void usesTargetTypeDirectlyIfNoGenericsAreInvolved() { ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class); ClassTypeInformation<?> from = ClassTypeInformation.from(Bar.class); assertThat(root.getProperty("object")).hasValueSatisfying(it -> assertThat(it.specialize(from)).isEqualTo(from)); } @Test // DATACMNS-855 public void specializedTypeEqualsAndHashCode() { ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class); OptionalAssert<TypeInformation<?>> assertion = assertThat(root.getProperty("abstractBar")); assertion .map(it -> Pair.of(it.specialize(ClassTypeInformation.from(Bar.class)), it.specialize(ClassTypeInformation.from(Bar.class))))// .hasValueSatisfying(pair -> { assertThat(pair.getFirst()).isEqualTo(pair.getSecond()); assertThat(pair.getSecond()).isEqualTo(pair.getFirst()); assertThat(pair.getFirst().hashCode()).isEqualTo(pair.getSecond().hashCode()); }); } @Test // DATACMNS-896 public void prefersLocalTypeMappingOverNestedWithSameGenericType() { ClassTypeInformation<Concrete> information = ClassTypeInformation.from(Concrete.class); assertThat(information.getProperty("field")) .hasValueSatisfying(it -> assertThat(it.getType()).isEqualTo(Nested.class)); } @Test // DATACMNS-940 public void detectsJavaslangTraversableComponentType() { ClassTypeInformation<SampleTraversable> information = ClassTypeInformation.from(SampleTraversable.class); assertThat(information.getComponentType()) .hasValueSatisfying(it -> assertThat(it.getType()).isAssignableFrom(Integer.class)); } @Test // DATACMNS-940 public void detectsJavaslangMapComponentAndValueType() { ClassTypeInformation<SampleMap> information = ClassTypeInformation.from(SampleMap.class); assertThat(information.getComponentType()) .hasValueSatisfying(it -> assertThat(it.getType()).isAssignableFrom(String.class)); assertThat(information.getMapValueType()) .hasValueSatisfying(it -> assertThat(it.getType()).isAssignableFrom(Integer.class)); } static class StringMapContainer extends MapContainer<String> { } static class MapContainer<T> { Map<String, T> genericMap; Map<String, Calendar> map; } static class StringCollectionContainer extends CollectionContainer<String> { } @SuppressWarnings("rawtypes") static class CollectionContainer<T> { T[] array; Collection<T>[] foo; Set<String> set; Set rawSet; } static class GenericTypeWithBound<T extends Person> { T person; } static class AnotherGenericType<T extends Person, S extends GenericTypeWithBound<T>> { S nested; } static class SpecialGenericTypeWithBound extends GenericTypeWithBound<SpecialPerson> { } static abstract class SpecialPerson extends Person { protected SpecialPerson(Integer ssn, String firstName, String lastName) { super(ssn, firstName, lastName); } } static class GenericType<T, S> { Long index; T content; } static class ConcreteType extends GenericType<String, Object> { } static class GenericWrapper<S> { GenericType<S, Object> wrapped; } static class ConcreteWrapper extends GenericWrapper<String> { } static class AnotherConcreteWrapper extends GenericWrapper<Long> { } static class PropertyGetter { private String _name; public byte[] getName() { return _name.getBytes(); } } static class ClassWithWildCardBound { List<? extends String> wildcard; List<? extends Collection<? extends String>> complexWildcard; } static class Base<T> { } static class FirstExtension<T> extends Base<String> { public Base<GenericWrapper<T>> foo(Base<GenericWrapper<T>> param) { return null; } } static class SecondExtension extends FirstExtension<Long> { } interface GenericInterface<T> { } interface TypedClient { void stringMethod(GenericInterface<String> param); void longMethod(GenericInterface<Long> param); void boundToNumberMethod(GenericInterface<? extends Number> param); } class StringImplementation implements GenericInterface<String> { } class LongImplementation implements GenericInterface<Long> { } interface Product { Category getCategory(); } interface Category extends Identifiable { } interface Identifiable { Long getId(); } @SuppressWarnings("rawtypes") interface MyRawIterable extends Iterable {} interface MyIterable<T> extends Iterable<T> {} static class SuperGenerics { SortedMap<String, ? extends SortedMap<String, List<Person>>> seriously; } // DATACMNS-590 static abstract class GenericRoot<T extends GenericSub<?>> { List<T> subs; } static abstract class GenericSub<T extends GenericSubSub> { T subSub; } static abstract class GenericSubSub {} static class ConcreteRoot extends GenericRoot<ConcreteSub> {} static class ConcreteSub extends GenericSub<ConcreteSubSub> {} static class ConcreteSubSub extends GenericSubSub { String content; } // DATACMNS-594 static class Intermediate<T> { T content; } static abstract class GenericRootIntermediate<T> { Intermediate<T> intermediate; } static abstract class GenericInnerIntermediate<T> { Intermediate<T> intermediate; } static class ConcreteRootIntermediate extends GenericRootIntermediate<ConcreteInnerIntermediate> {} static class ConcreteInnerIntermediate extends GenericInnerIntermediate<Leaf> {} static class Leaf {} static class TypeWithAbstractGenericType<T, S> { AbstractBar<T, S> abstractBar; Object object; } static class Foo extends TypeWithAbstractGenericType<Character, Integer> {} static abstract class AbstractBar<T, S> {} static class Bar<T, S> extends AbstractBar<T, S> { T field; S anotherField; } // DATACMNS-896 static class SomeType<T> { T field; } static class Nested extends SomeType<String> {} static class Concrete extends SomeType<Nested> {} static interface SampleTraversable extends Traversable<Integer> {} static interface SampleMap extends javaslang.collection.Map<String, Integer> {} }