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.ParameterizedType;
033import java.lang.reflect.Type;
034import java.lang.reflect.TypeVariable;
035import java.lang.reflect.WildcardType;
036import java.util.ArrayDeque;
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Deque;
040import java.util.HashSet;
041import java.util.Iterator;
042import java.util.List;
043import java.util.Map;
044import java.util.Set;
045import java.util.Spliterator;
046import java.util.Spliterators;
047import java.util.function.UnaryOperator;
048import java.util.stream.Stream;
049import java.util.stream.StreamSupport;
050
051/**
052 * Utility methods for working with generic types.
053 *
054 * <p>Most of these utilities are designed to go along with
055 * <a href="https://github.com/leangen/geantyref">GeAnTyRef</a>.</p>
056 *
057 * @see GenericTypeReflector for other tools to work with types
058 * @see TypeFactory for methods to construct types
059 * @since 4.0.0
060 */
061public final class Types {
062
063    private static final Map<Type, Type> BOXED_TO_PRIMITIVE = UnmodifiableCollections.buildMap(m -> {
064        m.put(Boolean.class, boolean.class);
065        m.put(Character.class, char.class);
066        m.put(Byte.class, byte.class);
067        m.put(Short.class, short.class);
068        m.put(Integer.class, int.class);
069        m.put(Long.class, long.class);
070        m.put(Float.class, float.class);
071        m.put(Double.class, double.class);
072        m.put(Void.class, void.class);
073    });
074
075    private static final Map<Type, Type> PRIMITIVE_TO_BOXED = UnmodifiableCollections.buildMap(m -> {
076        m.put(boolean.class, Boolean.class);
077        m.put(char.class, Character.class);
078        m.put(byte.class, Byte.class);
079        m.put(short.class, Short.class);
080        m.put(int.class, Integer.class);
081        m.put(long.class, Long.class);
082        m.put(float.class, Float.class);
083        m.put(double.class, Double.class);
084        m.put(void.class, Void.class);
085    });
086
087    private Types() {
088    }
089
090    /**
091     * Get if the provided type is an array type.
092     *
093     * <p>Being an array type means that the provided
094     * type has a component type.</p>
095     *
096     * @param input input type
097     * @return whether the type is an array
098     * @since 4.0.0
099     */
100    public static boolean isArray(final Type input) {
101        if (input instanceof Class<?>) {
102            return ((Class<?>) input).isArray();
103        } else if (input instanceof ParameterizedType) {
104            return isArray(((ParameterizedType) input).getRawType());
105        } else {
106            return input instanceof GenericArrayType;
107        }
108    }
109
110    /**
111     * Get whether or not the provided input type is a boxed primitive type.
112     *
113     * <p>This check will <em>not</em> match unboxed primitives.</p>
114     *
115     * @param input type to check
116     * @return if type is a boxed primitive
117     * @since 4.0.0
118     */
119    public static boolean isBoxedPrimitive(final Type input) {
120        return BOXED_TO_PRIMITIVE.containsKey(input);
121    }
122
123    /**
124     * Unbox the input type if it is a boxed primitive.
125     *
126     * @param input input type
127     * @return the unboxed version of the input type,
128     *          or the input type if it was already non-primitive
129     * @since 4.0.0
130     */
131    public static Type unbox(final Type input) {
132        final Type ret = BOXED_TO_PRIMITIVE.get(input);
133        return ret == null ? input : ret;
134    }
135
136    /**
137     * Get the default value for a type.
138     *
139     * <p>For all reference types, this is {@code null}. For all primitive
140     * types, this is equivalent to their defined {@code default} value.</p>
141     *
142     * @param type the type to get a default value for
143     * @return the default value, or {@code null} for reference types
144     * @since 4.1.0
145     */
146    public static @Nullable Object defaultValue(final Class<?> type) {
147        requireNonNull(type, "type");
148
149        if (!type.isPrimitive() || type == void.class) {
150            return null;
151        } else if (type == boolean.class) {
152            return Boolean.FALSE;
153        } else if (type == char.class) {
154            return (char) 0;
155        } else if (type == byte.class) {
156            return (byte) 0;
157        } else if (type == short.class) {
158            return (short) 0;
159        } else if (type == int.class) {
160            return 0;
161        } else if (type == long.class) {
162            return 0L;
163        } else if (type == float.class) {
164            return 0F;
165        } else if (type == double.class) {
166            return 0D;
167        } else {
168            // TODO: Verify that this works with Valhalla primitive types
169            return Array.get(Array.newInstance(type, 1), 0);
170        }
171    }
172
173    /**
174     * Box the input type if it is an unboxed primitive {@link Class}.
175     *
176     * @param input input type
177     * @return the unboxed version of the input type, or the input type if
178     *          it was already a primitive, or had no primitive equivalent
179     * @since 4.0.0
180     */
181    public static Type box(final Type input) {
182        final Type ret = PRIMITIVE_TO_BOXED.get(input);
183        return ret == null ? input : ret;
184    }
185
186    /**
187     * Given an element type, create a new list type.
188     *
189     * @param elementType type token representing the element type
190     * @param <T> type of element
191     * @return new list type token
192     * @since 4.0.0
193     */
194    @SuppressWarnings("unchecked")
195    public static <T> TypeToken<List<T>> makeListType(final TypeToken<T> elementType) {
196        return (TypeToken<List<T>>) TypeToken.get(TypeFactory.parameterizedClass(List.class, elementType.getType()));
197    }
198
199    /**
200     * Get an element containing the annotations of all the provided elements.
201     *
202     * <p>If multiple elements have the same annotation, only the first one
203     * with an applicable type is returned.</p>
204     *
205     * @param elements elements to combine
206     * @return new union element
207     * @since 4.0.0
208     */
209    public static AnnotatedElement combinedAnnotations(final AnnotatedElement... elements) {
210        return new CombinedAnnotations(Arrays.copyOf(elements, elements.length));
211    }
212
213    /**
214     * Throw an exception if the passed type is raw (missing parameters)..
215     *
216     * @param input input type
217     * @return type, passed through
218     * @since 4.0.0
219     */
220    public static Type requireCompleteParameters(final Type input) {
221        if (GenericTypeReflector.isMissingTypeParameters(input)) {
222            throw new IllegalArgumentException("Provided type " + input + " is a raw type, which is not accepted.");
223        }
224        return input;
225    }
226
227    /**
228     * Get all supertypes of this object with type parameters.
229     *
230     * <p>The iteration order is undefined. The returned stream will include the
231     * base type plus superclasses, but not superinterfaces.</p>
232     *
233     * @param type base type
234     * @return stream of supertypes
235     * @since 4.0.0
236     */
237    public static Stream<Type> allSuperTypes(final Type type) {
238        return calculateSuperTypes(type, false);
239    }
240
241    /**
242     * Get all supertypes and interfaces of the provided type.
243     *
244     * <p>The iteration order is undefined. The returned stream will include the
245     * base type plus superclasses and superinterfaces.</p>
246     *
247     * @param type base type
248     * @return stream of supertypes
249     * @since 4.0.0
250     */
251    public static Stream<Type> allSuperTypesAndInterfaces(final Type type) {
252        return calculateSuperTypes(type, true);
253    }
254
255    private static Stream<Type> calculateSuperTypes(final Type type, final boolean includeInterfaces) {
256        requireNonNull(type, "type");
257        return StreamSupport.stream(Spliterators.spliterator(new SuperTypesIterator(type, includeInterfaces), Long.MAX_VALUE,
258                                                             Spliterator.NONNULL | Spliterator.IMMUTABLE), false);
259    }
260
261    /**
262     * Recursively iterate through supertypes.
263     */
264    static class SuperTypesIterator implements Iterator<Type> {
265        private final boolean includeInterfaces;
266        private final Deque<Type> types = new ArrayDeque<>();
267        private final Set<Type> seen = new HashSet<>();
268
269        SuperTypesIterator(final Type base, final boolean includeInterfaces) {
270            this.types.add(base);
271            this.includeInterfaces = includeInterfaces;
272        }
273
274        @Override
275        public boolean hasNext() {
276            return !this.types.isEmpty();
277        }
278
279        @Override
280        public Type next() {
281            // Get current type, throws the correct exception if empty
282            final Type head = this.types.removeLast();
283
284            // Calculate the next step depending on the type of Type seen
285            // Arrays, covariant based on component type
286            if ((head instanceof Class<?> && ((Class<?>) head).isArray()) || head instanceof GenericArrayType) {
287                // find a super component-type
288                final Type componentType;
289                if (head instanceof Class<?>) {
290                    componentType = ((Class<?>) head).getComponentType();
291                } else {
292                    componentType = ((GenericArrayType) head).getGenericComponentType();
293                }
294
295                addSuperClassAndInterface(componentType, erase(componentType), TypeFactory::arrayOf);
296            } else if (head instanceof Class<?> || head instanceof ParameterizedType) {
297                final Class<?> clazz;
298                if (head instanceof ParameterizedType) {
299                    final ParameterizedType parameterized = (ParameterizedType) head;
300                    clazz = (Class<?>) parameterized.getRawType();
301                } else {
302                    clazz = (Class<?>) head;
303                }
304                addSuperClassAndInterface(head, clazz, null);
305            } else if (head instanceof TypeVariable<?>) {
306                addAllIfUnseen(head, ((TypeVariable<?>) head).getBounds());
307            } else if (head instanceof WildcardType) {
308                final Type[] upperBounds = ((WildcardType) head).getUpperBounds();
309                if (upperBounds.length == 1) { // single type
310                    final Type upperBound = upperBounds[0];
311                    addSuperClassAndInterface(head, erase(upperBound), TypeFactory::wildcardExtends);
312                } else { // for each bound, add as a single supertype
313                    addAllIfUnseen(head, ((WildcardType) head).getUpperBounds());
314                }
315            }
316            return head;
317        }
318
319        private void addAllIfUnseen(final Type base, final Type... types) {
320            for (final Type type : types) {
321                addIfUnseen(resolveType(type, base));
322            }
323        }
324
325        private void addIfUnseen(final Type type) {
326            if (this.seen.add(type)) {
327                this.types.addLast(type);
328            }
329        }
330
331        private void addSuperClassAndInterface(final Type base, final Class<?> actualClass, final @Nullable UnaryOperator<Type> postProcess) {
332            if (this.includeInterfaces) {
333                for (Type itf : actualClass.getGenericInterfaces()) {
334                    if (postProcess != null) {
335                        addIfUnseen(postProcess.apply(resolveType(itf, base)));
336                    } else {
337                        addIfUnseen(resolveType(itf, base));
338                    }
339                }
340            }
341
342            if (actualClass.getSuperclass() != null) {
343                final Type resolved = resolveType(actualClass.getGenericSuperclass(), base);
344                addIfUnseen(postProcess == null ? resolved : postProcess.apply(resolved));
345            }
346        }
347    }
348
349    static class CombinedAnnotations implements AnnotatedElement {
350        private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
351
352        private final AnnotatedElement[] elements;
353
354        CombinedAnnotations(final AnnotatedElement[] elements) {
355            this.elements = elements;
356        }
357
358        @Override
359        public boolean isAnnotationPresent(final Class<? extends Annotation> annotationClass) {
360            for (AnnotatedElement element : this.elements) {
361                if (element.isAnnotationPresent(annotationClass)) {
362                    return true;
363                }
364            }
365            return false;
366        }
367
368        @Override
369        public <T extends Annotation> @Nullable T getAnnotation(final Class<T> annotationClass) {
370            @Nullable T ret = null;
371            for (AnnotatedElement element : this.elements) {
372                ret = element.getAnnotation(annotationClass);
373                if (ret != null) {
374                    break;
375                }
376            }
377            return ret;
378        }
379
380        @Override
381        public Annotation[] getAnnotations() {
382            final List<Annotation> annotations = new ArrayList<>();
383            for (AnnotatedElement element : this.elements) {
384                final Annotation[] annotation = element.getAnnotations();
385                if (annotation.length > 0) {
386                    annotations.addAll(Arrays.asList(annotation));
387                }
388            }
389            return annotations.toArray(EMPTY_ANNOTATION_ARRAY);
390        }
391
392        @SuppressWarnings("unchecked")
393        @Override
394        public <T extends Annotation> T[] getAnnotationsByType(final Class<T> annotationClass) {
395            final List<T> annotations = new ArrayList<>();
396            for (AnnotatedElement element : this.elements) {
397                final T[] annotation = element.getAnnotationsByType(annotationClass);
398                if (annotation.length > 0) {
399                    annotations.addAll(Arrays.asList(annotation));
400                }
401            }
402            return annotations.toArray((T[]) EMPTY_ANNOTATION_ARRAY);
403        }
404
405        @Override
406        public <T extends Annotation> @Nullable T getDeclaredAnnotation(final Class<T> annotationClass) {
407            @Nullable T ret = null;
408            for (AnnotatedElement element : this.elements) {
409                ret = element.getDeclaredAnnotation(annotationClass);
410                if (ret != null) {
411                    break;
412                }
413            }
414            return ret;
415        }
416
417        @SuppressWarnings("unchecked")
418        @Override
419        public <T extends Annotation> T[] getDeclaredAnnotationsByType(final Class<T> annotationClass) {
420            final List<T> annotations = new ArrayList<>();
421            for (AnnotatedElement element : this.elements) {
422                final T[] annotation = element.getDeclaredAnnotationsByType(annotationClass);
423                if (annotation.length > 0) {
424                    annotations.addAll(Arrays.asList(annotation));
425                }
426            }
427            return annotations.toArray((T[]) EMPTY_ANNOTATION_ARRAY);
428        }
429
430        @Override
431        public Annotation[] getDeclaredAnnotations() {
432            final List<Annotation> annotations = new ArrayList<>();
433            for (AnnotatedElement element : this.elements) {
434                final Annotation[] annotation = element.getDeclaredAnnotations();
435                if (annotation.length > 0) {
436                    annotations.addAll(Arrays.asList(annotation));
437                }
438            }
439            return annotations.toArray(EMPTY_ANNOTATION_ARRAY);
440        }
441    }
442
443}