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<?> clazz) {
58              return clazz;
59          } else if (type instanceof ParameterizedType parameterizedType) {
60              return (Class<?>) parameterizedType.getRawType();
61          } else if (type instanceof WildcardType wildcardType) {
62              Type[] upperBounds = wildcardType.getUpperBounds();
63              return getRawType(getUppermostType(upperBounds));
64          } else if (type instanceof GenericArrayType genericArrayType) {
65              Class<?> rawComponentType = getRawType(genericArrayType.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<?> typeVariable) {
72              return getRawType(getUppermostType(typeVariable.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<?> clazz) {
104             return clazz.isArray() ? new Type[] {clazz.getComponentType()} : NO_TYPES;
105         } else if (type instanceof ParameterizedType parameterizedType) {
106             return parameterizedType.getActualTypeArguments();
107         } else if (type instanceof GenericArrayType genericArrayType) {
108             return new Type[] {genericArrayType.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 parameterizedType) {
145             Type[] typeArguments = parameterizedType.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<?> typeVariable) {
190             Type actualType = bindings.apply(typeVariable);
191             if (actualType == null) {
192                 throw new TypeNotBoundException("Type variable not found: " + typeVariable + " ( "
193                         + typeVariable.getGenericDeclaration() + " ) ");
194             }
195             return actualType;
196         }
197         if (type instanceof ParameterizedType parameterizedType) {
198             Type[] typeArguments = parameterizedType.getActualTypeArguments();
199             Type[] typeArguments2 = new Type[typeArguments.length];
200             for (int i = 0; i < typeArguments.length; i++) {
201                 typeArguments2[i] = bind(typeArguments[i], bindings);
202             }
203             return new ParameterizedTypeImpl(
204                     parameterizedType.getOwnerType(), parameterizedType.getRawType(), typeArguments2);
205         }
206         if (type instanceof GenericArrayType genericArrayType) {
207             Type componentType = genericArrayType.getGenericComponentType();
208             return new GenericArrayTypeImpl(bind(componentType, bindings));
209         }
210         if (type instanceof WildcardType wildcardType) {
211             Type[] upperBounds = wildcardType.getUpperBounds();
212             Type[] upperBounds2 = new Type[upperBounds.length];
213             for (int i = 0; i < upperBounds.length; i++) {
214                 upperBounds2[i] = bind(upperBounds[i], bindings);
215             }
216             Type[] lowerBounds = wildcardType.getLowerBounds();
217             Type[] lowerBounds2 = new Type[lowerBounds.length];
218             for (int i = 0; i < lowerBounds.length; i++) {
219                 lowerBounds2[i] = bind(lowerBounds[i], bindings);
220             }
221             return new WildcardTypeImpl(upperBounds2, lowerBounds2);
222         }
223         throw new IllegalArgumentException("Unsupported type: " + type);
224     }
225 
226     /**
227      * Creates an instance of {@link ParameterizedType}
228      *
229      * @param ownerType  an owner type
230      * @param rawType    a type to be parameterized
231      * @param parameters parameter types
232      * @return an instance of {@link ParameterizedType}
233      */
234     public static ParameterizedType parameterizedType(@Nullable Type ownerType, Type rawType, Type[] parameters) {
235         return new ParameterizedTypeImpl(ownerType, rawType, parameters);
236     }
237 
238     /**
239      * Creates an instance of {@link ParameterizedType}
240      *
241      * @see #parameterizedType(Type, Type, Type[])
242      */
243     public static ParameterizedType parameterizedType(Class<?> rawType, Type... parameters) {
244         return new ParameterizedTypeImpl(null, rawType, parameters);
245     }
246 
247     /**
248      * Get all super classes and interface implemented by the given type.
249      */
250     public static Set<Type> getAllSuperTypes(Type original) {
251         Deque<Type> todo = new ArrayDeque<>();
252         todo.add(original);
253         Set<Type> done = new HashSet<>();
254         while (!todo.isEmpty()) {
255             Type type = todo.remove();
256             if (done.add(type)) {
257                 Class<?> cls = getRawType(type);
258                 Function<TypeVariable<?>, Type> bindings;
259                 if (type instanceof ParameterizedType parameterizedType) {
260                     Type[] typeArguments = parameterizedType.getActualTypeArguments();
261                     TypeVariable<? extends Class<?>>[] typeVariables = cls.getTypeParameters();
262                     bindings = v -> {
263                         for (int i = 0; i < typeArguments.length; i++) {
264                             Type typeArgument = typeArguments[i];
265                             if (v.equals(typeVariables[i])) {
266                                 return typeArgument;
267                             }
268                         }
269                         return null;
270                     };
271                 } else {
272                     bindings = v -> null;
273                 }
274                 Type[] interfaces = cls.getGenericInterfaces();
275                 for (Type itf : interfaces) {
276                     try {
277                         todo.add(bind(itf, bindings));
278                     } catch (TypeNotBoundException e) {
279                         // ignore
280                     }
281                 }
282                 Type supercls = cls.getGenericSuperclass();
283                 if (supercls != null) {
284                     try {
285                         todo.add(bind(supercls, bindings));
286                     } catch (TypeNotBoundException e) {
287                         // ignore
288                     }
289                 }
290             }
291         }
292         return done;
293     }
294 
295     public static Type simplifyType(Type original) {
296         if (original instanceof Class) {
297             return original;
298         }
299 
300         if (original instanceof GenericArrayType genericArrayType) {
301             Type componentType = genericArrayType.getGenericComponentType();
302             Type repackedComponentType = simplifyType(componentType);
303             if (componentType != repackedComponentType) {
304                 return genericArrayType(repackedComponentType);
305             }
306             return original;
307         }
308 
309         if (original instanceof ParameterizedType parameterizedType) {
310             Type[] typeArguments = parameterizedType.getActualTypeArguments();
311             Type[] repackedTypeArguments = simplifyTypes(typeArguments);
312 
313             if (isAllObjects(repackedTypeArguments)) {
314                 return parameterizedType.getRawType();
315             }
316 
317             if (typeArguments != repackedTypeArguments) {
318                 return parameterizedType(
319                         parameterizedType.getOwnerType(), parameterizedType.getRawType(), repackedTypeArguments);
320             }
321             return original;
322         }
323 
324         if (original instanceof TypeVariable) {
325             throw new IllegalArgumentException("Key should not contain a type variable: " + original);
326         }
327 
328         if (original instanceof WildcardType wildcardType) {
329             Type[] upperBounds = wildcardType.getUpperBounds();
330             if (upperBounds.length == 1) {
331                 Type upperBound = upperBounds[0];
332                 if (upperBound != Object.class) {
333                     return simplifyType(upperBound);
334                 }
335             } else if (upperBounds.length > 1) {
336                 throw new IllegalArgumentException("Multiple upper bounds not supported: " + original);
337             }
338 
339             Type[] lowerBounds = wildcardType.getLowerBounds();
340             if (lowerBounds.length == 1) {
341                 return simplifyType(lowerBounds[0]);
342             } else if (lowerBounds.length > 1) {
343                 throw new IllegalArgumentException("Multiple lower bounds not supported: " + original);
344             }
345             return Object.class;
346         }
347 
348         return original;
349     }
350 
351     private static Type[] simplifyTypes(Type[] original) {
352         int length = original.length;
353         for (int i = 0; i < length; i++) {
354             Type typeArgument = original[i];
355             Type repackTypeArgument = simplifyType(typeArgument);
356             if (repackTypeArgument != typeArgument) {
357                 Type[] repackedTypeArguments = new Type[length];
358                 System.arraycopy(original, 0, repackedTypeArguments, 0, i);
359                 repackedTypeArguments[i++] = repackTypeArgument;
360                 for (; i < length; i++) {
361                     repackedTypeArguments[i] = simplifyType(original[i]);
362                 }
363                 return repackedTypeArguments;
364             }
365         }
366         return original;
367     }
368 
369     private static boolean isAllObjects(Type[] types) {
370         for (Type type : types) {
371             if (type != Object.class) {
372                 return false;
373             }
374         }
375         return true;
376     }
377 
378     /**
379      * Tests whether a {@code from} type is assignable to {@code to} type
380      *
381      * @param to   a 'to' type that should be checked for possible assignment
382      * @param from a 'from' type that should be checked for possible assignment
383      * @return whether an object of type {@code from} is assignable to an object of type {@code to}
384      */
385     public static boolean isAssignable(Type to, Type from) {
386         // shortcut
387         if (to instanceof Class<?> toClazz && from instanceof Class<?> fromClazz) {
388             return toClazz.isAssignableFrom(fromClazz);
389         }
390         return isAssignable(to, from, false);
391     }
392 
393     private static boolean isAssignable(Type to, Type from, boolean strict) {
394         if (to instanceof WildcardType || from instanceof WildcardType) {
395             Type[] toUppers, toLowers;
396             if (to instanceof WildcardType wildcardTo) {
397                 toUppers = wildcardTo.getUpperBounds();
398                 toLowers = wildcardTo.getLowerBounds();
399             } else {
400                 toUppers = new Type[] {to};
401                 toLowers = strict ? toUppers : NO_TYPES;
402             }
403 
404             Type[] fromUppers, fromLowers;
405             if (from instanceof WildcardType wildcardFrom) {
406                 fromUppers = wildcardFrom.getUpperBounds();
407                 fromLowers = wildcardFrom.getLowerBounds();
408             } else {
409                 fromUppers = new Type[] {from};
410                 fromLowers = strict ? fromUppers : NO_TYPES;
411             }
412 
413             for (Type toUpper : toUppers) {
414                 for (Type fromUpper : fromUppers) {
415                     if (!isAssignable(toUpper, fromUpper, false)) {
416                         return false;
417                     }
418                 }
419             }
420             if (toLowers.length == 0) {
421                 return true;
422             }
423             if (fromLowers.length == 0) {
424                 return false;
425             }
426             for (Type toLower : toLowers) {
427                 for (Type fromLower : fromLowers) {
428                     if (!isAssignable(fromLower, toLower, false)) {
429                         return false;
430                     }
431                 }
432             }
433             return true;
434         }
435         if (to instanceof GenericArrayType) {
436             to = getRawType(to);
437         }
438         if (from instanceof GenericArrayType) {
439             from = getRawType(from);
440         }
441         if (!strict && to instanceof Class<?> toClazz) {
442             return toClazz.isAssignableFrom(getRawType(from));
443         }
444         Class<?> toRawClazz = getRawType(to);
445         Type[] toTypeArguments = getActualTypeArguments(to);
446         return isAssignable(toRawClazz, toTypeArguments, from, strict);
447     }
448 
449     private static boolean isAssignable(Class<?> toRawClazz, Type[] toTypeArguments, Type from, boolean strict) {
450         Class<?> fromRawClazz = getRawType(from);
451         if (strict && !toRawClazz.equals(fromRawClazz)) {
452             return false;
453         }
454         if (!strict && !toRawClazz.isAssignableFrom(fromRawClazz)) {
455             return false;
456         }
457         if (toRawClazz.isArray()) {
458             return true;
459         }
460         Type[] fromTypeArguments = getActualTypeArguments(from);
461         if (toRawClazz == fromRawClazz) {
462             if (toTypeArguments.length > fromTypeArguments.length) {
463                 return false;
464             }
465             for (int i = 0; i < toTypeArguments.length; i++) {
466                 if (!isAssignable(toTypeArguments[i], fromTypeArguments[i], true)) {
467                     return false;
468                 }
469             }
470             return true;
471         }
472         Map<TypeVariable<?>, Type> typeBindings = getTypeBindings(from);
473         for (Type anInterface : fromRawClazz.getGenericInterfaces()) {
474             if (isAssignable(
475                     toRawClazz,
476                     toTypeArguments,
477                     bind(anInterface, key -> typeBindings.getOrDefault(key, wildcardTypeAny())),
478                     false)) {
479                 return true;
480             }
481         }
482         Type superclass = fromRawClazz.getGenericSuperclass();
483         return superclass != null && isAssignable(toRawClazz, toTypeArguments, bind(superclass, typeBindings), false);
484     }
485 
486     public static final class ParameterizedTypeImpl implements ParameterizedType {
487         private final @Nullable Type ownerType;
488         private final Type rawType;
489         private final Type[] actualTypeArguments;
490 
491         ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type[] actualTypeArguments) {
492             this.ownerType = ownerType;
493             this.rawType = rawType;
494             this.actualTypeArguments = actualTypeArguments;
495         }
496 
497         @Override
498         public Type getRawType() {
499             return rawType;
500         }
501 
502         @Override
503         public Type[] getActualTypeArguments() {
504             return actualTypeArguments;
505         }
506 
507         @Override
508         public @Nullable Type getOwnerType() {
509             return ownerType;
510         }
511 
512         @Override
513         public int hashCode() {
514             return Objects.hashCode(ownerType) ^ Arrays.hashCode(actualTypeArguments) ^ rawType.hashCode();
515         }
516 
517         @Override
518         public boolean equals(Object other) {
519             if (!(other instanceof ParameterizedType that)) {
520                 return false;
521             }
522             return this.getRawType().equals(that.getRawType())
523                     && Objects.equals(this.getOwnerType(), that.getOwnerType())
524                     && Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments());
525         }
526 
527         @Override
528         public String toString() {
529             return rawType.getTypeName()
530                     + Arrays.stream(actualTypeArguments).map(Types::toString).collect(joining(", ", "<", ">"));
531         }
532     }
533 
534     /**
535      * Creates an instance of {@link WildcardType} bound by upper and lower bounds
536      *
537      * @param upperBounds a wildcard upper bound types
538      * @param lowerBounds a wildcard lower bound types
539      * @return an instance of {@link WildcardType}
540      */
541     public static WildcardType wildcardType(Type[] upperBounds, Type[] lowerBounds) {
542         return new WildcardTypeImpl(upperBounds, lowerBounds);
543     }
544 
545     /**
546      * Returns an instance of {@link WildcardType} that matches any type
547      * <p>
548      * E.g. {@code <?>}
549      *
550      * @see #wildcardType(Type[], Type[])
551      */
552     public static WildcardType wildcardTypeAny() {
553         return WILDCARD_TYPE_ANY;
554     }
555 
556     /**
557      * Creates an instance of {@link WildcardType} bound by a single upper bound
558      * <p>
559      * E.g. {@code <? extends UpperBound>}
560      *
561      * @param upperBound a wildcard upper bound type
562      * @return an instance of {@link WildcardType}
563      * @see #wildcardType(Type[], Type[])
564      */
565     public static WildcardType wildcardTypeExtends(Type upperBound) {
566         return new WildcardTypeImpl(new Type[] {upperBound}, NO_TYPES);
567     }
568 
569     /**
570      * Creates an instance of {@link WildcardType} bound by a single lower bound
571      * <p>
572      * E.g. {@code <? super LowerBound>}
573      *
574      * @param lowerBound a wildcard lower bound type
575      * @return an instance of {@link WildcardType}
576      * @see #wildcardType(Type[], Type[])
577      */
578     public static WildcardType wildcardTypeSuper(Type lowerBound) {
579         return new WildcardTypeImpl(NO_TYPES, new Type[] {lowerBound});
580     }
581 
582     public static class WildcardTypeImpl implements WildcardType {
583         private final Type[] upperBounds;
584         private final Type[] lowerBounds;
585 
586         WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
587             this.upperBounds = upperBounds;
588             this.lowerBounds = lowerBounds;
589         }
590 
591         @Override
592         public Type[] getUpperBounds() {
593             return upperBounds;
594         }
595 
596         @Override
597         public Type[] getLowerBounds() {
598             return lowerBounds;
599         }
600 
601         @Override
602         public int hashCode() {
603             return Arrays.hashCode(upperBounds) ^ Arrays.hashCode(lowerBounds);
604         }
605 
606         @Override
607         public boolean equals(Object other) {
608             if (!(other instanceof WildcardType that)) {
609                 return false;
610             }
611             return Arrays.equals(this.getUpperBounds(), that.getUpperBounds())
612                     && Arrays.equals(this.getLowerBounds(), that.getLowerBounds());
613         }
614 
615         @Override
616         public String toString() {
617             return "?"
618                     + (upperBounds.length == 0
619                             ? ""
620                             : " extends "
621                                     + Arrays.stream(upperBounds)
622                                             .map(Types::toString)
623                                             .collect(joining(" & ")))
624                     + (lowerBounds.length == 0
625                             ? ""
626                             : " super "
627                                     + Arrays.stream(lowerBounds)
628                                             .map(Types::toString)
629                                             .collect(joining(" & ")));
630         }
631     }
632 
633     /**
634      * Creates an instance of {@link GenericArrayType} with a given component type
635      * <p>
636      * Same as {@code T[]}
637      *
638      * @param componentType a component type of generic array
639      * @return an instance of {@link GenericArrayType}
640      * @see #wildcardType(Type[], Type[])
641      */
642     public static GenericArrayType genericArrayType(Type componentType) {
643         return new GenericArrayTypeImpl(componentType);
644     }
645 
646     public static final class GenericArrayTypeImpl implements GenericArrayType {
647         private final Type componentType;
648 
649         GenericArrayTypeImpl(Type componentType) {
650             this.componentType = componentType;
651         }
652 
653         @Override
654         public Type getGenericComponentType() {
655             return componentType;
656         }
657 
658         @Override
659         public int hashCode() {
660             return componentType.hashCode();
661         }
662 
663         @Override
664         public boolean equals(Object other) {
665             if (!(other instanceof GenericArrayType that)) {
666                 return false;
667             }
668             return this.getGenericComponentType().equals(that.getGenericComponentType());
669         }
670 
671         @Override
672         public String toString() {
673             return Types.toString(componentType) + "[]";
674         }
675     }
676 
677     private static String toString(Type type) {
678         return type instanceof Class<?> clazz ? clazz.getName() : type.toString();
679     }
680 
681     /**
682      * Returns a simple name for a given {@link Type}
683      *
684      * @see Class#getSimpleName()
685      */
686     public static String getSimpleName(Type type) {
687         if (type instanceof Class<?> clazz) {
688             return clazz.getSimpleName();
689         } else if (type instanceof ParameterizedType parameterizedType) {
690             return Arrays.stream(parameterizedType.getActualTypeArguments())
691                     .map(Types::getSimpleName)
692                     .collect(joining(",", "<", ">"));
693         } else if (type instanceof WildcardType wildcardType) {
694             Type[] upperBounds = wildcardType.getUpperBounds();
695             Type[] lowerBounds = wildcardType.getLowerBounds();
696             return "?"
697                     + (upperBounds.length == 0
698                             ? ""
699                             : " extends "
700                                     + Arrays.stream(upperBounds)
701                                             .map(Types::getSimpleName)
702                                             .collect(joining(" & ")))
703                     + (lowerBounds.length == 0
704                             ? ""
705                             : " super "
706                                     + Arrays.stream(lowerBounds)
707                                             .map(Types::getSimpleName)
708                                             .collect(joining(" & ")));
709         } else if (type instanceof GenericArrayType genericArrayType) {
710             return Types.getSimpleName(genericArrayType.getGenericComponentType()) + "[]";
711         }
712 
713         return type.getTypeName();
714     }
715 
716     public static class TypeNotBoundException extends IllegalArgumentException {
717         public TypeNotBoundException(String s) {
718             super(s);
719         }
720     }
721 }