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}