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