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