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