View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.di.impl;
20  
21  import java.lang.reflect.GenericArrayType;
22  import java.lang.reflect.ParameterizedType;
23  import java.lang.reflect.Type;
24  import java.lang.reflect.TypeVariable;
25  import java.lang.reflect.WildcardType;
26  import java.util.ArrayDeque;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.Deque;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Set;
35  import java.util.concurrent.ConcurrentHashMap;
36  import java.util.function.Function;
37  
38  import org.apache.maven.api.annotations.Nullable;
39  
40  import static java.util.stream.Collectors.joining;
41  
42  /**
43   * Various helper methods for type processing
44   */
45  public class Types {
46      public static final Type[] NO_TYPES = new Type[0];
47      public static final WildcardType WILDCARD_TYPE_ANY = new WildcardTypeImpl(new Type[] {Object.class}, new Type[0]);
48      private static final Map<Type, Map<TypeVariable<?>, Type>> TYPE_BINDINGS_CACHE = new ConcurrentHashMap<>();
49  
50      /**
51       * Returns a raw {@link Class} for a given {@link Type}.
52       * <p>
53       * A type can be any of {@link Class}, {@link ParameterizedType}, {@link WildcardType},
54       * {@link GenericArrayType} or {@link TypeVariable}
55       */
56      public static Class<?> getRawType(Type type) {
57          if (type instanceof Class) {
58              return (Class<?>) type;
59          } else if (type instanceof ParameterizedType) {
60              return (Class<?>) ((ParameterizedType) type).getRawType();
61          } else if (type instanceof WildcardType) {
62              Type[] upperBounds = ((WildcardType) type).getUpperBounds();
63              return getRawType(getUppermostType(upperBounds));
64          } else if (type instanceof GenericArrayType) {
65              Class<?> rawComponentType = getRawType(((GenericArrayType) type).getGenericComponentType());
66              try {
67                  return Class.forName("[L" + rawComponentType.getName() + ";");
68              } catch (ClassNotFoundException e) {
69                  throw new RuntimeException(e);
70              }
71          } else if (type instanceof TypeVariable) {
72              return getRawType(getUppermostType(((TypeVariable<?>) type).getBounds()));
73          } else {
74              throw new IllegalArgumentException("Unsupported type: " + type);
75          }
76      }
77  
78      /**
79       * Returns the most common type among given types
80       */
81      public static Type getUppermostType(Type[] types) {
82          Type result = types[0];
83          for (int i = 1; i < types.length; i++) {
84              Type type = types[i];
85              if (isAssignable(type, result)) {
86                  result = type;
87                  continue;
88              } else if (isAssignable(result, type)) {
89                  continue;
90              }
91              throw new IllegalArgumentException("Unrelated types: " + result + " , " + type);
92          }
93          return result;
94      }
95  
96      /**
97       * Returns an array of actual type arguments for a given {@link Type}
98       *
99       * @param type type whose actual type arguments should be retrieved
100      * @return an array of actual type arguments for a given {@link Type}
101      */
102     public static Type[] getActualTypeArguments(Type type) {
103         if (type instanceof Class) {
104             return ((Class<?>) type).isArray() ? new Type[] {((Class<?>) type).getComponentType()} : NO_TYPES;
105         } else if (type instanceof ParameterizedType) {
106             return ((ParameterizedType) type).getActualTypeArguments();
107         } else if (type instanceof GenericArrayType) {
108             return new Type[] {((GenericArrayType) type).getGenericComponentType()};
109         }
110         throw new IllegalArgumentException("Unsupported type: " + type);
111     }
112 
113     /**
114      * Returns a map of type bindings for a given {@link Type}
115      */
116     public static Map<TypeVariable<?>, Type> getTypeBindings(Type type) {
117         Type[] typeArguments = getActualTypeArguments(type);
118         if (typeArguments.length == 0) {
119             return Collections.emptyMap();
120         }
121         TypeVariable<?>[] typeVariables = getRawType(type).getTypeParameters();
122         Map<TypeVariable<?>, Type> map = new HashMap<>();
123         for (int i = 0; i < typeVariables.length; i++) {
124             map.put(typeVariables[i], typeArguments[i]);
125         }
126         return map;
127     }
128 
129     /**
130      * Returns a map of all type bindings for a given {@link Type}.
131      * Includes type bindings from a whole class hierarchy
132      */
133     public static Map<TypeVariable<?>, Type> getAllTypeBindings(Type type) {
134         return TYPE_BINDINGS_CACHE.computeIfAbsent(type, t -> {
135             Map<TypeVariable<?>, Type> mapping = new HashMap<>();
136             getAllTypeBindingsImpl(t, mapping);
137             return mapping;
138         });
139     }
140 
141     private static void getAllTypeBindingsImpl(Type type, Map<TypeVariable<?>, Type> mapping) {
142         Class<?> cls = getRawType(type);
143 
144         if (type instanceof ParameterizedType) {
145             Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
146             if (typeArguments.length != 0) {
147                 TypeVariable<? extends Class<?>>[] typeVariables = cls.getTypeParameters();
148                 for (int i = 0; i < typeArguments.length; i++) {
149                     Type typeArgument = typeArguments[i];
150                     mapping.put(
151                             typeVariables[i],
152                             typeArgument instanceof TypeVariable
153                                     ? Objects.requireNonNull(mapping.get(typeArgument))
154                                     : typeArgument);
155                 }
156             }
157         }
158 
159         Type superclass = cls.getGenericSuperclass();
160         if (superclass != null) {
161             getAllTypeBindingsImpl(superclass, mapping);
162         }
163 
164         for (Type anInterface : cls.getGenericInterfaces()) {
165             getAllTypeBindingsImpl(anInterface, mapping);
166         }
167     }
168 
169     /**
170      * Binds a given type with actual type arguments
171      *
172      * @param type     a type to be bound
173      * @param bindings a map of actual types
174      */
175     public static Type bind(Type type, Map<TypeVariable<?>, Type> bindings) {
176         return bind(type, bindings::get);
177     }
178 
179     /**
180      * Binds a given type with actual type arguments
181      *
182      * @param type     a type to be bound
183      * @param bindings a lookup function for actual types
184      */
185     public static Type bind(Type type, Function<TypeVariable<?>, Type> bindings) {
186         if (type instanceof Class) {
187             return type;
188         }
189         if (type instanceof TypeVariable<?>) {
190             TypeVariable<?> typeVariable = (TypeVariable<?>) type;
191             Type actualType = bindings.apply(typeVariable);
192             if (actualType == null) {
193                 throw new TypeNotBoundException("Type variable not found: " + typeVariable + " ( "
194                         + typeVariable.getGenericDeclaration() + " ) ");
195             }
196             return actualType;
197         }
198         if (type instanceof ParameterizedType) {
199             ParameterizedType parameterizedType = (ParameterizedType) type;
200             Type[] typeArguments = parameterizedType.getActualTypeArguments();
201             Type[] typeArguments2 = new Type[typeArguments.length];
202             for (int i = 0; i < typeArguments.length; i++) {
203                 typeArguments2[i] = bind(typeArguments[i], bindings);
204             }
205             return new ParameterizedTypeImpl(
206                     parameterizedType.getOwnerType(), parameterizedType.getRawType(), typeArguments2);
207         }
208         if (type instanceof GenericArrayType) {
209             Type componentType = ((GenericArrayType) type).getGenericComponentType();
210             return new GenericArrayTypeImpl(bind(componentType, bindings));
211         }
212         if (type instanceof WildcardType) {
213             WildcardType wildcardType = (WildcardType) type;
214             Type[] upperBounds = wildcardType.getUpperBounds();
215             Type[] upperBounds2 = new Type[upperBounds.length];
216             for (int i = 0; i < upperBounds.length; i++) {
217                 upperBounds2[i] = bind(upperBounds[i], bindings);
218             }
219             Type[] lowerBounds = wildcardType.getLowerBounds();
220             Type[] lowerBounds2 = new Type[lowerBounds.length];
221             for (int i = 0; i < lowerBounds.length; i++) {
222                 lowerBounds2[i] = bind(lowerBounds[i], bindings);
223             }
224             return new WildcardTypeImpl(upperBounds2, lowerBounds2);
225         }
226         throw new IllegalArgumentException("Unsupported type: " + type);
227     }
228 
229     /**
230      * Creates an instance of {@link ParameterizedType}
231      *
232      * @param ownerType  an owner type
233      * @param rawType    a type to be parameterized
234      * @param parameters parameter types
235      * @return an instance of {@link ParameterizedType}
236      */
237     public static ParameterizedType parameterizedType(@Nullable Type ownerType, Type rawType, Type[] parameters) {
238         return new ParameterizedTypeImpl(ownerType, rawType, parameters);
239     }
240 
241     /**
242      * Creates an instance of {@link ParameterizedType}
243      *
244      * @see #parameterizedType(Type, Type, Type[])
245      */
246     public static ParameterizedType parameterizedType(Class<?> rawType, Type... parameters) {
247         return new ParameterizedTypeImpl(null, rawType, parameters);
248     }
249 
250     /**
251      * Get all super classes and interface implemented by the given type.
252      */
253     public static Set<Type> getAllSuperTypes(Type original) {
254         Deque<Type> todo = new ArrayDeque<>();
255         todo.add(original);
256         Set<Type> done = new HashSet<>();
257         while (!todo.isEmpty()) {
258             Type type = todo.remove();
259             if (done.add(type)) {
260                 Class<?> cls = getRawType(type);
261                 Function<TypeVariable<?>, Type> bindings;
262                 if (type instanceof ParameterizedType) {
263                     Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
264                     TypeVariable<? extends Class<?>>[] typeVariables = cls.getTypeParameters();
265                     bindings = v -> {
266                         for (int i = 0; i < typeArguments.length; i++) {
267                             Type typeArgument = typeArguments[i];
268                             if (v.equals(typeVariables[i])) {
269                                 return typeArgument;
270                             }
271                         }
272                         return null;
273                     };
274                 } else {
275                     bindings = v -> null;
276                 }
277                 Type[] interfaces = cls.getGenericInterfaces();
278                 for (Type itf : interfaces) {
279                     try {
280                         todo.add(bind(itf, bindings));
281                     } catch (TypeNotBoundException e) {
282                         // ignore
283                     }
284                 }
285                 Type supercls = cls.getGenericSuperclass();
286                 if (supercls != null) {
287                     try {
288                         todo.add(bind(supercls, bindings));
289                     } catch (TypeNotBoundException e) {
290                         // ignore
291                     }
292                 }
293             }
294         }
295         return done;
296     }
297 
298     public static Type simplifyType(Type original) {
299         if (original instanceof Class) {
300             return original;
301         }
302 
303         if (original instanceof GenericArrayType) {
304             Type componentType = ((GenericArrayType) original).getGenericComponentType();
305             Type repackedComponentType = simplifyType(componentType);
306             if (componentType != repackedComponentType) {
307                 return genericArrayType(repackedComponentType);
308             }
309             return original;
310         }
311 
312         if (original instanceof ParameterizedType) {
313             ParameterizedType parameterizedType = (ParameterizedType) original;
314             Type[] typeArguments = parameterizedType.getActualTypeArguments();
315             Type[] repackedTypeArguments = simplifyTypes(typeArguments);
316 
317             if (isAllObjects(repackedTypeArguments)) {
318                 return parameterizedType.getRawType();
319             }
320 
321             if (typeArguments != repackedTypeArguments) {
322                 return parameterizedType(
323                         parameterizedType.getOwnerType(), parameterizedType.getRawType(), repackedTypeArguments);
324             }
325             return original;
326         }
327 
328         if (original instanceof TypeVariable) {
329             throw new IllegalArgumentException("Key should not contain a type variable: " + original);
330         }
331 
332         if (original instanceof WildcardType) {
333             WildcardType wildcardType = (WildcardType) original;
334             Type[] upperBounds = wildcardType.getUpperBounds();
335             if (upperBounds.length == 1) {
336                 Type upperBound = upperBounds[0];
337                 if (upperBound != Object.class) {
338                     return simplifyType(upperBound);
339                 }
340             } else if (upperBounds.length > 1) {
341                 throw new IllegalArgumentException("Multiple upper bounds not supported: " + original);
342             }
343 
344             Type[] lowerBounds = wildcardType.getLowerBounds();
345             if (lowerBounds.length == 1) {
346                 return simplifyType(lowerBounds[0]);
347             } else if (lowerBounds.length > 1) {
348                 throw new IllegalArgumentException("Multiple lower bounds not supported: " + original);
349             }
350             return Object.class;
351         }
352 
353         return original;
354     }
355 
356     private static Type[] simplifyTypes(Type[] original) {
357         int length = original.length;
358         for (int i = 0; i < length; i++) {
359             Type typeArgument = original[i];
360             Type repackTypeArgument = simplifyType(typeArgument);
361             if (repackTypeArgument != typeArgument) {
362                 Type[] repackedTypeArguments = new Type[length];
363                 System.arraycopy(original, 0, repackedTypeArguments, 0, i);
364                 repackedTypeArguments[i++] = repackTypeArgument;
365                 for (; i < length; i++) {
366                     repackedTypeArguments[i] = simplifyType(original[i]);
367                 }
368                 return repackedTypeArguments;
369             }
370         }
371         return original;
372     }
373 
374     private static boolean isAllObjects(Type[] types) {
375         for (Type type : types) {
376             if (type != Object.class) {
377                 return false;
378             }
379         }
380         return true;
381     }
382 
383     /**
384      * Tests whether a {@code from} type is assignable to {@code to} type
385      *
386      * @param to   a 'to' type that should be checked for possible assignment
387      * @param from a 'from' type that should be checked for possible assignment
388      * @return whether an object of type {@code from} is assignable to an object of type {@code to}
389      */
390     public static boolean isAssignable(Type to, Type from) {
391         // shortcut
392         if (to instanceof Class && from instanceof Class) {
393             return ((Class<?>) to).isAssignableFrom((Class<?>) from);
394         }
395         return isAssignable(to, from, false);
396     }
397 
398     private static boolean isAssignable(Type to, Type from, boolean strict) {
399         if (to instanceof WildcardType || from instanceof WildcardType) {
400             Type[] toUppers, toLowers;
401             if (to instanceof WildcardType) {
402                 WildcardType wildcardTo = (WildcardType) to;
403                 toUppers = wildcardTo.getUpperBounds();
404                 toLowers = wildcardTo.getLowerBounds();
405             } else {
406                 toUppers = new Type[] {to};
407                 toLowers = strict ? toUppers : NO_TYPES;
408             }
409 
410             Type[] fromUppers, fromLowers;
411             if (from instanceof WildcardType) {
412                 WildcardType wildcardTo = (WildcardType) to;
413                 fromUppers = wildcardTo.getUpperBounds();
414                 fromLowers = wildcardTo.getLowerBounds();
415             } else {
416                 fromUppers = new Type[] {from};
417                 fromLowers = strict ? fromUppers : NO_TYPES;
418             }
419 
420             for (Type toUpper : toUppers) {
421                 for (Type fromUpper : fromUppers) {
422                     if (!isAssignable(toUpper, fromUpper, false)) {
423                         return false;
424                     }
425                 }
426             }
427             if (toLowers.length == 0) {
428                 return true;
429             }
430             if (fromLowers.length == 0) {
431                 return false;
432             }
433             for (Type toLower : toLowers) {
434                 for (Type fromLower : fromLowers) {
435                     if (!isAssignable(fromLower, toLower, false)) {
436                         return false;
437                     }
438                 }
439             }
440             return true;
441         }
442         if (to instanceof GenericArrayType) {
443             to = getRawType(to);
444         }
445         if (from instanceof GenericArrayType) {
446             from = getRawType(from);
447         }
448         if (!strict && to instanceof Class) {
449             return ((Class<?>) to).isAssignableFrom(getRawType(from));
450         }
451         Class<?> toRawClazz = getRawType(to);
452         Type[] toTypeArguments = getActualTypeArguments(to);
453         return isAssignable(toRawClazz, toTypeArguments, from, strict);
454     }
455 
456     private static boolean isAssignable(Class<?> toRawClazz, Type[] toTypeArguments, Type from, boolean strict) {
457         Class<?> fromRawClazz = getRawType(from);
458         if (strict && !toRawClazz.equals(fromRawClazz)) {
459             return false;
460         }
461         if (!strict && !toRawClazz.isAssignableFrom(fromRawClazz)) {
462             return false;
463         }
464         if (toRawClazz.isArray()) {
465             return true;
466         }
467         Type[] fromTypeArguments = getActualTypeArguments(from);
468         if (toRawClazz == fromRawClazz) {
469             if (toTypeArguments.length > fromTypeArguments.length) {
470                 return false;
471             }
472             for (int i = 0; i < toTypeArguments.length; i++) {
473                 if (!isAssignable(toTypeArguments[i], fromTypeArguments[i], true)) {
474                     return false;
475                 }
476             }
477             return true;
478         }
479         Map<TypeVariable<?>, Type> typeBindings = getTypeBindings(from);
480         for (Type anInterface : fromRawClazz.getGenericInterfaces()) {
481             if (isAssignable(
482                     toRawClazz,
483                     toTypeArguments,
484                     bind(anInterface, key -> typeBindings.getOrDefault(key, wildcardTypeAny())),
485                     false)) {
486                 return true;
487             }
488         }
489         Type superclass = fromRawClazz.getGenericSuperclass();
490         return superclass != null && isAssignable(toRawClazz, toTypeArguments, bind(superclass, typeBindings), false);
491     }
492 
493     public static final class ParameterizedTypeImpl implements ParameterizedType {
494         private final @Nullable Type ownerType;
495         private final Type rawType;
496         private final Type[] actualTypeArguments;
497 
498         ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type[] actualTypeArguments) {
499             this.ownerType = ownerType;
500             this.rawType = rawType;
501             this.actualTypeArguments = actualTypeArguments;
502         }
503 
504         @Override
505         public Type getRawType() {
506             return rawType;
507         }
508 
509         @Override
510         public Type[] getActualTypeArguments() {
511             return actualTypeArguments;
512         }
513 
514         @Override
515         public @Nullable Type getOwnerType() {
516             return ownerType;
517         }
518 
519         @Override
520         public int hashCode() {
521             return Objects.hashCode(ownerType) ^ Arrays.hashCode(actualTypeArguments) ^ rawType.hashCode();
522         }
523 
524         @Override
525         public boolean equals(Object other) {
526             if (!(other instanceof ParameterizedType)) {
527                 return false;
528             }
529             ParameterizedType that = (ParameterizedType) other;
530             return this.getRawType().equals(that.getRawType())
531                     && Objects.equals(this.getOwnerType(), that.getOwnerType())
532                     && Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments());
533         }
534 
535         @Override
536         public String toString() {
537             return rawType.getTypeName()
538                     + Arrays.stream(actualTypeArguments).map(Types::toString).collect(joining(", ", "<", ">"));
539         }
540     }
541 
542     /**
543      * Creates an instance of {@link WildcardType} bound by upper and lower bounds
544      *
545      * @param upperBounds a wildcard upper bound types
546      * @param lowerBounds a wildcard lower bound types
547      * @return an instance of {@link WildcardType}
548      */
549     public static WildcardType wildcardType(Type[] upperBounds, Type[] lowerBounds) {
550         return new WildcardTypeImpl(upperBounds, lowerBounds);
551     }
552 
553     /**
554      * Returns an instance of {@link WildcardType} that matches any type
555      * <p>
556      * E.g. {@code <?>}
557      *
558      * @see #wildcardType(Type[], Type[])
559      */
560     public static WildcardType wildcardTypeAny() {
561         return WILDCARD_TYPE_ANY;
562     }
563 
564     /**
565      * Creates an instance of {@link WildcardType} bound by a single upper bound
566      * <p>
567      * E.g. {@code <? extends UpperBound>}
568      *
569      * @param upperBound a wildcard upper bound type
570      * @return an instance of {@link WildcardType}
571      * @see #wildcardType(Type[], Type[])
572      */
573     public static WildcardType wildcardTypeExtends(Type upperBound) {
574         return new WildcardTypeImpl(new Type[] {upperBound}, NO_TYPES);
575     }
576 
577     /**
578      * Creates an instance of {@link WildcardType} bound by a single lower bound
579      * <p>
580      * E.g. {@code <? super LowerBound>}
581      *
582      * @param lowerBound a wildcard lower bound type
583      * @return an instance of {@link WildcardType}
584      * @see #wildcardType(Type[], Type[])
585      */
586     public static WildcardType wildcardTypeSuper(Type lowerBound) {
587         return new WildcardTypeImpl(NO_TYPES, new Type[] {lowerBound});
588     }
589 
590     public static class WildcardTypeImpl implements WildcardType {
591         private final Type[] upperBounds;
592         private final Type[] lowerBounds;
593 
594         WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
595             this.upperBounds = upperBounds;
596             this.lowerBounds = lowerBounds;
597         }
598 
599         @Override
600         public Type[] getUpperBounds() {
601             return upperBounds;
602         }
603 
604         @Override
605         public Type[] getLowerBounds() {
606             return lowerBounds;
607         }
608 
609         @Override
610         public int hashCode() {
611             return Arrays.hashCode(upperBounds) ^ Arrays.hashCode(lowerBounds);
612         }
613 
614         @Override
615         public boolean equals(Object other) {
616             if (!(other instanceof WildcardType)) {
617                 return false;
618             }
619             WildcardType that = (WildcardType) other;
620             return Arrays.equals(this.getUpperBounds(), that.getUpperBounds())
621                     && Arrays.equals(this.getLowerBounds(), that.getLowerBounds());
622         }
623 
624         @Override
625         public String toString() {
626             return "?"
627                     + (upperBounds.length == 0
628                             ? ""
629                             : " extends "
630                                     + Arrays.stream(upperBounds)
631                                             .map(Types::toString)
632                                             .collect(joining(" & ")))
633                     + (lowerBounds.length == 0
634                             ? ""
635                             : " super "
636                                     + Arrays.stream(lowerBounds)
637                                             .map(Types::toString)
638                                             .collect(joining(" & ")));
639         }
640     }
641 
642     /**
643      * Creates an instance of {@link GenericArrayType} with a given component type
644      * <p>
645      * Same as {@code T[]}
646      *
647      * @param componentType a component type of generic array
648      * @return an instance of {@link GenericArrayType}
649      * @see #wildcardType(Type[], Type[])
650      */
651     public static GenericArrayType genericArrayType(Type componentType) {
652         return new GenericArrayTypeImpl(componentType);
653     }
654 
655     public static final class GenericArrayTypeImpl implements GenericArrayType {
656         private final Type componentType;
657 
658         GenericArrayTypeImpl(Type componentType) {
659             this.componentType = componentType;
660         }
661 
662         @Override
663         public Type getGenericComponentType() {
664             return componentType;
665         }
666 
667         @Override
668         public int hashCode() {
669             return componentType.hashCode();
670         }
671 
672         @Override
673         public boolean equals(Object other) {
674             if (!(other instanceof GenericArrayType)) {
675                 return false;
676             }
677             GenericArrayType that = (GenericArrayType) other;
678             return this.getGenericComponentType().equals(that.getGenericComponentType());
679         }
680 
681         @Override
682         public String toString() {
683             return Types.toString(componentType) + "[]";
684         }
685     }
686 
687     private static String toString(Type type) {
688         return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
689     }
690 
691     /**
692      * Returns a simple name for a given {@link Type}
693      *
694      * @see Class#getSimpleName()
695      */
696     public static String getSimpleName(Type type) {
697         if (type instanceof Class) {
698             return ((Class<?>) type).getSimpleName();
699         } else if (type instanceof ParameterizedType) {
700             return Arrays.stream(((ParameterizedType) type).getActualTypeArguments())
701                     .map(Types::getSimpleName)
702                     .collect(joining(",", "<", ">"));
703         } else if (type instanceof WildcardType) {
704             WildcardType wildcardType = (WildcardType) type;
705             Type[] upperBounds = wildcardType.getUpperBounds();
706             Type[] lowerBounds = wildcardType.getLowerBounds();
707             return "?"
708                     + (upperBounds.length == 0
709                             ? ""
710                             : " extends "
711                                     + Arrays.stream(upperBounds)
712                                             .map(Types::getSimpleName)
713                                             .collect(joining(" & ")))
714                     + (lowerBounds.length == 0
715                             ? ""
716                             : " super "
717                                     + Arrays.stream(lowerBounds)
718                                             .map(Types::getSimpleName)
719                                             .collect(joining(" & ")));
720         } else if (type instanceof GenericArrayType) {
721             return Types.getSimpleName(((GenericArrayType) type).getGenericComponentType()) + "[]";
722         }
723 
724         return type.getTypeName();
725     }
726 
727     public static class TypeNotBoundException extends IllegalArgumentException {
728         public TypeNotBoundException(String s) {
729             super(s);
730         }
731     }
732 }