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 lombok.EqualsAndHashCode;
19 import lombok.NonNull;
20 import lombok.RequiredArgsConstructor;
21
22 import java.beans.PropertyDescriptor;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.GenericArrayType;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.ParameterizedType;
28 import java.lang.reflect.Type;
29 import java.lang.reflect.TypeVariable;
30 import java.lang.reflect.WildcardType;
31 import java.util.*;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.stream.Collectors;
34
35 import org.springframework.beans.BeanUtils;
36 import org.springframework.core.GenericTypeResolver;
37 import org.springframework.lang.Nullable;
38 import org.springframework.util.Assert;
39 import org.springframework.util.ClassUtils;
40 import org.springframework.util.ReflectionUtils;
41
42 /**
43  * Basic {@link TypeDiscoverer} that contains basic functionality to discover property types.
44  *
45  * @author Oliver Gierke
46  * @author Christoph Strobl
47  * @author Mark Paluch
48  */

49 class TypeDiscoverer<S> implements TypeInformation<S> {
50
51     private static final Class<?>[] MAP_TYPES;
52
53     static {
54
55         ClassLoader classLoader = TypeDiscoverer.class.getClassLoader();
56
57         Set<Class<?>> mapTypes = new HashSet<>();
58         mapTypes.add(Map.class);
59
60         try {
61             mapTypes.add(ClassUtils.forName("io.vavr.collection.Map", classLoader));
62         } catch (ClassNotFoundException o_O) {}
63
64         MAP_TYPES = mapTypes.toArray(new Class[0]);
65     }
66
67     private final Type type;
68     private final Map<TypeVariable<?>, Type> typeVariableMap;
69     private final Map<String, Optional<TypeInformation<?>>> fieldTypes = new ConcurrentHashMap<>();
70     private final int hashCode;
71
72     private final Lazy<Class<S>> resolvedType;
73     private final Lazy<TypeInformation<?>> componentType;
74     private final Lazy<TypeInformation<?>> valueType;
75
76     /**
77      * Creates a new {@link TypeDiscoverer} for the given type, type variable map and parent.
78      *
79      * @param type must not be {@literal null}.
80      * @param typeVariableMap must not be {@literal null}.
81      */

82     protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {
83
84         Assert.notNull(type, "Type must not be null!");
85         Assert.notNull(typeVariableMap, "TypeVariableMap must not be null!");
86
87         this.type = type;
88         this.resolvedType = Lazy.of(() -> resolveType(type));
89         this.componentType = Lazy.of(this::doGetComponentType);
90         this.valueType = Lazy.of(this::doGetMapValueType);
91         this.typeVariableMap = typeVariableMap;
92         this.hashCode = 17 + 31 * type.hashCode() + 31 * typeVariableMap.hashCode();
93     }
94
95     /**
96      * Returns the type variable map.
97      *
98      * @return
99      */

100     protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
101         return typeVariableMap;
102     }
103
104     /**
105      * Creates {@link TypeInformation} for the given {@link Type}.
106      *
107      * @param fieldType must not be {@literal null}.
108      * @return
109      */

110     @SuppressWarnings({ "rawtypes""unchecked" })
111     protected TypeInformation<?> createInfo(Type fieldType) {
112
113         Assert.notNull(fieldType, "Field type must not be null!");
114
115         if (fieldType.equals(this.type)) {
116             return this;
117         }
118
119         if (fieldType instanceof Class) {
120             return ClassTypeInformation.from((Class<?>) fieldType);
121         }
122
123         if (fieldType instanceof ParameterizedType) {
124
125             ParameterizedType parameterizedType = (ParameterizedType) fieldType;
126             return new ParameterizedTypeInformation(parameterizedType, this);
127         }
128
129         if (fieldType instanceof TypeVariable) {
130
131             TypeVariable<?> variable = (TypeVariable<?>) fieldType;
132             return new TypeVariableTypeInformation(variable, this);
133         }
134
135         if (fieldType instanceof GenericArrayType) {
136             return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
137         }
138
139         if (fieldType instanceof WildcardType) {
140
141             WildcardType wildcardType = (WildcardType) fieldType;
142             Type[] bounds = wildcardType.getLowerBounds();
143
144             if (bounds.length > 0) {
145                 return createInfo(bounds[0]);
146             }
147
148             bounds = wildcardType.getUpperBounds();
149
150             if (bounds.length > 0) {
151                 return createInfo(bounds[0]);
152             }
153         }
154
155         throw new IllegalArgumentException();
156     }
157
158     /**
159      * Resolves the given type into a plain {@link Class}.
160      *
161      * @param type
162      * @return
163      */

