1 /*
2  * Copyright 2011-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.data.util;
17
18 import java.lang.reflect.ParameterizedType;
19 import java.lang.reflect.Type;
20 import java.lang.reflect.TypeVariable;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.stream.IntStream;
31
32 import org.springframework.lang.Nullable;
33 import org.springframework.util.StringUtils;
34
35 /**
36  * Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent
37  * class we will have to resolve generic parameters against.
38  *
39  * @author Oliver Gierke
40  * @author Mark Paluch
41  * @author Christoph Strobl
42  */

43 class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
44
45     private final ParameterizedType type;
46     private final Lazy<Boolean> resolved;
47
48     /**
49      * Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
50      *
51      * @param type must not be {@literal null}
52      * @param parent must not be {@literal null}
53      */

54     public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
55
56         super(type, parent, calculateTypeVariables(type, parent));
57
58         this.type = type;
59         this.resolved = Lazy.of(() -> isResolvedCompletely());
60     }
61
62     /*
63      * (non-Javadoc)
64      * @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
65      */

66     @Override
67     @Nullable
68     protected TypeInformation<?> doGetMapValueType() {
69
70         if (Map.class.isAssignableFrom(getType())) {
71
72             Type[] arguments = type.getActualTypeArguments();
73
74             if (arguments.length > 1) {
75                 return createInfo(arguments[1]);
76             }
77         }
78
79         Class<?> rawType = getType();
80
81         Set<Type> supertypes = new HashSet<>();
82         Optional.ofNullable(rawType.getGenericSuperclass()).ifPresent(supertypes::add);
83         supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces()));
84
85         Optional<TypeInformation<?>> result = supertypes.stream()//
86                 .map(it -> Pair.of(it, resolveType(it)))//
87                 .filter(it -> Map.class.isAssignableFrom(it.getSecond()))//
88                 .<TypeInformation<?>> map(it -> {
89
90                     ParameterizedType parameterizedSupertype = (ParameterizedType) it.getFirst();
91                     Type[] arguments = parameterizedSupertype.getActualTypeArguments();
92                     return createInfo(arguments[1]);
93                 }).findFirst();
94
95         return result.orElseGet(super::doGetMapValueType);
96     }
97
98     /*
99      * (non-Javadoc)
100      * @see org.springframework.data.util.TypeDiscoverer#getTypeParameters()
101      */

102     @Override
103     public List<TypeInformation<?>> getTypeArguments() {
104
105         List<TypeInformation<?>> result = new ArrayList<>();
106
107         for (Type argument : type.getActualTypeArguments()) {
108             result.add(createInfo(argument));
109         }
110
111         return result;
112     }
113
114     /*
115      * (non-Javadoc)
116      * @see org.springframework.data.util.TypeDiscoverer#isAssignableFrom(org.springframework.data.util.TypeInformation)
117      */

118     @Override
119     public boolean isAssignableFrom(TypeInformation<?> target) {
120
121         if (this.equals(target)) {
122             return true;
123         }
124
125         Class<T> rawType = getType();
126         Class<?> rawTargetType = target.getType();
127
128         if (!rawType.isAssignableFrom(rawTargetType)) {
129             return false;
130         }
131
132         TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target
133                 : target.getSuperTypeInformation(rawType);
134
135         List<TypeInformation<?>> myParameters = getTypeArguments();
136         List<TypeInformation<?>> typeParameters = otherTypeInformation == null ? Collections.emptyList()
137                 : otherTypeInformation.getTypeArguments();
138
139         if (myParameters.size() != typeParameters.size()) {
140             return false;
141         }
142
143         for (int i = 0; i < myParameters.size(); i++) {
144             if (!myParameters.get(i).isAssignableFrom(typeParameters.get(i))) {
145                 return false;
146             }
147         }
148
149         return true;
150     }
151
152     /*
153      * (non-Javadoc)
154      * @see org.springframework.data.util.TypeDiscoverer#doGetComponentType()
155      */

