From c5721cfc0c642d24d1d7bc9d60a5ed1618faf796 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 14 Nov 2014 16:34:31 +0100 Subject: [PATCH] DATACMNS-594 - Fixed creation of type variable map to extend into detected bounds. The initial setup of the type variable map now unfolds the detected types in the map to make sure we detect all type variables present in the current scope. Added caching of component and map value TypeInformation instances to avoid repeated creation. --- .../data/util/ClassTypeInformation.java | 18 +++++++++ .../util/GenericArrayTypeInformation.java | 4 +- .../util/ParameterizedTypeInformation.java | 39 +++++++++++-------- .../data/util/TypeDiscoverer.java | 32 +++++++++++++-- .../util/ClassTypeInformationUnitTests.java | 32 +++++++++++++++ 5 files changed, 103 insertions(+), 22 deletions(-) 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 {} }