164     @SuppressWarnings({ "unchecked""rawtypes" })
165     protected Class<S> resolveType(Type type) {
166
167         Map<TypeVariable, Type> map = new HashMap<>();
168         map.putAll(getTypeVariableMap());
169
170         return (Class<S>) GenericTypeResolver.resolveType(type, map);
171     }
172
173     /*
174      * (non-Javadoc)
175      * @see org.springframework.data.util.TypeInformation#getParameterTypes(java.lang.reflect.Constructor)
176      */

177     public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {
178
179         Assert.notNull(constructor, "Constructor must not be null!");
180
181         Type[] types = constructor.getGenericParameterTypes();
182         List<TypeInformation<?>> result = new ArrayList<>(types.length);
183
184         for (Type parameterType : types) {
185             result.add(createInfo(parameterType));
186         }
187
188         return result;
189     }
190
191     /*
192      * (non-Javadoc)
193      * @see org.springframework.data.util.TypeInformation#getProperty(java.lang.String)
194      */

195     @Nullable
196     public TypeInformation<?> getProperty(String fieldname) {
197
198         int separatorIndex = fieldname.indexOf('.');
199
200         if (separatorIndex == -1) {
201             return fieldTypes.computeIfAbsent(fieldname, this::getPropertyInformation).orElse(null);
202         }
203
204         String head = fieldname.substring(0, separatorIndex);
205         TypeInformation<?> info = getProperty(head);
206
207         if (info == null) {
208             return null;
209         }
210
211         return info.getProperty(fieldname.substring(separatorIndex + 1));
212     }
213
214     /**
215      * Returns the {@link TypeInformation} for the given atomic field. Will inspect fields first and return the type of a
216      * field if available. Otherwise it will fall back to a {@link PropertyDescriptor}.
217      *
218      * @see #getGenericType(PropertyDescriptor)
219      * @param fieldname
220      * @return
221      */

222     @SuppressWarnings("null")
223     private Optional<TypeInformation<?>> getPropertyInformation(String fieldname) {
224
225         Class<?> rawType = getType();
226         Field field = ReflectionUtils.findField(rawType, fieldname);
227
228         if (field != null) {
229             return Optional.of(createInfo(field.getGenericType()));
230         }
231
232         return findPropertyDescriptor(rawType, fieldname).map(it -> createInfo(getGenericType(it)));
233     }
234
235     /**
236      * Finds the {@link PropertyDescriptor} for the property with the given name on the given type.
237      *
238      * @param type must not be {@literal null}.
239      * @param fieldname must not be {@literal null} or empty.
240      * @return
241      */

242     private static Optional<PropertyDescriptor> findPropertyDescriptor(Class<?> type, String fieldname) {
243
244         PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, fieldname);
245
246         if (descriptor != null) {
247             return Optional.of(descriptor);
248         }
249
250         List<Class<?>> superTypes = new ArrayList<>();
251         superTypes.addAll(Arrays.asList(type.getInterfaces()));
252         superTypes.add(type.getSuperclass());
253
254         return Streamable.of(type.getInterfaces()).stream()//
255                 .flatMap(it -> Optionals.toStream(findPropertyDescriptor(it, fieldname)))//
256                 .findFirst();
257     }
258
259     /**
260      * Returns the generic type for the given {@link PropertyDescriptor}. Will inspect its read method followed by the
261      * first parameter of the write method.
262      *
263      * @param descriptor must not be {@literal null}
264      * @return
265      */

266     @Nullable
267     private static Type getGenericType(PropertyDescriptor descriptor) {
268
269         Method method = descriptor.getReadMethod();
270
271         if (method != null) {
272             return method.getGenericReturnType();
273         }
274
275         method = descriptor.getWriteMethod();
276
277         if (method == null) {
278             return null;
279         }
280
281         Type[] parameterTypes = method.getGenericParameterTypes();
282         return parameterTypes.length == 0 ? null : parameterTypes[0];
283     }
284
285     /*
286      * (non-Javadoc)
287      * @see org.springframework.data.util.TypeInformation#getType()
288      */

289     public Class<S> getType() {
290         return resolvedType.get();
291     }
292
293     /*
294      * (non-Javadoc)
295      * @see org.springframework.data.util.TypeInformation#getRawTypeInformation()
296      */

297     @Override
298     public ClassTypeInformation<?> getRawTypeInformation() {
299         return ClassTypeInformation.from(getType()).getRawTypeInformation();
300     }
301
302     /*
303      * (non-Javadoc)
304      * @see org.springframework.data.util.TypeInformation#getActualType()
305      */

