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