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}