306     @Nullable
307     public TypeInformation<?> getActualType() {
308
309         if (isMap()) {
310             return getMapValueType();
311         }
312
313         if (isCollectionLike()) {
314             return getComponentType();
315         }
316
317         return this;
318     }
319
320     /*
321      * (non-Javadoc)
322      * @see org.springframework.data.util.TypeInformation#isMap()
323      */

324     public boolean isMap() {
325
326         Class<S> type = getType();
327
328         for (Class<?> mapType : MAP_TYPES) {
329             if (mapType.isAssignableFrom(type)) {
330                 return true;
331             }
332         }
333
334         return false;
335     }
336
337     /*
338      * (non-Javadoc)
339      * @see org.springframework.data.util.TypeInformation#getMapValueType()
340      */

341     @Nullable
342     public TypeInformation<?> getMapValueType() {
343         return valueType.orElse(null);
344     }
345
346     @Nullable
347     protected TypeInformation<?> doGetMapValueType() {
348         return isMap() ? getTypeArgument(getBaseType(MAP_TYPES), 1)
349                 : getTypeArguments().stream().skip(1).findFirst().orElse(null);
350     }
351
352     /*
353      * (non-Javadoc)
354      * @see org.springframework.data.util.TypeInformation#isCollectionLike()
355      */

356     public boolean isCollectionLike() {
357
358         Class<?> rawType = getType();
359
360         return rawType.isArray() //
361                 || Iterable.class.equals(rawType) //
362                 || Collection.class.isAssignableFrom(rawType) //
363                 || Streamable.class.isAssignableFrom(rawType);
364     }
365
366     /*
367      * (non-Javadoc)
368      * @see org.springframework.data.util.TypeInformation#getComponentType()
369      */

370     @Nullable
371     public final TypeInformation<?> getComponentType() {
372         return componentType.orElse(null);
373     }
374
375     @Nullable
376     protected TypeInformation<?> doGetComponentType() {
377
378         Class<S> rawType = getType();
379
380         if (rawType.isArray()) {
381             return createInfo(rawType.getComponentType());
382         }
383
384         if (isMap()) {
385             return getTypeArgument(getBaseType(MAP_TYPES), 0);
386         }
387
388         if (Iterable.class.isAssignableFrom(rawType)) {
389             return getTypeArgument(Iterable.class, 0);
390         }
391
392         List<TypeInformation<?>> arguments = getTypeArguments();
393
394         return arguments.size() > 0 ? arguments.get(0) : null;
395     }
396
397     /*
398      * (non-Javadoc)
399      * @see org.springframework.data.util.TypeInformation#getReturnType(java.lang.reflect.Method)
400      */

401     public TypeInformation<?> getReturnType(Method method) {
402
403         Assert.notNull(method, "Method must not be null!");
404         return createInfo(method.getGenericReturnType());
405     }
406
407     /*
408      * (non-Javadoc)
409      * @see org.springframework.data.util.TypeInformation#getMethodParameterTypes(java.lang.reflect.Method)
410      */

411     public List<TypeInformation<?>> getParameterTypes(Method method) {
412
413         Assert.notNull(method, "Method most not be null!");
414
415         return Streamable.of(method.getGenericParameterTypes()).stream()//
416                 .map(this::createInfo)//
417                 .collect(Collectors.toList());
418     }
419
420     /*
421      * (non-Javadoc)
422      * @see org.springframework.data.util.TypeInformation#getSuperTypeInformation(java.lang.Class)
423      */

424     @Nullable
425     public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {
426
427         Class<?> rawType = getType();
428
429         if (!superType.isAssignableFrom(rawType)) {
430             return null;
431         }
432
433         if (getType().equals(superType)) {
434             return this;
435         }
436
437         List<Type> candidates = new ArrayList<>();
438         Type genericSuperclass = rawType.getGenericSuperclass();
439
440         if (genericSuperclass != null) {
441             candidates.add(genericSuperclass);
442         }
443
444         candidates.addAll(Arrays.asList(rawType.getGenericInterfaces()));
445
446         for (Type candidate : candidates) {
447
448             TypeInformation<?> candidateInfo = createInfo(candidate);
449
450             if (superType.equals(candidateInfo.getType())) {
451                 return candidateInfo;
452             } else {
453
454                 TypeInformation<?> nestedSuperType = candidateInfo.getSuperTypeInformation(superType);
455
456                 if (nestedSuperType != null) {
457                     return nestedSuperType;
458                 }
459             }
460         }
461
462         return null;
463     }
464
465     /*
466      * (non-Javadoc)
467      * @see org.springframework.data.util.TypeInformation#getTypeParameters()
468      */

