From ec3977160bba3250a1a918a66acf67cd5e861590 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 11 Jan 2022 12:55:33 -0500 Subject: [PATCH] Convert Types to Kotlin (#1488) --- .../main/java/com/squareup/moshi/Types.java | 386 ------------------ .../src/main/java/com/squareup/moshi/Types.kt | 352 ++++++++++++++++ 2 files changed, 352 insertions(+), 386 deletions(-) delete mode 100644 moshi/src/main/java/com/squareup/moshi/Types.java create mode 100644 moshi/src/main/java/com/squareup/moshi/Types.kt diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java deleted file mode 100644 index 19332e7..0000000 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.squareup.moshi; - -import static com.squareup.moshi.internal.Util.EMPTY_TYPE_ARRAY; -import static com.squareup.moshi.internal.Util.getGenericSupertype; -import static com.squareup.moshi.internal.Util.resolve; - -import com.squareup.moshi.internal.GenericArrayTypeImpl; -import com.squareup.moshi.internal.ParameterizedTypeImpl; -import com.squareup.moshi.internal.WildcardTypeImpl; -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Proxy; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import javax.annotation.CheckReturnValue; -import javax.annotation.Nullable; - -/** Factory methods for types. */ -@CheckReturnValue -public final class Types { - private Types() {} - - /** - * Resolves the generated {@link JsonAdapter} fully qualified class name for a given {@link - * JsonClass JsonClass-annotated} {@code clazz}. This is the same lookup logic used by both the - * Moshi code generation as well as lookup for any JsonClass-annotated classes. This can be useful - * if generating your own JsonAdapters without using Moshi's first party code gen. - * - * @param clazz the class to calculate a generated JsonAdapter name for. - * @return the resolved fully qualified class name to the expected generated JsonAdapter class. - * Note that this name will always be a top-level class name and not a nested class. - */ - public static String generatedJsonAdapterName(Class clazz) { - if (clazz.getAnnotation(JsonClass.class) == null) { - throw new IllegalArgumentException("Class does not have a JsonClass annotation: " + clazz); - } - return generatedJsonAdapterName(clazz.getName()); - } - - /** - * Resolves the generated {@link JsonAdapter} fully qualified class name for a given {@link - * JsonClass JsonClass-annotated} {@code className}. This is the same lookup logic used by both - * the Moshi code generation as well as lookup for any JsonClass-annotated classes. This can be - * useful if generating your own JsonAdapters without using Moshi's first party code gen. - * - * @param className the fully qualified class to calculate a generated JsonAdapter name for. - * @return the resolved fully qualified class name to the expected generated JsonAdapter class. - * Note that this name will always be a top-level class name and not a nested class. - */ - public static String generatedJsonAdapterName(String className) { - return className.replace("$", "_") + "JsonAdapter"; - } - - /** - * Checks if {@code annotations} contains {@code jsonQualifier}. Returns the subset of {@code - * annotations} without {@code jsonQualifier}, or null if {@code annotations} does not contain - * {@code jsonQualifier}. - */ - public static @Nullable Set nextAnnotations( - Set annotations, Class jsonQualifier) { - if (!jsonQualifier.isAnnotationPresent(JsonQualifier.class)) { - throw new IllegalArgumentException(jsonQualifier + " is not a JsonQualifier."); - } - if (annotations.isEmpty()) { - return null; - } - for (Annotation annotation : annotations) { - if (jsonQualifier.equals(annotation.annotationType())) { - Set delegateAnnotations = new LinkedHashSet<>(annotations); - delegateAnnotations.remove(annotation); - return Collections.unmodifiableSet(delegateAnnotations); - } - } - return null; - } - - /** - * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this - * method if {@code rawType} is not enclosed in another type. - */ - public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) { - if (typeArguments.length == 0) { - throw new IllegalArgumentException("Missing type arguments for " + rawType); - } - return ParameterizedTypeImpl.create(null, rawType, typeArguments); - } - - /** - * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this - * method if {@code rawType} is enclosed in {@code ownerType}. - */ - public static ParameterizedType newParameterizedTypeWithOwner( - Type ownerType, Type rawType, Type... typeArguments) { - if (typeArguments.length == 0) { - throw new IllegalArgumentException("Missing type arguments for " + rawType); - } - return ParameterizedTypeImpl.create(ownerType, rawType, typeArguments); - } - - /** Returns an array type whose elements are all instances of {@code componentType}. */ - public static GenericArrayType arrayOf(Type componentType) { - return GenericArrayTypeImpl.create(componentType); - } - - /** - * Returns a type that represents an unknown type that extends {@code bound}. For example, if - * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If - * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code ? - * extends Object}. - */ - public static WildcardType subtypeOf(Type bound) { - Type[] upperBounds; - if (bound instanceof WildcardType) { - upperBounds = ((WildcardType) bound).getUpperBounds(); - } else { - upperBounds = new Type[] {bound}; - } - return WildcardTypeImpl.create(upperBounds, EMPTY_TYPE_ARRAY); - } - - /** - * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code - * bound} is {@code String.class}, this returns {@code ? super String}. - */ - public static WildcardType supertypeOf(Type bound) { - Type[] lowerBounds; - if (bound instanceof WildcardType) { - lowerBounds = ((WildcardType) bound).getLowerBounds(); - } else { - lowerBounds = new Type[] {bound}; - } - return WildcardTypeImpl.create(new Type[] {Object.class}, lowerBounds); - } - - public static Class getRawType(Type type) { - if (type instanceof Class) { - // type is a normal class. - return (Class) type; - - } else if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - - // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but - // suspects some pathological case related to nested classes exists. - Type rawType = parameterizedType.getRawType(); - return (Class) rawType; - - } else if (type instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) type).getGenericComponentType(); - return Array.newInstance(getRawType(componentType), 0).getClass(); - - } else if (type instanceof TypeVariable) { - // We could use the variable's bounds, but that won't work if there are multiple. having a raw - // type that's more general than necessary is okay. - return Object.class; - - } else if (type instanceof WildcardType) { - return getRawType(((WildcardType) type).getUpperBounds()[0]); - - } else { - String className = type == null ? "null" : type.getClass().getName(); - throw new IllegalArgumentException( - "Expected a Class, ParameterizedType, or " - + "GenericArrayType, but <" - + type - + "> is of type " - + className); - } - } - - /** - * Returns the element type of this collection type. - * - * @throws IllegalArgumentException if this type is not a collection. - */ - public static Type collectionElementType(Type context, Class contextRawType) { - Type collectionType = getSupertype(context, contextRawType, Collection.class); - - if (collectionType instanceof WildcardType) { - collectionType = ((WildcardType) collectionType).getUpperBounds()[0]; - } - if (collectionType instanceof ParameterizedType) { - return ((ParameterizedType) collectionType).getActualTypeArguments()[0]; - } - return Object.class; - } - - /** Returns true if {@code a} and {@code b} are equal. */ - public static boolean equals(@Nullable Type a, @Nullable Type b) { - if (a == b) { - return true; // Also handles (a == null && b == null). - - } else if (a instanceof Class) { - if (b instanceof GenericArrayType) { - return equals( - ((Class) a).getComponentType(), ((GenericArrayType) b).getGenericComponentType()); - } - return a.equals(b); // Class already specifies equals(). - - } else if (a instanceof ParameterizedType) { - if (!(b instanceof ParameterizedType)) return false; - ParameterizedType pa = (ParameterizedType) a; - ParameterizedType pb = (ParameterizedType) b; - Type[] aTypeArguments = - pa instanceof ParameterizedTypeImpl - ? ((ParameterizedTypeImpl) pa).typeArguments - : pa.getActualTypeArguments(); - Type[] bTypeArguments = - pb instanceof ParameterizedTypeImpl - ? ((ParameterizedTypeImpl) pb).typeArguments - : pb.getActualTypeArguments(); - return equals(pa.getOwnerType(), pb.getOwnerType()) - && pa.getRawType().equals(pb.getRawType()) - && Arrays.equals(aTypeArguments, bTypeArguments); - - } else if (a instanceof GenericArrayType) { - if (b instanceof Class) { - return equals( - ((Class) b).getComponentType(), ((GenericArrayType) a).getGenericComponentType()); - } - if (!(b instanceof GenericArrayType)) return false; - GenericArrayType ga = (GenericArrayType) a; - GenericArrayType gb = (GenericArrayType) b; - return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); - - } else if (a instanceof WildcardType) { - if (!(b instanceof WildcardType)) return false; - WildcardType wa = (WildcardType) a; - WildcardType wb = (WildcardType) b; - return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) - && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); - - } else if (a instanceof TypeVariable) { - if (!(b instanceof TypeVariable)) return false; - TypeVariable va = (TypeVariable) a; - TypeVariable vb = (TypeVariable) b; - return va.getGenericDeclaration() == vb.getGenericDeclaration() - && va.getName().equals(vb.getName()); - - } else { - // This isn't a supported type. - return false; - } - } - - /** - * @param clazz the target class to read the {@code fieldName} field annotations from. - * @param fieldName the target field name on {@code clazz}. - * @return a set of {@link JsonQualifier}-annotated {@link Annotation} instances retrieved from - * the targeted field. Can be empty if none are found. - * @deprecated this is no longer needed in Kotlin 1.6.0 (which has direct annotation - * instantiation) and is obsolete. - */ - @Deprecated - public static Set getFieldJsonQualifierAnnotations( - Class clazz, String fieldName) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - Annotation[] fieldAnnotations = field.getDeclaredAnnotations(); - Set annotations = new LinkedHashSet<>(fieldAnnotations.length); - for (Annotation annotation : fieldAnnotations) { - if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) { - annotations.add(annotation); - } - } - return Collections.unmodifiableSet(annotations); - } catch (NoSuchFieldException e) { - throw new IllegalArgumentException( - "Could not access field " + fieldName + " on class " + clazz.getCanonicalName(), e); - } - } - - @SuppressWarnings("unchecked") - static T createJsonQualifierImplementation(final Class annotationType) { - if (!annotationType.isAnnotation()) { - throw new IllegalArgumentException(annotationType + " must be an annotation."); - } - if (!annotationType.isAnnotationPresent(JsonQualifier.class)) { - throw new IllegalArgumentException(annotationType + " must have @JsonQualifier."); - } - if (annotationType.getDeclaredMethods().length != 0) { - throw new IllegalArgumentException(annotationType + " must not declare methods."); - } - return (T) - Proxy.newProxyInstance( - annotationType.getClassLoader(), - new Class[] {annotationType}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - switch (methodName) { - case "annotationType": - return annotationType; - case "equals": - Object o = args[0]; - return annotationType.isInstance(o); - case "hashCode": - return 0; - case "toString": - return "@" + annotationType.getName() + "()"; - default: - return method.invoke(proxy, args); - } - } - }); - } - - /** - * Returns a two element array containing this map's key and value types in positions 0 and 1 - * respectively. - */ - static Type[] mapKeyAndValueTypes(Type context, Class contextRawType) { - // Work around a problem with the declaration of java.util.Properties. That class should extend - // Hashtable, but it's declared to extend Hashtable. - if (context == Properties.class) return new Type[] {String.class, String.class}; - - Type mapType = getSupertype(context, contextRawType, Map.class); - if (mapType instanceof ParameterizedType) { - ParameterizedType mapParameterizedType = (ParameterizedType) mapType; - return mapParameterizedType.getActualTypeArguments(); - } - return new Type[] {Object.class, Object.class}; - } - - /** - * Returns the generic form of {@code supertype}. For example, if this is {@code - * ArrayList}, this returns {@code Iterable} given the input {@code - * Iterable.class}. - * - * @param supertype a superclass of, or interface implemented by, this. - */ - static Type getSupertype(Type context, Class contextRawType, Class supertype) { - if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException(); - return resolve( - getGenericSupertype(context, contextRawType, supertype), context, contextRawType); - } - - static Type getGenericSuperclass(Type type) { - Class rawType = Types.getRawType(type); - return resolve(rawType.getGenericSuperclass(), type, rawType); - } - - /** - * Returns the element type of {@code type} if it is an array type, or null if it is not an array - * type. - */ - static Type arrayComponentType(Type type) { - if (type instanceof GenericArrayType) { - return ((GenericArrayType) type).getGenericComponentType(); - } else if (type instanceof Class) { - return ((Class) type).getComponentType(); - } else { - return null; - } - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/Types.kt b/moshi/src/main/java/com/squareup/moshi/Types.kt new file mode 100644 index 0000000..6f85d4e --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/Types.kt @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi + +import com.squareup.moshi.internal.EMPTY_TYPE_ARRAY +import com.squareup.moshi.internal.GenericArrayTypeImpl +import com.squareup.moshi.internal.ParameterizedTypeImpl +import com.squareup.moshi.internal.WildcardTypeImpl +import com.squareup.moshi.internal.getGenericSupertype +import com.squareup.moshi.internal.resolve +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Proxy +import java.lang.reflect.Type +import java.lang.reflect.TypeVariable +import java.lang.reflect.WildcardType +import java.util.Collections +import java.util.Properties +import javax.annotation.CheckReturnValue + +/** Factory methods for types. */ +@CheckReturnValue +public object Types { + /** + * Resolves the generated [JsonAdapter] fully qualified class name for a given [clazz]. This is the same lookup logic + * used by both the Moshi code generation as well as lookup for any JsonClass-annotated classes. This can be useful + * if generating your own JsonAdapters without using Moshi's first party code gen. + * + * @param clazz the class to calculate a generated JsonAdapter name for. + * @return the resolved fully qualified class name to the expected generated JsonAdapter class. + * Note that this name will always be a top-level class name and not a nested class. + */ + @JvmStatic + public fun generatedJsonAdapterName(clazz: Class<*>): String { + if (clazz.getAnnotation(JsonClass::class.java) == null) { + throw IllegalArgumentException("Class does not have a JsonClass annotation: $clazz") + } + return generatedJsonAdapterName(clazz.name) + } + + /** + * Resolves the generated [JsonAdapter] fully qualified class name for a given [ ] `className`. This is the same lookup logic used by both + * the Moshi code generation as well as lookup for any JsonClass-annotated classes. This can be + * useful if generating your own JsonAdapters without using Moshi's first party code gen. + * + * @param className the fully qualified class to calculate a generated JsonAdapter name for. + * @return the resolved fully qualified class name to the expected generated JsonAdapter class. + * Note that this name will always be a top-level class name and not a nested class. + */ + @JvmStatic + public fun generatedJsonAdapterName(className: String): String { + return className.replace("$", "_") + "JsonAdapter" + } + + /** + * Checks if `annotations` contains `jsonQualifier`. Returns the subset of `annotations` without `jsonQualifier`, or null if `annotations` does not contain + * `jsonQualifier`. + */ + @JvmStatic + public fun nextAnnotations( + annotations: Set, + jsonQualifier: Class + ): Set? { + require(jsonQualifier.isAnnotationPresent(JsonQualifier::class.java)) { + "$jsonQualifier is not a JsonQualifier." + } + if (annotations.isEmpty()) { + return null + } + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((jsonQualifier == (annotation as java.lang.annotation.Annotation).annotationType())) { + val delegateAnnotations = LinkedHashSet(annotations) + delegateAnnotations.remove(annotation) + return Collections.unmodifiableSet(delegateAnnotations) + } + } + return null + } + + /** + * Returns a new parameterized type, applying `typeArguments` to `rawType`. Use this + * method if `rawType` is not enclosed in another type. + */ + @JvmStatic + public fun newParameterizedType(rawType: Type, vararg typeArguments: Type): ParameterizedType { + require(typeArguments.isNotEmpty()) { + "Missing type arguments for $rawType" + } + return ParameterizedTypeImpl(null, rawType, *typeArguments) + } + + /** + * Returns a new parameterized type, applying `typeArguments` to `rawType`. Use this + * method if `rawType` is enclosed in `ownerType`. + */ + @JvmStatic + public fun newParameterizedTypeWithOwner( + ownerType: Type?, + rawType: Type, + vararg typeArguments: Type + ): ParameterizedType { + require(typeArguments.isNotEmpty()) { + "Missing type arguments for $rawType" + } + return ParameterizedTypeImpl(ownerType, rawType, *typeArguments) + } + + /** Returns an array type whose elements are all instances of `componentType`. */ + @JvmStatic + public fun arrayOf(componentType: Type): GenericArrayType { + return GenericArrayTypeImpl(componentType) + } + + /** + * Returns a type that represents an unknown type that extends `bound`. For example, if + * `bound` is `CharSequence.class`, this returns `? extends CharSequence`. If + * `bound` is `Object.class`, this returns `?`, which is shorthand for `? + * extends Object`. + */ + @JvmStatic + public fun subtypeOf(bound: Type): WildcardType { + val upperBounds = if (bound is WildcardType) { + bound.upperBounds + } else { + arrayOf(bound) + } + return WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY) + } + + /** + * Returns a type that represents an unknown supertype of `bound`. For example, if `bound` is `String.class`, this returns `? super String`. + */ + @JvmStatic + public fun supertypeOf(bound: Type): WildcardType { + val lowerBounds = if (bound is WildcardType) { + bound.lowerBounds + } else { + arrayOf(bound) + } + return WildcardTypeImpl(arrayOf(Any::class.java), lowerBounds) + } + + @JvmStatic + public fun getRawType(type: Type?): Class<*> { + return when (type) { + is Class<*> -> { + // type is a normal class. + type + } + is ParameterizedType -> { + // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but + // suspects some pathological case related to nested classes exists. + val rawType = type.rawType + rawType as Class<*> + } + is GenericArrayType -> { + val componentType = type.genericComponentType + java.lang.reflect.Array.newInstance(getRawType(componentType), 0).javaClass + } + is TypeVariable<*> -> { + // We could use the variable's bounds, but that won't work if there are multiple. having a raw + // type that's more general than necessary is okay. + Any::class.java + } + is WildcardType -> getRawType(type.upperBounds[0]) + else -> { + val className = if (type == null) "null" else type.javaClass.name + throw IllegalArgumentException("Expected a Class, ParameterizedType, or GenericArrayType, but <$type> is of type $className") + } + } + } + + /** + * Returns the element type of this collection type. + * + * @throws IllegalArgumentException if this type is not a collection. + */ + @JvmStatic + public fun collectionElementType(context: Type, contextRawType: Class<*>): Type { + var collectionType: Type? = getSupertype(context, contextRawType, MutableCollection::class.java) + if (collectionType is WildcardType) { + collectionType = collectionType.upperBounds[0] + } + return if (collectionType is ParameterizedType) { + collectionType.actualTypeArguments[0] + } else Any::class.java + } + + /** Returns true if `a` and `b` are equal. */ + @JvmStatic + public fun equals(a: Type?, b: Type?): Boolean { + if (a === b) { + return true // Also handles (a == null && b == null). + } + // This isn't a supported type. + when (a) { + is Class<*> -> { + return if (b is GenericArrayType) { + equals(a.componentType, b.genericComponentType) + } else { + a == b // Class already specifies equals(). + } + } + is ParameterizedType -> { + if (b !is ParameterizedType) return false + val aTypeArguments = if (a is ParameterizedTypeImpl) a.typeArguments else a.actualTypeArguments + val bTypeArguments = if (b is ParameterizedTypeImpl) b.typeArguments else b.actualTypeArguments + return ( + equals(a.ownerType, b.ownerType) && + (a.rawType == b.rawType) && aTypeArguments.contentEquals(bTypeArguments) + ) + } + is GenericArrayType -> { + if (b is Class<*>) { + return equals(b.componentType, a.genericComponentType) + } + if (b !is GenericArrayType) return false + return equals(a.genericComponentType, b.genericComponentType) + } + is WildcardType -> { + if (b !is WildcardType) return false + return (a.upperBounds.contentEquals(b.upperBounds) && a.lowerBounds.contentEquals(b.lowerBounds)) + } + is TypeVariable<*> -> { + if (b !is TypeVariable<*>) return false + return (a.genericDeclaration === b.genericDeclaration && (a.name == b.name)) + } + else -> return false // This isn't a supported type. + } + } + + /** + * @param clazz the target class to read the `fieldName` field annotations from. + * @param fieldName the target field name on `clazz`. + * @return a set of [JsonQualifier]-annotated [Annotation] instances retrieved from + * the targeted field. Can be empty if none are found. + */ + @Deprecated("This is no longer needed in Kotlin 1.6.0 (which has direct annotation instantiation) and is obsolete.") + @JvmStatic + public fun getFieldJsonQualifierAnnotations( + clazz: Class<*>, + fieldName: String + ): Set { + try { + val field = clazz.getDeclaredField(fieldName) + field.isAccessible = true + val fieldAnnotations = field.declaredAnnotations + val annotations: MutableSet = LinkedHashSet(fieldAnnotations.size) + for (annotation in fieldAnnotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType() + .isAnnotationPresent(JsonQualifier::class.java) + ) { + annotations.add(annotation) + } + } + return Collections.unmodifiableSet(annotations) + } catch (e: NoSuchFieldException) { + throw IllegalArgumentException( + "Could not access field " + fieldName + " on class " + clazz.canonicalName, e + ) + } + } + + @JvmStatic + public fun createJsonQualifierImplementation(annotationType: Class): T { + require(annotationType.isAnnotation) { + "$annotationType must be an annotation." + } + require(annotationType.isAnnotationPresent(JsonQualifier::class.java)) { + "$annotationType must have @JsonQualifier." + } + require(annotationType.declaredMethods.isEmpty()) { + "$annotationType must not declare methods." + } + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance( + annotationType.classLoader, arrayOf>(annotationType) + ) { proxy, method, args -> + when (method.name) { + "annotationType" -> annotationType + "equals" -> { + val o = args[0] + annotationType.isInstance(o) + } + "hashCode" -> 0 + "toString" -> "@" + annotationType.name + "()" + else -> method.invoke(proxy, *args) + } + } as T + } + + /** + * Returns a two element array containing this map's key and value types in positions 0 and 1 + * respectively. + */ + @JvmStatic + public fun mapKeyAndValueTypes(context: Type, contextRawType: Class<*>): Array { + // Work around a problem with the declaration of java.util.Properties. That class should extend + // Hashtable, but it's declared to extend Hashtable. + if (context === Properties::class.java) return arrayOf(String::class.java, String::class.java) + val mapType = getSupertype(context, contextRawType, MutableMap::class.java) + if (mapType is ParameterizedType) { + return mapType.actualTypeArguments + } + return arrayOf(Any::class.java, Any::class.java) + } + + /** + * Returns the generic form of `supertype`. For example, if this is `ArrayList`, this returns `Iterable` given the input `Iterable.class`. + * + * @param supertype a superclass of, or interface implemented by, this. + */ + @JvmStatic + public fun getSupertype(context: Type, contextRawType: Class<*>, supertype: Class<*>): Type { + if (!supertype.isAssignableFrom(contextRawType)) throw IllegalArgumentException() + return getGenericSupertype(context, contextRawType, supertype).resolve((context), (contextRawType)) + } + + @JvmStatic + public fun getGenericSuperclass(type: Type): Type { + val rawType = getRawType(type) + return rawType.genericSuperclass.resolve(type, rawType) + } + + /** + * Returns the element type of `type` if it is an array type, or null if it is not an array + * type. + */ + @JvmStatic + public fun arrayComponentType(type: Type): Type? { + return when (type) { + is GenericArrayType -> type.genericComponentType + is Class<*> -> type.componentType + else -> null + } + } +}