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}