469     public List<TypeInformation<?>> getTypeArguments() {
470         return Collections.emptyList();
471     }
472
473     /* (non-Javadoc)
474      * @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation)
475      */

476     public boolean isAssignableFrom(TypeInformation<?> target) {
477
478         TypeInformation<?> superTypeInformation = target.getSuperTypeInformation(getType());
479
480         return superTypeInformation == null ? false : superTypeInformation.equals(this);
481     }
482
483     /*
484      * (non-Javadoc)
485      * @see org.springframework.data.util.TypeInformation#specialize(org.springframework.data.util.ClassTypeInformation)
486      */

487     @Override
488     @SuppressWarnings("unchecked")
489     public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
490
491         Assert.notNull(type, "Type must not be null!");
492         Assert.isTrue(getType().isAssignableFrom(type.getType()),
493                 () -> String.format("%s must be assignable from %s", getType(), type.getType()));
494
495         List<TypeInformation<?>> typeArguments = getTypeArguments();
496
497         return (TypeInformation<? extends S>) (typeArguments.isEmpty() //
498                 ? type //
499                 : type.createInfo(new SyntheticParamterizedType(type, getTypeArguments())));
500     }
501
502     @Nullable
503     private TypeInformation<?> getTypeArgument(Class<?> bound, int index) {
504
505         Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound);
506
507         if (arguments != null) {
508             return createInfo(arguments[index]);
509         }
510
511         return getSuperTypeInformation(bound) instanceof ParameterizedTypeInformation //
512                 ? ClassTypeInformation.OBJECT //
513                 : null;
514     }
515
516     private Class<?> getBaseType(Class<?>[] candidates) {
517
518         Class<S> type = getType();
519
520         for (Class<?> candidate : candidates) {
521             if (candidate.isAssignableFrom(type)) {
522                 return candidate;
523             }
524         }
525
526         throw new IllegalArgumentException(String.format("Type %s not contained in candidates %s!", type, candidates));
527     }
528
529     /*
530      * (non-Javadoc)
531      * @see java.lang.Object#equals(java.lang.Object)
532      */

533     @Override
534     public boolean equals(@Nullable Object obj) {
535
536         if (obj == this) {
537             return true;
538         }
539
540         if (obj == null) {
541             return false;
542         }
543
544         if (!this.getClass().equals(obj.getClass())) {
545             return false;
546         }
547
548         TypeDiscoverer<?> that = (TypeDiscoverer<?>) obj;
549
550         if (!this.type.equals(that.type)) {
551             return false;
552         }
553
554         if (this.typeVariableMap.isEmpty() && that.typeVariableMap.isEmpty()) {
555             return true;
556         }
557
558         return this.typeVariableMap.equals(that.typeVariableMap);
559     }
560
561     /*
562      * (non-Javadoc)
563      * @see java.lang.Object#hashCode()
564      */

565     @Override
566     public int hashCode() {
567         return hashCode;
568     }
569
570     /**
571      * A synthetic {@link ParameterizedType}.
572      *
573      * @author Oliver Gierke
574      * @since 1.11
575      */

576     @EqualsAndHashCode
577     @RequiredArgsConstructor
578     private static class SyntheticParamterizedType implements ParameterizedType {
579
580         private final @NonNull ClassTypeInformation<?> typeInformation;
581         private final @NonNull List<TypeInformation<?>> typeParameters;
582
583         /*
584          * (non-Javadoc)
585          * @see java.lang.reflect.ParameterizedType#getRawType()
586          */

587         @Override
588         public Type getRawType() {
589             return typeInformation.getType();
590         }
591
592         /*
593          * (non-Javadoc)
594          * @see java.lang.reflect.ParameterizedType#getOwnerType()
595          */

596         @Override
597         @Nullable
598         public Type getOwnerType() {
599             return null;
600         }
601
602         /*
603          * (non-Javadoc)
604          * @see java.lang.reflect.ParameterizedType#getActualTypeArguments()
605          */

606         @Override
607         public Type[] getActualTypeArguments() {
608
609             Type[] result = new Type[typeParameters.size()];
610
611             for (int i = 0; i < typeParameters.size(); i++) {
612                 result[i] = typeParameters.get(i).getType();
613             }
614
615             return result;
616         }
617     }
618 }
619