001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.spongepowered.configurate.util;
018
019import static io.leangen.geantyref.GenericTypeReflector.erase;
020import static io.leangen.geantyref.GenericTypeReflector.resolveType;
021import static java.util.Objects.requireNonNull;
022
023import io.leangen.geantyref.GenericTypeReflector;
024import io.leangen.geantyref.TypeFactory;
025import io.leangen.geantyref.TypeToken;
026import org.checkerframework.checker.nullness.qual.Nullable;
027
028import java.lang.annotation.Annotation;
029import java.lang.reflect.AnnotatedElement;
030import java.lang.reflect.Array;
031import java.lang.reflect.GenericArrayType;
032import java.lang.reflect.Method;
033import java.lang.reflect.ParameterizedType;
034import java.lang.reflect.Type;
035import java.lang.reflect.TypeVariable;
036import java.lang.reflect.WildcardType;
037import java.util.ArrayDeque;
038import java.util.ArrayList;
039import java.util.Arrays;
040import java.util.Collections;
041import java.util.Deque;
042import java.util.HashSet;
043import java.util.Iterator;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047import java.util.Spliterator;
048import java.util.Spliterators;
049import java.util.function.UnaryOperator;
050import java.util.stream.Stream;
051import java.util.stream.StreamSupport;
052
053/**
054 * Utility methods for working with generic types.
055 *
056 * <p>Most of these utilities are designed to go along with
057 * <a href="https://github.com/leangen/geantyref">GeAnTyRef</a>.</p>
058 *
059 * @see GenericTypeReflector for other tools to work with types
060 * @see TypeFactory for methods to construct types
061 * @since 4.0.0
062 */
063public final class Types {
064
065    private static final Map<Type, Type> BOXED_TO_PRIMITIVE = UnmodifiableCollections.buildMap(m -> {
066        m.put(Boolean.class, boolean.class);
067        m.put(Character.class, char.class);
068        m.put(Byte.class, byte.class);
069        m.put(Short.class, short.class);
070        m.put(Integer.class, int.class);
071        m.put(Long.class, long.class);
072        m.put(Float.class, float.class);
073        m.put(Double.class, double.class);
074        m.put(Void.class, void.class);
075    });
076
077    private static final Map<Type, Type> PRIMITIVE_TO_BOXED = UnmodifiableCollections.buildMap(m -> {
078        m.put(boolean.class, Boolean.class);
079        m.put(char.class, Character.class);
080        m.put(byte.class, Byte.class);
081        m.put(short.class, Short.class);
082        m.put(int.class, Integer.class);
083        m.put(long.class, Long.class);
084        m.put(float.class, Float.class);
085        m.put(double.class, Double.class);
086        m.put(void.class, Void.class);
087    });
088
089    private Types() {
090    }
091
092    /**
093     * Get if the provided type is an array type.
094     *
095     * <p>Being an array type means that the provided
096     * type has a component type.</p>
097     *
098     * @param input input type
099     * @return whether the type is an array
100     * @since 4.0.0
101     */
102    public static boolean isArray(final Type input) {
103        if (input instanceof Class<?>) {
104            return ((Class<?>) input).isArray();
105        } else if (input instanceof ParameterizedType) {
106            return isArray(((ParameterizedType) input).getRawType());
107        } else {
108            return input instanceof GenericArrayType;
109        }
110    }
111
112    /**
113     * Get whether or not the provided input type is a boxed primitive type.
114     *
115     * <p>This check will <em>not</em> match unboxed primitives.</p>
116     *
117     * @param input type to check
118     * @return if type is a boxed primitive
119     * @since 4.0.0
120     */
121    public static boolean isBoxedPrimitive(final Type input) {
122        return BOXED_TO_PRIMITIVE.containsKey(input);
123    }
124
125    /**
126     * Unbox the input type if it is a boxed primitive.
127     *
128     * @param input input type
129     * @return the unboxed version of the input type,
130     *          or the input type if it was already non-primitive
131     * @since 4.0.0
132     */
133    public static Type unbox(final Type input) {
134        final Type ret = BOXED_TO_PRIMITIVE.get(input);
135        return ret == null ? input : ret;
136    }
137
138    /**
139     * Get the default value for a type.
140     *
141     * <p>For all reference types, this is {@code null}. For all primitive
142     * types, this is equivalent to their defined {@code default} value.</p>
143     *
144     * @param type the type to get a default value for
145     * @return the default value, or {@code null} for reference types
146     * @since 4.1.0
147     */
148    public static @Nullable Object defaultValue(final Class<?> type) {
149        requireNonNull(type, "type");
150
151        if (!type.isPrimitive() || type == void.class) {
152            return null;
153        } else if (type == boolean.class) {
154            return Boolean.FALSE;
155        } else if (type == char.class) {
156            return (char) 0;
157        } else if (type == byte.class) {
158            return (byte) 0;
159        } else if (type == short.class) {
160            return (short) 0;
161        } else if (type == int.class) {
162            return 0;
163        } else if (type == long.class) {
164            return 0L;
165        } else if (type == float.class) {
166            return 0F;
167        } else if (type == double.class) {
168            return 0D;
169        } else {
170            // TODO: Verify that this works with Valhalla primitive types
171            return Array.get(Array.newInstance(type, 1), 0);
172        }
173    }
174
175    /**
176     * Box the input type if it is an unboxed primitive {@link Class}.
177     *
178     * @param input input type
179     * @return the unboxed version of the input type, or the input type if
180     *          it was already a primitive, or had no primitive equivalent
181     * @since 4.0.0
182     */
183    public static Type box(final Type input) {
184        final Type ret = PRIMITIVE_TO_BOXED.get(input);
185        return ret == null ? input : ret;
186    }
187
188    /**
189     * Given an element type, create a new list type.
190     *
191     * <p>The provided element type must not be a <em>raw type</em></p>
192     *
193     * <p>This has an outcome similar to constructing a {@link TypeToken}
194     * directly, but avoids generating an extra anonymous class.</p>
195     *
196     * @param elementType class representing the element type
197     * @param <T> type of element
198     * @return new list type token
199     * @since 4.2.0
200     */
201    @SuppressWarnings("unchecked")
202    public static <T> TypeToken<List<T>> makeList(final Class<T> elementType) {
203        return (TypeToken<List<T>>) TypeToken.get(TypeFactory.parameterizedClass(List.class, elementType));
204    }
205
206    /**
207     * Given an element type, create a new list type.
208     *
209     * <p>This has an outcome similar to constructing a {@link TypeToken}
210     * directly, but avoids generating an extra anonymous class.</p>
211     *
212     * @param elementType type token representing the element type
213     * @param <T> type of element
214     * @return new list type token
215     * @since 4.0.0
216     */
217    @SuppressWarnings("unchecked")
218    public static <T> TypeToken<List<T>> makeListType(final TypeToken<T> elementType) {
219        return (TypeToken<List<T>>) TypeToken.get(TypeFactory.parameterizedClass(List.class, elementType.getType()));
220    }
221
222    /**
223     * Given an element type, create a new {@link Map} type.
224     *
225     * <p>The provided key and value types must not be a <em>raw type</em></p>
226     *
227     * <p>This has an outcome similar to constructing a {@link TypeToken}
228     * directly, but avoids generating an extra anonymous class.</p>
229     *
230     * @param key type of the map's key
231     * @param value type of the map's value
232     * @param <K> type of key
233     * @param <V> type of value
234     * @return new {@link Map} type token
235     * @since 4.2.0
236     */
237    @SuppressWarnings("unchecked")
238    public static <K, V> TypeToken<Map<K, V>> makeMap(final Class<K> key, final Class<V> value) {
239        return (TypeToken<Map<K, V>>) TypeToken.get(TypeFactory.parameterizedClass(Map.class, key, value));
240    }
241
242    /**
243     * Given an element type, create a new {@link Map} type.
244     *
245     * <p>This has an outcome similar to constructing a {@link TypeToken}
246     * directly, but avoids generating an extra anonymous class.</p>
247     *
248     * @param key type of the map's key
249     * @param value type of the map's value
250     * @param <K> type of key
251     * @param <V> type of value
252     * @return new {@link Map} type token
253     * @since 4.2.0
254     */
255    @SuppressWarnings("unchecked")
256    public static <K, V> TypeToken<Map<K, V>> makeMap(final TypeToken<K> key, final TypeToken<V> value) {
257        return (TypeToken<Map<K, V>>) TypeToken.get(TypeFactory.parameterizedClass(Map.class, key.getType(), value.getType()));
258    }
259
260    /**
261     * Given an element type, create a new {@link Map} type.
262     *
263     * <p>This has an outcome similar to constructing a {@link TypeToken}
264     * directly, but avoids generating an extra anonymous class.</p>
265     *
266     * @param key type of the map's key
267     * @param value type of the map's value
268     * @param <K> type of key
269     * @param <V> type of value
270     * @return new {@link Map} type token
271     * @since 4.2.0
272     */
273    @SuppressWarnings("unchecked")
274    public static <K, V> TypeToken<Map<K, V>> makeMap(final Class<K> key, final TypeToken<V> value) {
275        return (TypeToken<Map<K, V>>) TypeToken.get(TypeFactory.parameterizedClass(Map.class, key, value.getType()));
276    }
277
278    /**
279     * Given an element type, create a new {@link Map} type.
280     *
281     * <p>This has an outcome similar to constructing a {@link TypeToken}
282     * directly, but avoids generating an extra anonymous class.</p>
283     *
284     * @param key type of the map's key
285     * @param value type of the map's value
286     * @param <K> type of key
287     * @param <V> type of value
288     * @return new {@link Map} type token
289     * @since 4.2.0
290     */
291    @SuppressWarnings("unchecked")
292    public static <K, V> TypeToken<Map<K, V>> makeMap(final TypeToken<K> key, final Class<V> value) {
293        return (TypeToken<Map<K, V>>) TypeToken.get(TypeFactory.parameterizedClass(Map.class, key.getType(), value));
294    }
295
296    /**
297     * Get an element containing the annotations of all the provided elements.
298     *
299     * <p>If multiple elements have the same annotation, only the first one
300     * with an applicable type is returned.</p>
301     *
302     * @param elements elements to combine
303     * @return new union element
304     * @since 4.0.0
305     */
306    public static AnnotatedElement combinedAnnotations(final AnnotatedElement... elements) {
307        return new CombinedAnnotations(Arrays.copyOf(elements, elements.length));
308    }
309
310    /**
311     * Throw an exception if the passed type is raw (missing parameters)..
312     *
313     * @param input input type
314     * @return type, passed through
315     * @since 4.0.0
316     */
317    public static Type requireCompleteParameters(final Type input) {
318        if (GenericTypeReflector.isMissingTypeParameters(input)) {
319            throw new IllegalArgumentException("Provided type " + input + " is a raw type, which is not accepted.");
320        }
321        return input;
322    }
323
324    /**
325     * Get all supertypes of this object with type parameters.
326     *
327     * <p>The iteration order is undefined. The returned stream will include the
328     * base type plus superclasses, but not superinterfaces.</p>
329     *
330     * @param type base type
331     * @return stream of supertypes
332     * @since 4.0.0
333     */
334    public static Stream<Type> allSuperTypes(final Type type) {
335        return calculateSuperTypes(type, false);
336    }
337
338    /**
339     * Get all supertypes and interfaces of the provided type.
340     *
341     * <p>The iteration order is undefined. The returned stream will include the
342     * base type plus superclasses and superinterfaces.</p>
343     *
344     * @param type base type
345     * @return stream of supertypes
346     * @since 4.0.0
347     */
348    public static Stream<Type> allSuperTypesAndInterfaces(final Type type) {
349        return calculateSuperTypes(type, true);
350    }
351
352    private static Stream<Type> calculateSuperTypes(final Type type, final boolean includeInterfaces) {
353        requireNonNull(type, "type");
354        return StreamSupport.stream(Spliterators.spliterator(new SuperTypesIterator(type, includeInterfaces), Long.MAX_VALUE,
355                                                             Spliterator.NONNULL | Spliterator.IMMUTABLE), false);
356    }
357
358    /**
359     * Recursively iterate through supertypes.
360     */
361    static class SuperTypesIterator implements Iterator<Type> {
362        private final boolean includeInterfaces;
363        private final Deque<Type> types = new ArrayDeque<>();
364        private final Set<Type> seen = new HashSet<>();
365
366        SuperTypesIterator(final Type base, final boolean includeInterfaces) {
367            this.types.add(base);
368            this.includeInterfaces = includeInterfaces;
369        }
370
371        @Override
372        public boolean hasNext() {
373            return !this.types.isEmpty();
374        }
375
376        @Override
377        @SuppressWarnings("checkstyle:UnnecessaryParentheses")
378        public Type next() {
379            // Get current type, throws the correct exception if empty
380            final Type head = this.types.removeLast();
381
382            // Calculate the next step depending on the type of Type seen
383            // Arrays, covariant based on component type
384            if ((head instanceof Class<?> && ((Class<?>) head).isArray()) || head instanceof GenericArrayType) {
385                // find a super component-type
386                final Type componentType;
387                if (head instanceof Class<?>) {
388                    componentType = ((Class<?>) head).getComponentType();
389                } else {
390                    componentType = ((GenericArrayType) head).getGenericComponentType();
391                }
392
393                addSuperClassAndInterface(componentType, erase(componentType), TypeFactory::arrayOf);
394            } else if (head instanceof Class<?> || head instanceof ParameterizedType) {
395                final Class<?> clazz;
396                if (head instanceof ParameterizedType) {
397                    final ParameterizedType parameterized = (ParameterizedType) head;
398                    clazz = (Class<?>) parameterized.getRawType();
399                } else {
400                    clazz = (Class<?>) head;
401                }
402                addSuperClassAndInterface(head, clazz, null);
403            } else if (head instanceof TypeVariable<?>) {
404                addAllIfUnseen(head, ((TypeVariable<?>) head).getBounds());
405            } else if (head instanceof WildcardType) {
406                final Type[] upperBounds = ((WildcardType) head).getUpperBounds();
407                if (upperBounds.length == 1) { // single type
408                    final Type upperBound = upperBounds[0];
409                    addSuperClassAndInterface(head, erase(upperBound), TypeFactory::wildcardExtends);
410                } else { // for each bound, add as a single supertype
411                    addAllIfUnseen(head, ((WildcardType) head).getUpperBounds());
412                }
413            }
414            return head;
415        }
416
417        private void addAllIfUnseen(final Type base, final Type... types) {
418            for (final Type type : types) {
419                addIfUnseen(resolveType(type, base));
420            }
421        }
422
423        private void addIfUnseen(final Type type) {
424            if (this.seen.add(type)) {
425                this.types.addLast(type);
426            }
427        }
428
429        private void addSuperClassAndInterface(final Type base, final Class<?> actualClass, final @Nullable UnaryOperator<Type> postProcess) {
430            if (this.includeInterfaces) {
431                for (final Type itf : actualClass.getGenericInterfaces()) {
432                    if (postProcess != null) {
433                        addIfUnseen(postProcess.apply(resolveType(itf, base)));
434                    } else {
435                        addIfUnseen(resolveType(itf, base));
436                    }
437                }
438            }
439
440            if (actualClass.getSuperclass() != null) {
441                final Type resolved = resolveType(actualClass.getGenericSuperclass(), base);
442                addIfUnseen(postProcess == null ? resolved : postProcess.apply(resolved));
443            }
444        }
445    }
446
447    static class CombinedAnnotations implements AnnotatedElement {
448        private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
449
450        private final AnnotatedElement[] elements;
451
452        CombinedAnnotations(final AnnotatedElement[] elements) {
453            this.elements = elements;
454        }
455
456        @Override
457        public boolean isAnnotationPresent(final Class<? extends Annotation> annotationClass) {
458            for (final AnnotatedElement element : this.elements) {
459                if (element.isAnnotationPresent(annotationClass)) {
460                    return true;
461                }
462            }
463            return false;
464        }
465
466        @Override
467        public <T extends Annotation> @Nullable T getAnnotation(final Class<T> annotationClass) {
468            @Nullable T ret = null;
469            for (final AnnotatedElement element : this.elements) {
470                ret = element.getAnnotation(annotationClass);
471                if (ret != null) {
472                    break;
473                }
474            }
475            return ret;
476        }
477
478        @Override
479        public Annotation[] getAnnotations() {
480            final List<Annotation> annotations = new ArrayList<>();
481            for (final AnnotatedElement element : this.elements) {
482                final Annotation[] annotation = element.getAnnotations();
483                if (annotation.length > 0) {
484                    annotations.addAll(Arrays.asList(annotation));
485                }
486            }
487            return annotations.toArray(EMPTY_ANNOTATION_ARRAY);
488        }
489
490        @SuppressWarnings("unchecked")
491        @Override
492        public <T extends Annotation> T[] getAnnotationsByType(final Class<T> annotationClass) {
493            final List<T> annotations = new ArrayList<>();
494            for (final AnnotatedElement element : this.elements) {
495                final T[] annotation = element.getAnnotationsByType(annotationClass);
496                if (annotation.length > 0) {
497                    annotations.addAll(Arrays.asList(annotation));
498                }
499            }
500            return annotations.toArray((T[]) EMPTY_ANNOTATION_ARRAY);
501        }
502
503        @Override
504        public <T extends Annotation> @Nullable T getDeclaredAnnotation(final Class<T> annotationClass) {
505            @Nullable T ret = null;
506            for (final AnnotatedElement element : this.elements) {
507                ret = element.getDeclaredAnnotation(annotationClass);
508                if (ret != null) {
509                    break;
510                }
511            }
512            return ret;
513        }
514
515        @SuppressWarnings("unchecked")
516        @Override
517        public <T extends Annotation> T[] getDeclaredAnnotationsByType(final Class<T> annotationClass) {
518            final List<T> annotations = new ArrayList<>();
519            for (final AnnotatedElement element : this.elements) {
520                final T[] annotation = element.getDeclaredAnnotationsByType(annotationClass);
521                if (annotation.length > 0) {
522                    annotations.addAll(Arrays.asList(annotation));
523                }
524            }
525            return annotations.toArray((T[]) EMPTY_ANNOTATION_ARRAY);
526        }
527
528        @Override
529        public Annotation[] getDeclaredAnnotations() {
530            final List<Annotation> annotations = new ArrayList<>();
531            for (final AnnotatedElement element : this.elements) {
532                final Annotation[] annotation = element.getDeclaredAnnotations();
533                if (annotation.length > 0) {
534                    annotations.addAll(Arrays.asList(annotation));
535                }
536            }
537            return annotations.toArray(EMPTY_ANNOTATION_ARRAY);
538        }
539    }
540
541    /**
542     * Get all declared methods in the provided class and its superclasses and
543     * superinterfaces, up to but not including {@link Object}.
544     *
545     * <p>Overridden methods will be skipped when encountered in
546     * parent types.</p>
547     *
548     * @param clazz the class to visit
549     * @return a list of methods that may not be modifiable
550     * @since 4.2.0
551     */
552    public static List<Method> allDeclaredMethods(final Class<?> clazz) {
553        final List<Method> seenMethods = new ArrayList<>();
554        final Set<String> seenSignatures = new HashSet<>();
555        final Deque<Class<?>> typesToVisit = new ArrayDeque<>();
556        typesToVisit.add(clazz);
557
558        Class<?> pointer;
559        while ((pointer = typesToVisit.poll()) != null) {
560            // Visit all methods
561            for (final Method method : pointer.getDeclaredMethods()) {
562                final StringBuilder descBuilder = new StringBuilder(method.getName());
563                descBuilder.append('(');
564                for (final Class<?> param : method.getParameterTypes()) {
565                    // this is wrong but it's close enough for our purposes
566                    descBuilder.append(param.getName()).append(';');
567                }
568                descBuilder.append(')')
569                    .append(method.getReturnType().getName());
570
571                final String desc = descBuilder.toString();
572
573                if (seenSignatures.add(desc)) {
574                    seenMethods.add(method);
575                }
576            }
577
578            // Push supertypes
579            final Class<?> superclass = pointer.getSuperclass();
580            if (superclass != null && !Object.class.equals(superclass)) {
581                typesToVisit.add(superclass);
582            }
583
584            // Push superinterfaces (only including default methods)
585            Collections.addAll(typesToVisit, pointer.getInterfaces());
586        }
587
588        return seenMethods;
589    }
590
591}