diff --git a/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/src/main/java/org/springframework/data/util/ClassTypeInformation.java index a3caf9d8b..aecfc1a8f 100644 --- a/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -111,12 +112,29 @@ public class ClassTypeInformation extends TypeDiscoverer { * @return */ private static Map, Type> getTypeVariableMap(Class type) { + return getTypeVariableMap(type, new HashSet()); + } + + @SuppressWarnings("deprecation") + private static Map, Type> getTypeVariableMap(Class type, Collection visited) { + + if (visited.contains(type)) { + return Collections.emptyMap(); + } else { + visited.add(type); + } Map source = GenericTypeResolver.getTypeVariableMap(type); Map, Type> map = new HashMap, Type>(source.size()); for (Entry entry : source.entrySet()) { + + Type value = entry.getValue(); map.put(entry.getKey(), entry.getValue()); + + if (value instanceof Class) { + map.putAll(getTypeVariableMap((Class) value, visited)); + } } return map; diff --git a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java index 9f91b123f..b51ceb062 100644 --- a/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java +++ b/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java @@ -57,10 +57,10 @@ class GenericArrayTypeInformation extends ParentTypeAwareTypeInformation { /* * (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#getComponentType() + * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType() */ @Override - public TypeInformation getComponentType() { + protected TypeInformation doGetComponentType() { Type componentType = type.getGenericComponentType(); return createInfo(componentType); diff --git a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java index acae86349..61e0554fb 100644 --- a/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java @@ -36,7 +36,7 @@ import org.springframework.util.StringUtils; class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation { private final ParameterizedType type; - private TypeInformation componentType; + private Boolean resolved; /** * Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}. @@ -51,12 +51,12 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation this.type = type; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#getMapValueType() + * @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType() */ @Override - public TypeInformation getMapValueType() { + protected TypeInformation doGetMapValueType() { if (Map.class.isAssignableFrom(getType())) { @@ -141,18 +141,13 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation return true; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.util.TypeDiscoverer#getComponentType() + * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType() */ @Override - public TypeInformation getComponentType() { - - if (componentType == null) { - this.componentType = createInfo(type.getActualTypeArguments()[0]); - } - - return this.componentType; + protected TypeInformation doGetComponentType() { + return createInfo(type.getActualTypeArguments()[0]); } /* @@ -201,10 +196,14 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation private boolean isResolvedCompletely() { + if (resolved != null) { + return resolved; + } + Type[] types = type.getActualTypeArguments(); if (types.length == 0) { - return false; + return cacheAndReturn(false); } for (Type type : types) { @@ -213,15 +212,21 @@ class ParameterizedTypeInformation extends ParentTypeAwareTypeInformation if (info instanceof ParameterizedTypeInformation) { if (!((ParameterizedTypeInformation) info).isResolvedCompletely()) { - return false; + return cacheAndReturn(false); } } if (!(info instanceof ClassTypeInformation)) { - return false; + return cacheAndReturn(false); } } - return true; + return cacheAndReturn(true); + } + + private boolean cacheAndReturn(boolean resolved) { + + this.resolved = resolved; + return resolved; } } diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 1f77cc84f..28aac5766 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -51,6 +51,12 @@ class TypeDiscoverer implements TypeInformation { private final Map, Type> typeVariableMap; private final Map> fieldTypes = new ConcurrentHashMap>(); + private boolean componentTypeResolved = false; + private TypeInformation componentType; + + private boolean valueTypeResolved = false; + private TypeInformation valueType; + private Class resolvedType; /** @@ -83,7 +89,7 @@ class TypeDiscoverer implements TypeInformation { * @param fieldType * @return */ - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) protected TypeInformation createInfo(Type fieldType) { if (fieldType.equals(this.type)) { @@ -135,7 +141,7 @@ class TypeDiscoverer implements TypeInformation { * @param type * @return */ - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) protected Class resolveType(Type type) { Map map = new HashMap(); @@ -313,6 +319,16 @@ class TypeDiscoverer implements TypeInformation { */ public TypeInformation getMapValueType() { + if (!valueTypeResolved) { + this.valueType = doGetMapValueType(); + this.valueTypeResolved = true; + } + + return this.valueType; + } + + protected TypeInformation doGetMapValueType() { + if (isMap()) { return getTypeArgument(Map.class, 1); } @@ -345,7 +361,17 @@ class TypeDiscoverer implements TypeInformation { * (non-Javadoc) * @see org.springframework.data.util.TypeInformation#getComponentType() */ - public TypeInformation getComponentType() { + public final TypeInformation getComponentType() { + + if (!componentTypeResolved) { + this.componentType = doGetComponentType(); + this.componentTypeResolved = true; + } + + return this.componentType; + } + + protected TypeInformation doGetComponentType() { Class rawType = getType(); diff --git a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java index d498afc16..844bea797 100644 --- a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java +++ b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java @@ -341,6 +341,18 @@ public class ClassTypeInformationUnitTests { assertThat(subSubType.getType(), is((Object) ConcreteSubSub.class)); } + /** + * @see DATACMNS-594 + */ + @Test + public void considersGenericsOfTypeBounds() { + + ClassTypeInformation customer = ClassTypeInformation.from(ConcreteRootIntermediate.class); + TypeInformation leafType = customer.getProperty("intermediate.content.intermediate.content"); + + assertThat(leafType.getType(), is((Object) Leaf.class)); + } + static class StringMapContainer extends MapContainer { } @@ -495,4 +507,24 @@ public class ClassTypeInformationUnitTests { static class ConcreteSubSub extends GenericSubSub { String content; } + + // DATACMNS-594 + + static class Intermediate { + T content; + } + + static abstract class GenericRootIntermediate { + Intermediate intermediate; + } + + static abstract class GenericInnerIntermediate { + Intermediate intermediate; + } + + static class ConcreteRootIntermediate extends GenericRootIntermediate {} + + static class ConcreteInnerIntermediate extends GenericInnerIntermediate {} + + static class Leaf {} }