156     @Override
157     @Nullable
158     protected TypeInformation<?> doGetComponentType() {
159         return createInfo(type.getActualTypeArguments()[0]);
160     }
161
162     /*
163      * (non-Javadoc)
164      * @see org.springframework.data.util.TypeDiscoverer#specialize(org.springframework.data.util.ClassTypeInformation)
165      */

166     @Override
167     @SuppressWarnings("unchecked")
168     public TypeInformation<? extends T> specialize(ClassTypeInformation<?> type) {
169
170         if (isResolvedCompletely()) {
171             return (TypeInformation<? extends T>) type;
172         }
173
174         TypeInformation<?> asSupertype = type.getSuperTypeInformation(getType());
175
176         if (asSupertype == null || !ParameterizedTypeInformation.class.isInstance(asSupertype)) {
177             return super.specialize(type);
178         }
179
180         return ((ParameterizedTypeInformation<?>) asSupertype).isResolvedCompletely() //
181                 ? (TypeInformation<? extends T>) type //
182                 : super.specialize(type);
183     }
184
185     /*
186      * (non-Javadoc)
187      * @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object)
188      */

189     @Override
190     public boolean equals(@Nullable Object obj) {
191
192         if (obj == this) {
193             return true;
194         }
195
196         if (!(obj instanceof ParameterizedTypeInformation)) {
197             return false;
198         }
199
200         ParameterizedTypeInformation<?> that = (ParameterizedTypeInformation<?>) obj;
201
202         if (this.isResolved() && that.isResolved()) {
203             return this.type.equals(that.type);
204         }
205
206         return super.equals(obj);
207     }
208
209     /*
210      * (non-Javadoc)
211      * @see org.springframework.data.util.ParentTypeAwareTypeInformation#hashCode()
212      */

213     @Override
214     public int hashCode() {
215         return isResolved() ? this.type.hashCode() : super.hashCode();
216     }
217
218     /*
219      * (non-Javadoc)
220      * @see java.lang.Object#toString()
221      */

222     @Override
223     public String toString() {
224
225         return String.format("%s<%s>", getType().getName(),
226                 StringUtils.collectionToCommaDelimitedString(getTypeArguments()));
227     }
228
229     private boolean isResolved() {
230         return resolved.get();
231     }
232
233     private boolean isResolvedCompletely() {
234
235         Type[] typeArguments = type.getActualTypeArguments();
236
237         if (typeArguments.length == 0) {
238             return false;
239         }
240
241         for (Type typeArgument : typeArguments) {
242
243             TypeInformation<?> info = createInfo(typeArgument);
244
245             if (info instanceof ParameterizedTypeInformation) {
246                 if (!((ParameterizedTypeInformation<?>) info).isResolvedCompletely()) {
247                     return false;
248                 }
249             }
250
251             if (!(info instanceof ClassTypeInformation)) {
252                 return false;
253             }
254         }
255
256         return true;
257     }
258
259     /**
260      * Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
261      * declared.
262      *
263      * @param type must not be {@literal null}.
264      * @param parent must not be {@literal null}.
265      * @return will never be {@literal null}.
266      */

267     private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, TypeDiscoverer<?> parent) {
268
269         Class<?> resolvedType = parent.resolveType(type);
270         TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
271         Type[] arguments = type.getActualTypeArguments();
272
273         Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<>(parent.getTypeVariableMap());
274
275         IntStream.range(0, typeParameters.length) //
276                 .mapToObj(it -> Pair.of(typeParameters[it], flattenTypeVariable(arguments[it], localTypeVariables))) //
277                 .forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond()));
278
279         return localTypeVariables;
280     }
281
282     /**
283      * Recursively resolves the type bound to the given {@link Type} in case it's a {@link TypeVariable} and there's an
284      * entry in the given type variables.
285      *
286      * @param source must not be {@literal null}.
287      * @param variables must not be {@literal null}.
288      * @return will never be {@literal null}.
289      */

290     private static Type flattenTypeVariable(Type source, Map<TypeVariable<?>, Type> variables) {
291
292         if (!(source instanceof TypeVariable)) {
293             return source;
294         }
295
296         Type value = variables.get(source);
297
298         return value == null ? source : flattenTypeVariable(value, variables);
299     }
300 }
301