1
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
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
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
100 protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
101 return typeVariableMap;
102 }
103
104
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
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
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
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
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
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
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
289 public Class<S> getType() {
290 return resolvedType.get();
291 }
292
293
297 @Override
298 public ClassTypeInformation<?> getRawTypeInformation() {
299 return ClassTypeInformation.from(getType()).getRawTypeInformation();
300 }
301
302
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
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
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
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
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
401 public TypeInformation<?> getReturnType(Method method) {
402
403 Assert.notNull(method, "Method must not be null!");
404 return createInfo(method.getGenericReturnType());
405 }
406
407
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
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
469 public List<TypeInformation<?>> getTypeArguments() {
470 return Collections.emptyList();
471 }
472
473
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
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
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
565 @Override
566 public int hashCode() {
567 return hashCode;
568 }
569
570
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
587 @Override
588 public Type getRawType() {
589 return typeInformation.getType();
590 }
591
592
596 @Override
597 @Nullable
598 public Type getOwnerType() {
599 return null;
600 }
601
602
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