mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Convert Types to Kotlin (#1488)
This commit is contained in:
@@ -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<? extends Annotation> nextAnnotations(
|
||||
Set<? extends Annotation> annotations, Class<? extends Annotation> 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<? extends Annotation> 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<? extends Annotation> getFieldJsonQualifierAnnotations(
|
||||
Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
|
||||
Set<Annotation> 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 extends Annotation> T createJsonQualifierImplementation(final Class<T> 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<String, String>, but it's declared to extend Hashtable<Object, Object>.
|
||||
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<String>}, this returns {@code Iterable<String>} 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;
|
||||
}
|
||||
}
|
||||
}
|
352
moshi/src/main/java/com/squareup/moshi/Types.kt
Normal file
352
moshi/src/main/java/com/squareup/moshi/Types.kt
Normal file
@@ -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<Annotation>,
|
||||
jsonQualifier: Class<out Annotation?>
|
||||
): Set<Annotation>? {
|
||||
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<Type>(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<Type>(bound)
|
||||
}
|
||||
return WildcardTypeImpl(arrayOf<Type>(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<Annotation> {
|
||||
try {
|
||||
val field = clazz.getDeclaredField(fieldName)
|
||||
field.isAccessible = true
|
||||
val fieldAnnotations = field.declaredAnnotations
|
||||
val annotations: MutableSet<Annotation> = 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 <T : Annotation?> createJsonQualifierImplementation(annotationType: Class<T>): 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<Class<*>>(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<Type> {
|
||||
// Work around a problem with the declaration of java.util.Properties. That class should extend
|
||||
// Hashtable<String, String>, but it's declared to extend Hashtable<Object, Object>.
|
||||
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<String>`, this returns `Iterable<String>` 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
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user