1
2
3
4
5 package mockit.internal.reflection;
6
7 import edu.umd.cs.findbugs.annotations.NonNull;
8 import edu.umd.cs.findbugs.annotations.Nullable;
9
10 import java.lang.reflect.GenericArrayType;
11 import java.lang.reflect.GenericDeclaration;
12 import java.lang.reflect.Member;
13 import java.lang.reflect.ParameterizedType;
14 import java.lang.reflect.Type;
15 import java.lang.reflect.TypeVariable;
16 import java.lang.reflect.WildcardType;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 @SuppressWarnings("OverlyComplexClass")
25 public final class GenericTypeReflection {
26 @NonNull
27 private final Map<String, Type> typeParametersToTypeArguments;
28 @NonNull
29 private final Map<String, String> typeParametersToTypeArgumentNames;
30 private final boolean withSignatures;
31
32 public GenericTypeReflection(@NonNull Class<?> ownerClass, @Nullable Type genericType) {
33 this(ownerClass, genericType, true);
34 }
35
36 public GenericTypeReflection(@NonNull Class<?> ownerClass, @Nullable Type genericType, boolean withSignatures) {
37 typeParametersToTypeArguments = new HashMap<>(4);
38 typeParametersToTypeArgumentNames = withSignatures ? new HashMap<>(4) : Collections.<String, String> emptyMap();
39 this.withSignatures = withSignatures;
40 discoverTypeMappings(ownerClass, genericType);
41 }
42
43 private void discoverTypeMappings(@NonNull Class<?> rawType, @Nullable Type genericType) {
44 if (genericType instanceof ParameterizedType) {
45 addMappingsFromTypeParametersToTypeArguments(rawType, (ParameterizedType) genericType);
46 }
47
48 addGenericTypeMappingsForSuperTypes(rawType);
49 }
50
51 private void addGenericTypeMappingsForSuperTypes(@NonNull Class<?> rawType) {
52 Type superType = rawType;
53
54 while (superType != null && superType != Object.class) {
55 Class<?> superClass = (Class<?>) superType;
56 superType = superClass.getGenericSuperclass();
57
58 if (superType != null && superType != Object.class) {
59 superClass = addGenericTypeMappingsIfParameterized(superType);
60 superType = superClass;
61 }
62
63 addGenericTypeMappingsForInterfaces(superClass);
64 }
65 }
66
67 @NonNull
68 private Class<?> addGenericTypeMappingsIfParameterized(@NonNull Type superType) {
69 if (superType instanceof ParameterizedType) {
70 ParameterizedType genericSuperType = (ParameterizedType) superType;
71 Class<?> rawType = (Class<?>) genericSuperType.getRawType();
72 addMappingsFromTypeParametersToTypeArguments(rawType, genericSuperType);
73 return rawType;
74 }
75
76 return (Class<?>) superType;
77 }
78
79 private void addGenericTypeMappingsForInterfaces(@NonNull Class<?> classOrInterface) {
80 for (Type implementedInterface : classOrInterface.getGenericInterfaces()) {
81 Class<?> implementedType = addGenericTypeMappingsIfParameterized(implementedInterface);
82 addGenericTypeMappingsForInterfaces(implementedType);
83 }
84 }
85
86 private void addMappingsFromTypeParametersToTypeArguments(@NonNull Class<?> rawType,
87 @NonNull ParameterizedType genericType) {
88 String ownerTypeDesc = getOwnerClassDesc(rawType);
89 TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
90 Type[] typeArguments = genericType.getActualTypeArguments();
91
92 for (int i = 0, n = typeParameters.length; i < n; i++) {
93 TypeVariable<?> typeParam = typeParameters[i];
94 String typeVarName = typeParam.getName();
95
96 if (typeParametersToTypeArguments.containsKey(ownerTypeDesc + ':' + typeVarName)) {
97 continue;
98 }
99
100 Type typeArg = typeArguments[i];
101
102 if (typeArg instanceof Class<?>) {
103 addMappingForClassType(ownerTypeDesc, typeVarName, typeArg);
104 } else if (typeArg instanceof TypeVariable<?>) {
105 addMappingForTypeVariable(ownerTypeDesc, typeVarName, typeArg);
106 } else if (typeArg instanceof ParameterizedType) {
107 addMappingForParameterizedType(ownerTypeDesc, typeVarName, typeArg);
108 } else if (typeArg instanceof GenericArrayType) {
109 addMappingForArrayType(ownerTypeDesc, typeVarName, typeArg);
110 } else {
111 addMappingForFirstTypeBound(ownerTypeDesc, typeParam);
112 }
113 }
114
115 Type outerType = genericType.getOwnerType();
116
117 if (outerType instanceof ParameterizedType) {
118 ParameterizedType parameterizedOuterType = (ParameterizedType) outerType;
119 Class<?> rawOuterType = (Class<?>) parameterizedOuterType.getRawType();
120 addMappingsFromTypeParametersToTypeArguments(rawOuterType, parameterizedOuterType);
121 }
122 }
123
124 private void addMappingForClassType(@NonNull String ownerTypeDesc, @NonNull String typeName,
125 @NonNull Type typeArg) {
126 String mappedTypeArgName = null;
127
128 if (withSignatures) {
129 Class<?> classArg = (Class<?>) typeArg;
130 String ownerClassDesc = getOwnerClassDesc(classArg);
131 mappedTypeArgName = classArg.isArray() ? ownerClassDesc : 'L' + ownerClassDesc;
132 }
133
134 addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
135 }
136
137 private void addMappingForTypeVariable(@NonNull String ownerTypeDesc, @NonNull String typeName,
138 @NonNull Type typeArg) {
139 @Nullable
140 String mappedTypeArgName = null;
141
142 if (withSignatures) {
143 TypeVariable<?> typeVar = (TypeVariable<?>) typeArg;
144 String ownerClassDesc = getOwnerClassDesc(typeVar);
145 String intermediateTypeArg = ownerClassDesc + ":T" + typeVar.getName();
146 mappedTypeArgName = typeParametersToTypeArgumentNames.get(intermediateTypeArg);
147 }
148
149 addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
150 }
151
152 private void addMappingForParameterizedType(@NonNull String ownerTypeDesc, @NonNull String typeName,
153 @NonNull Type typeArg) {
154 String mappedTypeArgName = getMappedTypeArgName(typeArg);
155 addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
156 }
157
158 @Nullable
159 private String getMappedTypeArgName(@NonNull Type typeArg) {
160 if (withSignatures) {
161 Class<?> classType = getClassType(typeArg);
162 return 'L' + getOwnerClassDesc(classType);
163 }
164
165 return null;
166 }
167
168 private void addMappingForArrayType(@NonNull String ownerTypeDesc, @NonNull String typeName,
169 @NonNull Type typeArg) {
170 String mappedTypeArgName = null;
171
172 if (withSignatures) {
173 mappedTypeArgName = getMappedTypeArgName((GenericArrayType) typeArg);
174 }
175
176 addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
177 }
178
179 private void addMappingForFirstTypeBound(@NonNull String ownerTypeDesc, @NonNull TypeVariable<?> typeParam) {
180 Type typeArg = typeParam.getBounds()[0];
181 String mappedTypeArgName = getMappedTypeArgName(typeArg);
182 addTypeMapping(ownerTypeDesc, typeParam.getName(), typeArg, mappedTypeArgName);
183 }
184
185 @NonNull
186 private static String getOwnerClassDesc(@NonNull Class<?> rawType) {
187 return rawType.getName().replace('.', '/');
188 }
189
190 @NonNull
191 private Class<?> getClassType(@NonNull Type type) {
192 if (type instanceof ParameterizedType) {
193 ParameterizedType parameterizedType = (ParameterizedType) type;
194 return (Class<?>) parameterizedType.getRawType();
195 }
196
197 if (type instanceof TypeVariable<?>) {
198 TypeVariable<?> typeVar = (TypeVariable<?>) type;
199 String typeVarKey = getTypeVariableKey(typeVar);
200 @Nullable
201 Type typeArg = typeParametersToTypeArguments.get(typeVarKey);
202
203 if (typeArg == null) {
204 throw new IllegalArgumentException("Unable to resolve type variable \"" + typeVar.getName() + '"');
205 }
206
207
208 return getClassType(typeArg);
209 }
210
211 return (Class<?>) type;
212 }
213
214 @NonNull
215 private String getMappedTypeArgName(@NonNull GenericArrayType arrayType) {
216 StringBuilder argName = new StringBuilder(20);
217 argName.append('[');
218
219 while (true) {
220 Type componentType = arrayType.getGenericComponentType();
221
222 if (!(componentType instanceof GenericArrayType)) {
223 Class<?> classType = getClassType(componentType);
224 argName.append('L').append(getOwnerClassDesc(classType));
225 return argName.toString();
226 }
227 argName.append('[');
228
229 arrayType = (GenericArrayType) componentType;
230 }
231 }
232
233 private void addTypeMapping(@NonNull String ownerTypeDesc, @NonNull String typeVarName, @NonNull Type mappedTypeArg,
234 @Nullable String mappedTypeArgName) {
235 typeParametersToTypeArguments.put(ownerTypeDesc + ':' + typeVarName, mappedTypeArg);
236
237 if (mappedTypeArgName != null) {
238 addTypeMapping(ownerTypeDesc, typeVarName, mappedTypeArgName);
239 }
240 }
241
242 private void addTypeMapping(@NonNull String ownerTypeDesc, @NonNull String typeVarName,
243 @NonNull String mappedTypeArgName) {
244 String typeMappingKey = ownerTypeDesc + ":T" + typeVarName;
245 typeParametersToTypeArgumentNames.put(typeMappingKey, mappedTypeArgName);
246 }
247
248 public final class GenericSignature {
249 private final List<String> parameters = new ArrayList<>();
250 private final String parameterTypeDescs;
251 private final int lengthOfParameterTypeDescs;
252 private int currentPos;
253
254 GenericSignature(@NonNull String signature) {
255 int p = signature.indexOf('(');
256 int q = signature.lastIndexOf(')');
257 parameterTypeDescs = signature.substring(p + 1, q);
258 lengthOfParameterTypeDescs = parameterTypeDescs.length();
259 addTypeDescsToList();
260 }
261
262 private void addTypeDescsToList() {
263 while (currentPos < lengthOfParameterTypeDescs) {
264 addNextParameter();
265 }
266 }
267
268 private void addNextParameter() {
269 int startPos = currentPos;
270 int endPos;
271 char c = parameterTypeDescs.charAt(startPos);
272
273 switch (c) {
274 case 'T':
275 endPos = parameterTypeDescs.indexOf(';', startPos);
276 currentPos = endPos;
277 break;
278 case 'L':
279 endPos = advanceToEndOfTypeDesc();
280 break;
281 case '[': {
282 char elemTypeStart = firstCharacterOfArrayElementType();
283 if (elemTypeStart == 'T') {
284 endPos = parameterTypeDescs.indexOf(';', startPos);
285 currentPos = endPos;
286 } else if (elemTypeStart == 'L') {
287 endPos = advanceToEndOfTypeDesc();
288 } else {
289 endPos = currentPos + 1;
290 }
291 break;
292 }
293 default:
294 endPos = currentPos + 1;
295 break;
296 }
297
298 currentPos++;
299 String parameter = parameterTypeDescs.substring(startPos, endPos);
300 parameters.add(parameter);
301 }
302
303 private int advanceToEndOfTypeDesc() {
304 char c = '\0';
305
306 do {
307 currentPos++;
308 if (currentPos == lengthOfParameterTypeDescs) {
309 break;
310 }
311 c = parameterTypeDescs.charAt(currentPos);
312 } while (c != ';' && c != '<');
313
314 int endPos = currentPos;
315
316 if (c == '<') {
317 advancePastTypeArguments();
318 currentPos++;
319 }
320
321 return endPos;
322 }
323
324 private char firstCharacterOfArrayElementType() {
325 char c;
326
327 do {
328 currentPos++;
329 c = parameterTypeDescs.charAt(currentPos);
330 } while (c == '[');
331
332 return c;
333 }
334
335 private void advancePastTypeArguments() {
336 int angleBracketDepth = 1;
337
338 do {
339 currentPos++;
340 char c = parameterTypeDescs.charAt(currentPos);
341 if (c == '>') {
342 angleBracketDepth--;
343 } else if (c == '<') {
344 angleBracketDepth++;
345 }
346 } while (angleBracketDepth > 0);
347 }
348
349 public boolean satisfiesGenericSignature(@NonNull String otherSignature) {
350 GenericSignature other = new GenericSignature(otherSignature);
351 return areMatchingSignatures(other);
352 }
353
354 private boolean areMatchingSignatures(@NonNull GenericSignature other) {
355 int n = parameters.size();
356
357 if (n != other.parameters.size()) {
358 return false;
359 }
360
361 for (int i = 0; i < n; i++) {
362 String p1 = other.parameters.get(i);
363 String p2 = parameters.get(i);
364
365 if (!areParametersOfSameType(p1, p2)) {
366 return false;
367 }
368 }
369
370 return true;
371 }
372
373 @SuppressWarnings("MethodWithMultipleLoops")
374 private boolean areParametersOfSameType(@NonNull String param1, @NonNull String param2) {
375 if (param1.equals(param2)) {
376 return true;
377 }
378
379 int i = -1;
380 char c;
381 do {
382 i++;
383 c = param1.charAt(i);
384 } while (c == '[');
385 if (c != 'T') {
386 return false;
387 }
388
389 String typeVarName1 = param1.substring(i);
390 String typeVarName2 = param2.substring(i);
391 String typeArg1 = null;
392
393 for (Entry<String, String> typeParamAndArgName : typeParametersToTypeArgumentNames.entrySet()) {
394 String typeMappingKey = typeParamAndArgName.getKey();
395 String typeVarName = typeMappingKey.substring(typeMappingKey.indexOf(':') + 1);
396
397 if (typeVarName.equals(typeVarName1)) {
398 typeArg1 = typeParamAndArgName.getValue();
399 break;
400 }
401 }
402
403 return typeVarName2.equals(typeArg1);
404 }
405
406 public boolean satisfiesSignature(@NonNull String otherSignature) {
407 GenericSignature other = new GenericSignature(otherSignature);
408 return other.areMatchingSignatures(this);
409 }
410 }
411
412 @NonNull
413 public GenericSignature parseSignature(@NonNull String genericSignature) {
414 return new GenericSignature(genericSignature);
415 }
416
417 @NonNull
418 public String resolveSignature(@NonNull String ownerTypeDesc, @NonNull String genericSignature) {
419 addTypeArgumentsIfAvailable(ownerTypeDesc, genericSignature);
420
421 int p = genericSignature.lastIndexOf(')') + 1;
422 int q = genericSignature.length();
423 String returnType = genericSignature.substring(p, q);
424 String resolvedReturnType = replaceTypeParametersWithActualTypes(ownerTypeDesc, returnType);
425
426 StringBuilder finalSignature = new StringBuilder(genericSignature);
427 finalSignature.replace(p, q, resolvedReturnType);
428 return finalSignature.toString();
429 }
430
431 private void addTypeArgumentsIfAvailable(@NonNull String ownerTypeDesc, @NonNull String signature) {
432 int firstParen = signature.indexOf('(');
433 if (firstParen == 0) {
434 return;
435 }
436
437 int p = 1;
438 boolean lastMappingFound = false;
439
440 while (!lastMappingFound) {
441 int q = signature.indexOf(':', p);
442 String typeVar = signature.substring(p, q);
443
444 q++;
445
446 if (signature.charAt(q) == ':') {
447 q++;
448 }
449
450 int r = signature.indexOf(':', q);
451
452 if (r < 0) {
453 r = firstParen - 2;
454 lastMappingFound = true;
455 } else {
456 r = signature.lastIndexOf(';', r);
457 p = r + 1;
458 }
459
460 String typeArg = signature.substring(q, r);
461 addTypeMapping(ownerTypeDesc, typeVar, typeArg);
462 }
463 }
464
465 @NonNull
466 private String replaceTypeParametersWithActualTypes(@NonNull String ownerTypeDesc, @NonNull String typeDesc) {
467 if (typeDesc.charAt(0) == 'T' && !typeParametersToTypeArgumentNames.isEmpty()) {
468 return replaceTypeParameters(ownerTypeDesc, typeDesc);
469 }
470
471 int p = typeDesc.indexOf('<');
472
473 if (p < 0) {
474 return typeDesc;
475 }
476
477 String resolvedTypeDesc = typeDesc;
478
479 for (Entry<String, String> paramAndArg : typeParametersToTypeArgumentNames.entrySet()) {
480 String typeMappingKey = paramAndArg.getKey();
481 String typeParam = typeMappingKey.substring(typeMappingKey.indexOf(':') + 1) + ';';
482 String typeArg = paramAndArg.getValue() + ';';
483 resolvedTypeDesc = resolvedTypeDesc.replace(typeParam, typeArg);
484 }
485
486 return resolvedTypeDesc;
487 }
488
489 @NonNull
490 private String replaceTypeParameters(@NonNull String ownerTypeDesc, @NonNull String typeDesc) {
491 String typeParameter = typeDesc.substring(0, typeDesc.length() - 1);
492
493 while (true) {
494 @Nullable
495 String typeArg = typeParametersToTypeArgumentNames.get(ownerTypeDesc + ':' + typeParameter);
496
497 if (typeArg == null) {
498 return typeDesc;
499 }
500
501 if (typeArg.charAt(0) != 'T') {
502 return typeArg + ';';
503 }
504
505 typeParameter = typeArg;
506 }
507 }
508
509 @NonNull
510 public Type resolveTypeVariable(@NonNull TypeVariable<?> typeVariable) {
511 String typeVarKey = getTypeVariableKey(typeVariable);
512 @Nullable
513 Type typeArgument = typeParametersToTypeArguments.get(typeVarKey);
514
515 if (typeArgument == null) {
516 typeArgument = typeVariable.getBounds()[0];
517 }
518
519 if (typeArgument instanceof TypeVariable<?>) {
520 typeArgument = resolveTypeVariable((TypeVariable<?>) typeArgument);
521 }
522
523 return typeArgument;
524 }
525
526 @NonNull
527 private static String getTypeVariableKey(@NonNull TypeVariable<?> typeVariable) {
528 String ownerClassDesc = getOwnerClassDesc(typeVariable);
529 return ownerClassDesc + ':' + typeVariable.getName();
530 }
531
532 @NonNull
533 private static String getOwnerClassDesc(@NonNull TypeVariable<?> typeVariable) {
534 GenericDeclaration owner = typeVariable.getGenericDeclaration();
535 Class<?> ownerClass = owner instanceof Member ? ((Member) owner).getDeclaringClass() : (Class<?>) owner;
536 return getOwnerClassDesc(ownerClass);
537 }
538
539 public boolean areMatchingTypes(@NonNull Type declarationType, @NonNull Type realizationType) {
540 if (declarationType.equals(realizationType)) {
541 return true;
542 }
543
544 if (declarationType instanceof Class<?>) {
545 if (realizationType instanceof Class<?>) {
546 return ((Class<?>) declarationType).isAssignableFrom((Class<?>) realizationType);
547 }
548 } else if (declarationType instanceof TypeVariable<?>) {
549 if (realizationType instanceof TypeVariable<?>) {
550 return false;
551 }
552
553
554 if (areMatchingTypes((TypeVariable<?>) declarationType, realizationType)) {
555 return true;
556 }
557 } else if (declarationType instanceof ParameterizedType) {
558 ParameterizedType parameterizedDeclarationType = (ParameterizedType) declarationType;
559 ParameterizedType parameterizedRealizationType = getParameterizedType(realizationType);
560
561 if (parameterizedRealizationType != null) {
562 return areMatchingTypes(parameterizedDeclarationType, parameterizedRealizationType);
563 }
564 }
565
566 return false;
567 }
568
569 @Nullable
570 private static ParameterizedType getParameterizedType(@NonNull Type realizationType) {
571 if (realizationType instanceof ParameterizedType) {
572 return (ParameterizedType) realizationType;
573 }
574
575 if (realizationType instanceof Class<?>) {
576 return findRealizationSupertype((Class<?>) realizationType);
577 }
578
579 return null;
580 }
581
582 @Nullable
583 private static ParameterizedType findRealizationSupertype(@NonNull Class<?> realizationType) {
584 Type realizationSuperclass = realizationType.getGenericSuperclass();
585 ParameterizedType parameterizedRealizationType = null;
586
587 if (realizationSuperclass instanceof ParameterizedType) {
588 parameterizedRealizationType = (ParameterizedType) realizationSuperclass;
589 } else {
590 for (Type realizationSupertype : realizationType.getGenericInterfaces()) {
591 if (realizationSupertype instanceof ParameterizedType) {
592 parameterizedRealizationType = (ParameterizedType) realizationSupertype;
593 break;
594 }
595 }
596 }
597
598 return parameterizedRealizationType;
599 }
600
601 private boolean areMatchingTypes(@NonNull TypeVariable<?> declarationType, @NonNull Type realizationType) {
602 String typeVarKey = getTypeVariableKey(declarationType);
603 @Nullable
604 Type resolvedType = typeParametersToTypeArguments.get(typeVarKey);
605
606 return resolvedType != null && (resolvedType.equals(realizationType)
607 || typeSatisfiesResolvedTypeVariable(resolvedType, realizationType));
608 }
609
610 private boolean areMatchingTypes(@NonNull ParameterizedType declarationType,
611 @NonNull ParameterizedType realizationType) {
612 return declarationType.getRawType().equals(realizationType.getRawType())
613 && haveMatchingActualTypeArguments(declarationType, realizationType);
614 }
615
616 private boolean haveMatchingActualTypeArguments(@NonNull ParameterizedType declarationType,
617 @NonNull ParameterizedType realizationType) {
618 Type[] declaredTypeArguments = declarationType.getActualTypeArguments();
619 Type[] concreteTypeArguments = realizationType.getActualTypeArguments();
620
621 for (int i = 0, n = declaredTypeArguments.length; i < n; i++) {
622 Type declaredTypeArg = declaredTypeArguments[i];
623 Type concreteTypeArg = concreteTypeArguments[i];
624
625 if (declaredTypeArg instanceof TypeVariable<?>) {
626 if (areMatchingTypeArguments((TypeVariable<?>) declaredTypeArg, concreteTypeArg)) {
627 continue;
628 }
629 } else if (areMatchingTypes(declaredTypeArg, concreteTypeArg)) {
630 continue;
631 }
632
633 return false;
634 }
635
636 return true;
637 }
638
639 @SuppressWarnings("RedundantIfStatement")
640 private boolean areMatchingTypeArguments(@NonNull TypeVariable<?> declaredType, @NonNull Type concreteType) {
641 String typeVarKey = getTypeVariableKey(declaredType);
642 @Nullable
643 Type resolvedType = typeParametersToTypeArguments.get(typeVarKey);
644
645 if (resolvedType != null) {
646 if (resolvedType.equals(concreteType) || concreteType instanceof Class<?>
647 && typeSatisfiesResolvedTypeVariable(resolvedType, (Class<?>) concreteType)) {
648 return true;
649 }
650
651 if (concreteType instanceof WildcardType
652 && typeSatisfiesUpperBounds(resolvedType, ((WildcardType) concreteType).getUpperBounds())) {
653 return true;
654 }
655 } else if (typeSatisfiesUpperBounds(concreteType, declaredType.getBounds())) {
656 return true;
657 }
658
659 return false;
660 }
661
662 private boolean typeSatisfiesResolvedTypeVariable(@NonNull Type resolvedType, @NonNull Type realizationType) {
663 Class<?> realizationClass = getClassType(realizationType);
664 return typeSatisfiesResolvedTypeVariable(resolvedType, realizationClass);
665 }
666
667 private boolean typeSatisfiesResolvedTypeVariable(@NonNull Type resolvedType, @NonNull Class<?> realizationType) {
668 Class<?> resolvedClass = getClassType(resolvedType);
669 return resolvedClass.isAssignableFrom(realizationType);
670 }
671
672 private boolean typeSatisfiesUpperBounds(@NonNull Type type, @NonNull Type[] upperBounds) {
673 Class<?> classType = getClassType(type);
674
675 for (Type upperBound : upperBounds) {
676 Class<?> classBound = getClassType(upperBound);
677
678 if (!classBound.isAssignableFrom(classType)) {
679 return false;
680 }
681 }
682
683 return true;
684 }
685 }