mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Convert util package to Kotlin (#1461)
* Rename .java to .kt * Convert NonNullJsonAdapter * Rename .java to .kt * Convert NullSafeJsonAdapter * Fix missing null-check * Rename .java to .kt * Convert Util (initial pass) * Push to top-level Util * Use extensions and properties where possible * Use knownNotNull * Spotless * Clean up impl type classes a bit * Update code gen * Use extension for resolving * Spotless * Fix master conflicts * Rename to toStringWithAnnotations() * for (t in args.indices) { * for (i in interfaces.indices) { * Template
This commit is contained in:
@@ -78,7 +78,7 @@ final class FallbackEnum {
|
|||||||
nameStrings = new String[constants.length];
|
nameStrings = new String[constants.length];
|
||||||
for (int i = 0; i < constants.length; i++) {
|
for (int i = 0; i < constants.length; i++) {
|
||||||
String constantName = constants[i].name();
|
String constantName = constants[i].name();
|
||||||
nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName));
|
nameStrings[i] = Util.jsonName(enumType.getField(constantName), constantName);
|
||||||
}
|
}
|
||||||
options = JsonReader.Options.of(nameStrings);
|
options = JsonReader.Options.of(nameStrings);
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
|
@@ -68,7 +68,7 @@ public final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
|
|||||||
nameStrings = new String[constants.length];
|
nameStrings = new String[constants.length];
|
||||||
for (int i = 0; i < constants.length; i++) {
|
for (int i = 0; i < constants.length; i++) {
|
||||||
String constantName = constants[i].name();
|
String constantName = constants[i].name();
|
||||||
nameStrings[i] = jsonName(constantName, enumType.getField(constantName));
|
nameStrings[i] = jsonName(enumType.getField(constantName), constantName);
|
||||||
}
|
}
|
||||||
options = JsonReader.Options.of(nameStrings);
|
options = JsonReader.Options.of(nameStrings);
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
|
@@ -39,7 +39,6 @@ import com.squareup.moshi.JsonAdapter
|
|||||||
import com.squareup.moshi.JsonReader
|
import com.squareup.moshi.JsonReader
|
||||||
import com.squareup.moshi.JsonWriter
|
import com.squareup.moshi.JsonWriter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.internal.Util
|
|
||||||
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterOnly
|
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterOnly
|
||||||
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
|
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
|
||||||
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
|
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
|
||||||
@@ -47,7 +46,7 @@ import java.lang.reflect.Constructor
|
|||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import org.objectweb.asm.Type as AsmType
|
import org.objectweb.asm.Type as AsmType
|
||||||
|
|
||||||
private val MOSHI_UTIL = Util::class.asClassName()
|
private const val MOSHI_UTIL_PACKAGE = "com.squareup.moshi.internal"
|
||||||
private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
|
private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
|
||||||
private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren
|
private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren
|
||||||
|
|
||||||
@@ -61,8 +60,8 @@ public class AdapterGenerator(
|
|||||||
private companion object {
|
private companion object {
|
||||||
private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
|
private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
|
||||||
private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
|
private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
|
||||||
"%T.DEFAULT_CONSTRUCTOR_MARKER",
|
"%M",
|
||||||
Util::class
|
MemberName(MOSHI_UTIL_PACKAGE, "DEFAULT_CONSTRUCTOR_MARKER")
|
||||||
)
|
)
|
||||||
private val CN_MOSHI = Moshi::class.asClassName()
|
private val CN_MOSHI = Moshi::class.asClassName()
|
||||||
private val CN_TYPE = Type::class.asClassName()
|
private val CN_TYPE = Type::class.asClassName()
|
||||||
@@ -656,8 +655,8 @@ public class AdapterGenerator(
|
|||||||
|
|
||||||
private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
|
private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
|
||||||
return CodeBlock.of(
|
return CodeBlock.of(
|
||||||
"%T.unexpectedNull(%S, %S, %N)",
|
"%M(%S, %S, %N)",
|
||||||
MOSHI_UTIL,
|
MemberName(MOSHI_UTIL_PACKAGE, "unexpectedNull"),
|
||||||
property.localName,
|
property.localName,
|
||||||
property.jsonName,
|
property.jsonName,
|
||||||
reader
|
reader
|
||||||
@@ -699,8 +698,8 @@ public class AdapterGenerator(
|
|||||||
private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
|
private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
|
||||||
val missingPropertyBlock =
|
val missingPropertyBlock =
|
||||||
CodeBlock.of(
|
CodeBlock.of(
|
||||||
"%T.missingProperty(%S, %S, %N)",
|
"%M(%S, %S, %N)",
|
||||||
MOSHI_UTIL,
|
MemberName(MOSHI_UTIL_PACKAGE, "missingProperty"),
|
||||||
property.localName,
|
property.localName,
|
||||||
property.jsonName,
|
property.jsonName,
|
||||||
readerParam
|
readerParam
|
||||||
|
@@ -1270,7 +1270,7 @@ class GeneratedAdaptersTest {
|
|||||||
@Test fun customGenerator_withClassPresent() {
|
@Test fun customGenerator_withClassPresent() {
|
||||||
val moshi = Moshi.Builder().build()
|
val moshi = Moshi.Builder().build()
|
||||||
val adapter = moshi.adapter<CustomGeneratedClass>()
|
val adapter = moshi.adapter<CustomGeneratedClass>()
|
||||||
val unwrapped = (adapter as NullSafeJsonAdapter<CustomGeneratedClass>).delegate()
|
val unwrapped = (adapter as NullSafeJsonAdapter<CustomGeneratedClass>).delegate
|
||||||
assertThat(unwrapped).isInstanceOf(
|
assertThat(unwrapped).isInstanceOf(
|
||||||
GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter::class.java
|
GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter::class.java
|
||||||
)
|
)
|
||||||
|
@@ -22,9 +22,12 @@ import com.squareup.moshi.JsonReader
|
|||||||
import com.squareup.moshi.JsonWriter
|
import com.squareup.moshi.JsonWriter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.generatedAdapter
|
||||||
import com.squareup.moshi.internal.Util.generatedAdapter
|
import com.squareup.moshi.internal.isPlatformType
|
||||||
import com.squareup.moshi.internal.Util.resolve
|
import com.squareup.moshi.internal.jsonAnnotations
|
||||||
|
import com.squareup.moshi.internal.missingProperty
|
||||||
|
import com.squareup.moshi.internal.resolve
|
||||||
|
import com.squareup.moshi.internal.unexpectedNull
|
||||||
import com.squareup.moshi.rawType
|
import com.squareup.moshi.rawType
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
@@ -86,7 +89,7 @@ internal class KotlinJsonAdapter<T>(
|
|||||||
values[propertyIndex] = binding.adapter.fromJson(reader)
|
values[propertyIndex] = binding.adapter.fromJson(reader)
|
||||||
|
|
||||||
if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
|
if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
|
||||||
throw Util.unexpectedNull(
|
throw unexpectedNull(
|
||||||
binding.property.name,
|
binding.property.name,
|
||||||
binding.jsonName,
|
binding.jsonName,
|
||||||
reader
|
reader
|
||||||
@@ -102,7 +105,7 @@ internal class KotlinJsonAdapter<T>(
|
|||||||
when {
|
when {
|
||||||
constructor.parameters[i].isOptional -> isFullInitialized = false
|
constructor.parameters[i].isOptional -> isFullInitialized = false
|
||||||
constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
|
constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
|
||||||
else -> throw Util.missingProperty(
|
else -> throw missingProperty(
|
||||||
constructor.parameters[i].name,
|
constructor.parameters[i].name,
|
||||||
allBindings[i]?.jsonName,
|
allBindings[i]?.jsonName,
|
||||||
reader
|
reader
|
||||||
@@ -195,9 +198,9 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||||||
if (rawType.isInterface) return null
|
if (rawType.isInterface) return null
|
||||||
if (rawType.isEnum) return null
|
if (rawType.isEnum) return null
|
||||||
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
||||||
if (Util.isPlatformType(rawType)) return null
|
if (rawType.isPlatformType) return null
|
||||||
try {
|
try {
|
||||||
val generatedAdapter = generatedAdapter(moshi, type, rawType)
|
val generatedAdapter = moshi.generatedAdapter(type, rawType)
|
||||||
if (generatedAdapter != null) {
|
if (generatedAdapter != null) {
|
||||||
return generatedAdapter
|
return generatedAdapter
|
||||||
}
|
}
|
||||||
@@ -288,10 +291,10 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||||||
}
|
}
|
||||||
else -> error("Not possible!")
|
else -> error("Not possible!")
|
||||||
}
|
}
|
||||||
val resolvedPropertyType = resolve(type, rawType, propertyType)
|
val resolvedPropertyType = propertyType.resolve(type, rawType)
|
||||||
val adapter = moshi.adapter<Any?>(
|
val adapter = moshi.adapter<Any?>(
|
||||||
resolvedPropertyType,
|
resolvedPropertyType,
|
||||||
Util.jsonAnnotations(allAnnotations.toTypedArray()),
|
allAnnotations.toTypedArray().jsonAnnotations,
|
||||||
property.name
|
property.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -52,12 +52,12 @@ tasks.withType<Test>().configureEach {
|
|||||||
tasks.withType<KotlinCompile>()
|
tasks.withType<KotlinCompile>()
|
||||||
.configureEach {
|
.configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
val args = mutableListOf("-Xopt-in=kotlin.contracts.ExperimentalContracts")
|
val toAdd = mutableListOf("-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.contracts.ExperimentalContracts")
|
||||||
if (name.contains("test", true)) {
|
if (name.contains("test", true)) {
|
||||||
args += "-Xopt-in=kotlin.ExperimentalStdlibApi"
|
toAdd += "-Xopt-in=kotlin.ExperimentalStdlibApi"
|
||||||
}
|
}
|
||||||
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
|
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
|
||||||
freeCompilerArgs += args
|
freeCompilerArgs += toAdd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi
|
package com.squareup.moshi
|
||||||
|
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.boxIfPrimitive
|
||||||
import java.lang.reflect.GenericArrayType
|
import java.lang.reflect.GenericArrayType
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.lang.reflect.WildcardType
|
import java.lang.reflect.WildcardType
|
||||||
@@ -42,7 +42,7 @@ public inline fun <reified T : Annotation> Set<Annotation>.nextAnnotations(): Se
|
|||||||
public inline fun <reified T> subtypeOf(): WildcardType {
|
public inline fun <reified T> subtypeOf(): WildcardType {
|
||||||
var type = typeOf<T>().javaType
|
var type = typeOf<T>().javaType
|
||||||
if (type is Class<*>) {
|
if (type is Class<*>) {
|
||||||
type = Util.boxIfPrimitive(type)
|
type = type.boxIfPrimitive()
|
||||||
}
|
}
|
||||||
return Types.subtypeOf(type)
|
return Types.subtypeOf(type)
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ public inline fun <reified T> subtypeOf(): WildcardType {
|
|||||||
public inline fun <reified T> supertypeOf(): WildcardType {
|
public inline fun <reified T> supertypeOf(): WildcardType {
|
||||||
var type = typeOf<T>().javaType
|
var type = typeOf<T>().javaType
|
||||||
if (type is Class<*>) {
|
if (type is Class<*>) {
|
||||||
type = Util.boxIfPrimitive(type)
|
type = type.boxIfPrimitive()
|
||||||
}
|
}
|
||||||
return Types.supertypeOf(type)
|
return Types.supertypeOf(type)
|
||||||
}
|
}
|
||||||
|
@@ -16,8 +16,8 @@
|
|||||||
package com.squareup.moshi;
|
package com.squareup.moshi;
|
||||||
|
|
||||||
import static com.squareup.moshi.internal.Util.canonicalize;
|
import static com.squareup.moshi.internal.Util.canonicalize;
|
||||||
import static com.squareup.moshi.internal.Util.jsonAnnotations;
|
import static com.squareup.moshi.internal.Util.getJsonAnnotations;
|
||||||
import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations;
|
import static com.squareup.moshi.internal.Util.toStringWithAnnotations;
|
||||||
|
|
||||||
import com.squareup.moshi.internal.Util;
|
import com.squareup.moshi.internal.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -57,7 +57,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
"No "
|
"No "
|
||||||
+ missingAnnotation
|
+ missingAnnotation
|
||||||
+ " adapter for "
|
+ " adapter for "
|
||||||
+ typeAnnotatedWithAnnotations(type, annotations),
|
+ toStringWithAnnotations(type, annotations),
|
||||||
e);
|
e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -172,7 +172,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
&& parametersAreJsonAdapters(2, parameterTypes)) {
|
&& parametersAreJsonAdapters(2, parameterTypes)) {
|
||||||
// void pointToJson(JsonWriter jsonWriter, Point point) {
|
// void pointToJson(JsonWriter jsonWriter, Point point) {
|
||||||
// void pointToJson(JsonWriter jsonWriter, Point point, JsonAdapter<?> adapter, ...) {
|
// void pointToJson(JsonWriter jsonWriter, Point point, JsonAdapter<?> adapter, ...) {
|
||||||
Set<? extends Annotation> qualifierAnnotations = jsonAnnotations(parameterAnnotations[1]);
|
Set<? extends Annotation> qualifierAnnotations = getJsonAnnotations(parameterAnnotations[1]);
|
||||||
return new AdapterMethod(
|
return new AdapterMethod(
|
||||||
parameterTypes[1],
|
parameterTypes[1],
|
||||||
qualifierAnnotations,
|
qualifierAnnotations,
|
||||||
@@ -190,10 +190,10 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
|
|
||||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||||
// List<Integer> pointToJson(Point point) {
|
// List<Integer> pointToJson(Point point) {
|
||||||
final Set<? extends Annotation> returnTypeAnnotations = jsonAnnotations(method);
|
final Set<? extends Annotation> returnTypeAnnotations = Util.getJsonAnnotations(method);
|
||||||
final Set<? extends Annotation> qualifierAnnotations =
|
final Set<? extends Annotation> qualifierAnnotations =
|
||||||
jsonAnnotations(parameterAnnotations[0]);
|
getJsonAnnotations(parameterAnnotations[0]);
|
||||||
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
|
boolean nullable = Util.getHasNullable(parameterAnnotations[0]);
|
||||||
return new AdapterMethod(
|
return new AdapterMethod(
|
||||||
parameterTypes[0],
|
parameterTypes[0],
|
||||||
qualifierAnnotations,
|
qualifierAnnotations,
|
||||||
@@ -251,7 +251,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
static AdapterMethod fromAdapter(Object adapter, Method method) {
|
static AdapterMethod fromAdapter(Object adapter, Method method) {
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
final Type returnType = method.getGenericReturnType();
|
final Type returnType = method.getGenericReturnType();
|
||||||
final Set<? extends Annotation> returnTypeAnnotations = jsonAnnotations(method);
|
final Set<? extends Annotation> returnTypeAnnotations = Util.getJsonAnnotations(method);
|
||||||
final Type[] parameterTypes = method.getGenericParameterTypes();
|
final Type[] parameterTypes = method.getGenericParameterTypes();
|
||||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||||
|
|
||||||
@@ -273,8 +273,8 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||||
// Point pointFromJson(List<Integer> o) {
|
// Point pointFromJson(List<Integer> o) {
|
||||||
final Set<? extends Annotation> qualifierAnnotations =
|
final Set<? extends Annotation> qualifierAnnotations =
|
||||||
jsonAnnotations(parameterAnnotations[0]);
|
getJsonAnnotations(parameterAnnotations[0]);
|
||||||
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
|
boolean nullable = Util.getHasNullable(parameterAnnotations[0]);
|
||||||
return new AdapterMethod(
|
return new AdapterMethod(
|
||||||
returnType, returnTypeAnnotations, adapter, method, parameterTypes.length, 1, nullable) {
|
returnType, returnTypeAnnotations, adapter, method, parameterTypes.length, 1, nullable) {
|
||||||
JsonAdapter<Object> delegate;
|
JsonAdapter<Object> delegate;
|
||||||
@@ -354,7 +354,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||||
for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) {
|
for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) {
|
||||||
Type type = ((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0];
|
Type type = ((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0];
|
||||||
Set<? extends Annotation> jsonAnnotations = jsonAnnotations(parameterAnnotations[i]);
|
Set<? extends Annotation> jsonAnnotations = getJsonAnnotations(parameterAnnotations[i]);
|
||||||
jsonAdapters[i - adaptersOffset] =
|
jsonAdapters[i - adaptersOffset] =
|
||||||
Types.equals(this.type, type) && annotations.equals(jsonAnnotations)
|
Types.equals(this.type, type) && annotations.equals(jsonAnnotations)
|
||||||
? moshi.nextAdapter(factory, type, jsonAnnotations)
|
? moshi.nextAdapter(factory, type, jsonAnnotations)
|
||||||
|
@@ -139,8 +139,8 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
|||||||
if (jsonAnnotation != null && jsonAnnotation.ignore()) continue;
|
if (jsonAnnotation != null && jsonAnnotation.ignore()) continue;
|
||||||
|
|
||||||
// Look up a type adapter for this type.
|
// Look up a type adapter for this type.
|
||||||
Type fieldType = resolve(type, rawType, field.getGenericType());
|
Type fieldType = resolve(field.getGenericType(), type, rawType);
|
||||||
Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
|
Set<? extends Annotation> annotations = Util.getJsonAnnotations(field);
|
||||||
String fieldName = field.getName();
|
String fieldName = field.getName();
|
||||||
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations, fieldName);
|
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations, fieldName);
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
|||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
|
||||||
// Store it using the field's name. If there was already a field with this name, fail!
|
// Store it using the field's name. If there was already a field with this name, fail!
|
||||||
String jsonName = jsonName(fieldName, jsonAnnotation);
|
String jsonName = jsonName(jsonAnnotation, fieldName);
|
||||||
FieldBinding<Object> fieldBinding = new FieldBinding<>(jsonName, field, adapter);
|
FieldBinding<Object> fieldBinding = new FieldBinding<>(jsonName, field, adapter);
|
||||||
FieldBinding<?> replaced = fieldBindings.put(jsonName, fieldBinding);
|
FieldBinding<?> replaced = fieldBindings.put(jsonName, fieldBinding);
|
||||||
if (replaced != null) {
|
if (replaced != null) {
|
||||||
|
@@ -16,12 +16,12 @@
|
|||||||
package com.squareup.moshi
|
package com.squareup.moshi
|
||||||
|
|
||||||
import com.squareup.moshi.Types.createJsonQualifierImplementation
|
import com.squareup.moshi.Types.createJsonQualifierImplementation
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.NO_ANNOTATIONS
|
||||||
import com.squareup.moshi.internal.Util.canonicalize
|
import com.squareup.moshi.internal.canonicalize
|
||||||
import com.squareup.moshi.internal.Util.isAnnotationPresent
|
import com.squareup.moshi.internal.isAnnotationPresent
|
||||||
import com.squareup.moshi.internal.Util.removeSubtypeWildcard
|
import com.squareup.moshi.internal.removeSubtypeWildcard
|
||||||
import com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations
|
import com.squareup.moshi.internal.toStringWithAnnotations
|
||||||
import com.squareup.moshi.internal.Util.typesMatch
|
import com.squareup.moshi.internal.typesMatch
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import javax.annotation.CheckReturnValue
|
import javax.annotation.CheckReturnValue
|
||||||
|
|
||||||
@@ -42,10 +42,10 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
|
|
||||||
/** Returns a JSON adapter for `type`, creating it if necessary. */
|
/** Returns a JSON adapter for `type`, creating it if necessary. */
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public fun <T> adapter(type: Type): JsonAdapter<T> = adapter(type, Util.NO_ANNOTATIONS)
|
public fun <T> adapter(type: Type): JsonAdapter<T> = adapter(type, NO_ANNOTATIONS)
|
||||||
|
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public fun <T> adapter(type: Class<T>): JsonAdapter<T> = adapter(type, Util.NO_ANNOTATIONS)
|
public fun <T> adapter(type: Class<T>): JsonAdapter<T> = adapter(type, NO_ANNOTATIONS)
|
||||||
|
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public fun <T> adapter(type: Type, annotationType: Class<out Annotation>): JsonAdapter<T> =
|
public fun <T> adapter(type: Type, annotationType: Class<out Annotation>): JsonAdapter<T> =
|
||||||
@@ -78,7 +78,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
annotations: Set<Annotation>,
|
annotations: Set<Annotation>,
|
||||||
fieldName: String?
|
fieldName: String?
|
||||||
): JsonAdapter<T> {
|
): JsonAdapter<T> {
|
||||||
val cleanedType = removeSubtypeWildcard(canonicalize(type))
|
val cleanedType = type.canonicalize().removeSubtypeWildcard()
|
||||||
|
|
||||||
// If there's an equivalent adapter in the cache, we're done!
|
// If there's an equivalent adapter in the cache, we're done!
|
||||||
val cacheKey = cacheKey(cleanedType, annotations)
|
val cacheKey = cacheKey(cleanedType, annotations)
|
||||||
@@ -107,7 +107,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
success = true
|
success = true
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("No JsonAdapter for ${typeAnnotatedWithAnnotations(type, annotations)}")
|
throw IllegalArgumentException("No JsonAdapter for ${type.toStringWithAnnotations(annotations)}")
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw lookupChain.exceptionWithLookupStack(e)
|
throw lookupChain.exceptionWithLookupStack(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -121,7 +121,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
type: Type,
|
type: Type,
|
||||||
annotations: Set<Annotation>
|
annotations: Set<Annotation>
|
||||||
): JsonAdapter<T> {
|
): JsonAdapter<T> {
|
||||||
val cleanedType = removeSubtypeWildcard(canonicalize(type))
|
val cleanedType = type.canonicalize().removeSubtypeWildcard()
|
||||||
val skipPastIndex = factories.indexOf(skipPast)
|
val skipPastIndex = factories.indexOf(skipPast)
|
||||||
require(skipPastIndex != -1) { "Unable to skip past unknown factory $skipPast" }
|
require(skipPastIndex != -1) { "Unable to skip past unknown factory $skipPast" }
|
||||||
for (i in (skipPastIndex + 1) until factories.size) {
|
for (i in (skipPastIndex + 1) until factories.size) {
|
||||||
@@ -129,7 +129,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter<T>?
|
val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter<T>?
|
||||||
if (result != null) return result
|
if (result != null) return result
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("No next JsonAdapter for ${typeAnnotatedWithAnnotations(cleanedType, annotations)}")
|
throw IllegalArgumentException("No next JsonAdapter for ${cleanedType.toStringWithAnnotations(annotations)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a new builder containing all custom factories used by the current instance. */
|
/** Returns a new builder containing all custom factories used by the current instance. */
|
||||||
@@ -354,7 +354,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
require(annotation.isAnnotationPresent(JsonQualifier::class.java)) { "$annotation does not have @JsonQualifier" }
|
require(annotation.isAnnotationPresent(JsonQualifier::class.java)) { "$annotation does not have @JsonQualifier" }
|
||||||
require(annotation.declaredMethods.isEmpty()) { "Use JsonAdapter.Factory for annotations with elements" }
|
require(annotation.declaredMethods.isEmpty()) { "Use JsonAdapter.Factory for annotations with elements" }
|
||||||
return JsonAdapter.Factory { targetType, annotations, _ ->
|
return JsonAdapter.Factory { targetType, annotations, _ ->
|
||||||
if (typesMatch(type, targetType) && annotations.size == 1 && isAnnotationPresent(annotations, annotation)) {
|
if (typesMatch(type, targetType) && annotations.size == 1 && annotations.isAnnotationPresent(annotation)) {
|
||||||
jsonAdapter
|
jsonAdapter
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@@ -275,7 +275,7 @@ final class StandardJsonAdapters {
|
|||||||
for (int i = 0; i < constants.length; i++) {
|
for (int i = 0; i < constants.length; i++) {
|
||||||
T constant = constants[i];
|
T constant = constants[i];
|
||||||
String constantName = constant.name();
|
String constantName = constant.name();
|
||||||
nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName));
|
nameStrings[i] = Util.jsonName(enumType.getField(constantName), constantName);
|
||||||
}
|
}
|
||||||
options = JsonReader.Options.of(nameStrings);
|
options = JsonReader.Options.of(nameStrings);
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
|
@@ -19,9 +19,9 @@ 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.getGenericSupertype;
|
||||||
import static com.squareup.moshi.internal.Util.resolve;
|
import static com.squareup.moshi.internal.Util.resolve;
|
||||||
|
|
||||||
import com.squareup.moshi.internal.Util.GenericArrayTypeImpl;
|
import com.squareup.moshi.internal.GenericArrayTypeImpl;
|
||||||
import com.squareup.moshi.internal.Util.ParameterizedTypeImpl;
|
import com.squareup.moshi.internal.ParameterizedTypeImpl;
|
||||||
import com.squareup.moshi.internal.Util.WildcardTypeImpl;
|
import com.squareup.moshi.internal.WildcardTypeImpl;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@@ -110,7 +110,7 @@ public final class Types {
|
|||||||
if (typeArguments.length == 0) {
|
if (typeArguments.length == 0) {
|
||||||
throw new IllegalArgumentException("Missing type arguments for " + rawType);
|
throw new IllegalArgumentException("Missing type arguments for " + rawType);
|
||||||
}
|
}
|
||||||
return new ParameterizedTypeImpl(null, rawType, typeArguments);
|
return ParameterizedTypeImpl.create(null, rawType, typeArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,12 +122,12 @@ public final class Types {
|
|||||||
if (typeArguments.length == 0) {
|
if (typeArguments.length == 0) {
|
||||||
throw new IllegalArgumentException("Missing type arguments for " + rawType);
|
throw new IllegalArgumentException("Missing type arguments for " + rawType);
|
||||||
}
|
}
|
||||||
return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
|
return ParameterizedTypeImpl.create(ownerType, rawType, typeArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an array type whose elements are all instances of {@code componentType}. */
|
/** Returns an array type whose elements are all instances of {@code componentType}. */
|
||||||
public static GenericArrayType arrayOf(Type componentType) {
|
public static GenericArrayType arrayOf(Type componentType) {
|
||||||
return new GenericArrayTypeImpl(componentType);
|
return GenericArrayTypeImpl.create(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +143,7 @@ public final class Types {
|
|||||||
} else {
|
} else {
|
||||||
upperBounds = new Type[] {bound};
|
upperBounds = new Type[] {bound};
|
||||||
}
|
}
|
||||||
return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);
|
return WildcardTypeImpl.create(upperBounds, EMPTY_TYPE_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +157,7 @@ public final class Types {
|
|||||||
} else {
|
} else {
|
||||||
lowerBounds = new Type[] {bound};
|
lowerBounds = new Type[] {bound};
|
||||||
}
|
}
|
||||||
return new WildcardTypeImpl(new Type[] {Object.class}, lowerBounds);
|
return WildcardTypeImpl.create(new Type[] {Object.class}, lowerBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Class<?> getRawType(Type type) {
|
public static Class<?> getRawType(Type type) {
|
||||||
@@ -362,12 +362,12 @@ public final class Types {
|
|||||||
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
|
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
|
||||||
if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
|
if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
|
||||||
return resolve(
|
return resolve(
|
||||||
context, contextRawType, getGenericSupertype(context, contextRawType, supertype));
|
getGenericSupertype(context, contextRawType, supertype), context, contextRawType);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Type getGenericSuperclass(Type type) {
|
static Type getGenericSuperclass(Type type) {
|
||||||
Class<?> rawType = Types.getRawType(type);
|
Class<?> rawType = Types.getRawType(type);
|
||||||
return resolve(type, rawType, rawType.getGenericSuperclass());
|
return resolve(rawType.getGenericSuperclass(), type, rawType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 Square, 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
|
|
||||||
*
|
|
||||||
* https://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.internal;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonDataException;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public final class NonNullJsonAdapter<T> extends JsonAdapter<T> {
|
|
||||||
|
|
||||||
private final JsonAdapter<T> delegate;
|
|
||||||
|
|
||||||
public NonNullJsonAdapter(JsonAdapter<T> delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonAdapter<T> delegate() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public T fromJson(JsonReader reader) throws IOException {
|
|
||||||
if (reader.peek() == JsonReader.Token.NULL) {
|
|
||||||
throw new JsonDataException("Unexpected null at " + reader.getPath());
|
|
||||||
} else {
|
|
||||||
return delegate.fromJson(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
throw new JsonDataException("Unexpected null at " + writer.getPath());
|
|
||||||
} else {
|
|
||||||
delegate.toJson(writer, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return delegate + ".nonNull()";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Square, 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
|
||||||
|
*
|
||||||
|
* https://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.internal
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.JsonReader
|
||||||
|
import com.squareup.moshi.JsonWriter
|
||||||
|
|
||||||
|
public class NonNullJsonAdapter<T>(public val delegate: JsonAdapter<T>) : JsonAdapter<T>() {
|
||||||
|
override fun fromJson(reader: JsonReader): T {
|
||||||
|
return if (reader.peek() == JsonReader.Token.NULL) {
|
||||||
|
throw JsonDataException("Unexpected null at " + reader.path)
|
||||||
|
} else {
|
||||||
|
val result = delegate.fromJson(reader)
|
||||||
|
knownNotNull(result)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(writer: JsonWriter, value: T?) {
|
||||||
|
if (value == null) {
|
||||||
|
throw JsonDataException("Unexpected null at " + writer.path)
|
||||||
|
} else {
|
||||||
|
delegate.toJson(writer, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "$delegate.nonNull()"
|
||||||
|
}
|
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 Square, 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
|
|
||||||
*
|
|
||||||
* https://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.internal;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public final class NullSafeJsonAdapter<T> extends JsonAdapter<T> {
|
|
||||||
|
|
||||||
private final JsonAdapter<T> delegate;
|
|
||||||
|
|
||||||
public NullSafeJsonAdapter(JsonAdapter<T> delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonAdapter<T> delegate() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable T fromJson(JsonReader reader) throws IOException {
|
|
||||||
if (reader.peek() == JsonReader.Token.NULL) {
|
|
||||||
return reader.nextNull();
|
|
||||||
} else {
|
|
||||||
return delegate.fromJson(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
writer.nullValue();
|
|
||||||
} else {
|
|
||||||
delegate.toJson(writer, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return delegate + ".nullSafe()";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Square, 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
|
||||||
|
*
|
||||||
|
* https://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.internal
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonReader
|
||||||
|
import com.squareup.moshi.JsonWriter
|
||||||
|
|
||||||
|
public class NullSafeJsonAdapter<T>(public val delegate: JsonAdapter<T>) : JsonAdapter<T>() {
|
||||||
|
override fun fromJson(reader: JsonReader): T? {
|
||||||
|
return if (reader.peek() == JsonReader.Token.NULL) {
|
||||||
|
reader.nextNull()
|
||||||
|
} else {
|
||||||
|
delegate.fromJson(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(writer: JsonWriter, value: T?) {
|
||||||
|
if (value == null) {
|
||||||
|
writer.nullValue()
|
||||||
|
} else {
|
||||||
|
delegate.toJson(writer, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "$delegate.nullSafe()"
|
||||||
|
}
|
@@ -1,684 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 Square, 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
|
|
||||||
*
|
|
||||||
* https://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.internal;
|
|
||||||
|
|
||||||
import static com.squareup.moshi.Types.arrayOf;
|
|
||||||
import static com.squareup.moshi.Types.subtypeOf;
|
|
||||||
import static com.squareup.moshi.Types.supertypeOf;
|
|
||||||
|
|
||||||
import com.squareup.moshi.Json;
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonClass;
|
|
||||||
import com.squareup.moshi.JsonDataException;
|
|
||||||
import com.squareup.moshi.JsonQualifier;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import com.squareup.moshi.Types;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.AnnotatedElement;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.GenericArrayType;
|
|
||||||
import java.lang.reflect.GenericDeclaration;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
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.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public final class Util {
|
|
||||||
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
|
||||||
public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
|
|
||||||
@Nullable public static final Class<?> DEFAULT_CONSTRUCTOR_MARKER;
|
|
||||||
@Nullable private static final Class<? extends Annotation> METADATA;
|
|
||||||
|
|
||||||
/** A map from primitive types to their corresponding wrapper types. */
|
|
||||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Class<? extends Annotation> metadata = null;
|
|
||||||
try {
|
|
||||||
//noinspection unchecked
|
|
||||||
metadata = (Class<? extends Annotation>) Class.forName(getKotlinMetadataClassName());
|
|
||||||
} catch (ClassNotFoundException ignored) {
|
|
||||||
}
|
|
||||||
METADATA = metadata;
|
|
||||||
|
|
||||||
// We look up the constructor marker separately because Metadata might be (justifiably)
|
|
||||||
// stripped by R8/Proguard but the DefaultConstructorMarker is still present.
|
|
||||||
Class<?> defaultConstructorMarker = null;
|
|
||||||
try {
|
|
||||||
defaultConstructorMarker = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker");
|
|
||||||
} catch (ClassNotFoundException ignored) {
|
|
||||||
}
|
|
||||||
DEFAULT_CONSTRUCTOR_MARKER = defaultConstructorMarker;
|
|
||||||
|
|
||||||
Map<Class<?>, Class<?>> primToWrap = new LinkedHashMap<>(16);
|
|
||||||
|
|
||||||
primToWrap.put(boolean.class, Boolean.class);
|
|
||||||
primToWrap.put(byte.class, Byte.class);
|
|
||||||
primToWrap.put(char.class, Character.class);
|
|
||||||
primToWrap.put(double.class, Double.class);
|
|
||||||
primToWrap.put(float.class, Float.class);
|
|
||||||
primToWrap.put(int.class, Integer.class);
|
|
||||||
primToWrap.put(long.class, Long.class);
|
|
||||||
primToWrap.put(short.class, Short.class);
|
|
||||||
primToWrap.put(void.class, Void.class);
|
|
||||||
|
|
||||||
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metada
|
|
||||||
private static String getKotlinMetadataClassName() {
|
|
||||||
return "kotlin.Metadata";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Util() {}
|
|
||||||
|
|
||||||
public static String jsonName(String declaredName, AnnotatedElement element) {
|
|
||||||
return jsonName(declaredName, element.getAnnotation(Json.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String jsonName(String declaredName, @Nullable Json annotation) {
|
|
||||||
if (annotation == null) return declaredName;
|
|
||||||
String annotationName = annotation.name();
|
|
||||||
return Json.UNSET_NAME.equals(annotationName) ? declaredName : annotationName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean typesMatch(Type pattern, Type candidate) {
|
|
||||||
// TODO: permit raw types (like Set.class) to match non-raw candidates (like Set<Long>).
|
|
||||||
return Types.equals(pattern, candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<? extends Annotation> jsonAnnotations(AnnotatedElement annotatedElement) {
|
|
||||||
return jsonAnnotations(annotatedElement.getAnnotations());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<? extends Annotation> jsonAnnotations(Annotation[] annotations) {
|
|
||||||
Set<Annotation> result = null;
|
|
||||||
for (Annotation annotation : annotations) {
|
|
||||||
if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) {
|
|
||||||
if (result == null) result = new LinkedHashSet<>();
|
|
||||||
result.add(annotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result != null ? Collections.unmodifiableSet(result) : Util.NO_ANNOTATIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isAnnotationPresent(
|
|
||||||
Set<? extends Annotation> annotations, Class<? extends Annotation> annotationClass) {
|
|
||||||
if (annotations.isEmpty()) return false; // Save an iterator in the common case.
|
|
||||||
for (Annotation annotation : annotations) {
|
|
||||||
if (annotation.annotationType() == annotationClass) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if {@code annotations} has any annotation whose simple name is Nullable. */
|
|
||||||
public static boolean hasNullable(Annotation[] annotations) {
|
|
||||||
for (Annotation annotation : annotations) {
|
|
||||||
if (annotation.annotationType().getSimpleName().equals("Nullable")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if {@code rawType} is built in. We don't reflect on private fields of platform
|
|
||||||
* types because they're unspecified and likely to be different on Java vs. Android.
|
|
||||||
*/
|
|
||||||
public static boolean isPlatformType(Class<?> rawType) {
|
|
||||||
String name = rawType.getName();
|
|
||||||
return name.startsWith("android.")
|
|
||||||
|| name.startsWith("androidx.")
|
|
||||||
|| name.startsWith("java.")
|
|
||||||
|| name.startsWith("javax.")
|
|
||||||
|| name.startsWith("kotlin.")
|
|
||||||
|| name.startsWith("kotlinx.")
|
|
||||||
|| name.startsWith("scala.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throws the cause of {@code e}, wrapping it if it is checked. */
|
|
||||||
public static RuntimeException rethrowCause(InvocationTargetException e) {
|
|
||||||
Throwable cause = e.getTargetException();
|
|
||||||
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
|
|
||||||
if (cause instanceof Error) throw (Error) cause;
|
|
||||||
throw new RuntimeException(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a type that is functionally equal but not necessarily equal according to {@link
|
|
||||||
* Object#equals(Object) Object.equals()}.
|
|
||||||
*/
|
|
||||||
public static Type canonicalize(Type type) {
|
|
||||||
if (type instanceof Class) {
|
|
||||||
Class<?> c = (Class<?>) type;
|
|
||||||
return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
|
|
||||||
|
|
||||||
} else if (type instanceof ParameterizedType) {
|
|
||||||
if (type instanceof ParameterizedTypeImpl) return type;
|
|
||||||
ParameterizedType p = (ParameterizedType) type;
|
|
||||||
return new ParameterizedTypeImpl(
|
|
||||||
p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());
|
|
||||||
|
|
||||||
} else if (type instanceof GenericArrayType) {
|
|
||||||
if (type instanceof GenericArrayTypeImpl) return type;
|
|
||||||
GenericArrayType g = (GenericArrayType) type;
|
|
||||||
return new GenericArrayTypeImpl(g.getGenericComponentType());
|
|
||||||
|
|
||||||
} else if (type instanceof WildcardType) {
|
|
||||||
if (type instanceof WildcardTypeImpl) return type;
|
|
||||||
WildcardType w = (WildcardType) type;
|
|
||||||
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return type; // This type is unsupported!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */
|
|
||||||
public static Type removeSubtypeWildcard(Type type) {
|
|
||||||
if (!(type instanceof WildcardType)) return type;
|
|
||||||
|
|
||||||
Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
|
|
||||||
if (lowerBounds.length != 0) return type;
|
|
||||||
|
|
||||||
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
|
|
||||||
if (upperBounds.length != 1) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
return upperBounds[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
|
||||||
return resolve(context, contextRawType, toResolve, new LinkedHashSet<TypeVariable<?>>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type resolve(
|
|
||||||
Type context,
|
|
||||||
Class<?> contextRawType,
|
|
||||||
Type toResolve,
|
|
||||||
Collection<TypeVariable<?>> visitedTypeVariables) {
|
|
||||||
// This implementation is made a little more complicated in an attempt to avoid object-creation.
|
|
||||||
while (true) {
|
|
||||||
if (toResolve instanceof TypeVariable) {
|
|
||||||
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
|
|
||||||
if (visitedTypeVariables.contains(typeVariable)) {
|
|
||||||
// cannot reduce due to infinite recursion
|
|
||||||
return toResolve;
|
|
||||||
} else {
|
|
||||||
visitedTypeVariables.add(typeVariable);
|
|
||||||
}
|
|
||||||
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
|
|
||||||
if (toResolve == typeVariable) return toResolve;
|
|
||||||
|
|
||||||
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
|
||||||
Class<?> original = (Class<?>) toResolve;
|
|
||||||
Type componentType = original.getComponentType();
|
|
||||||
Type newComponentType =
|
|
||||||
resolve(context, contextRawType, componentType, visitedTypeVariables);
|
|
||||||
return componentType == newComponentType ? original : arrayOf(newComponentType);
|
|
||||||
|
|
||||||
} else if (toResolve instanceof GenericArrayType) {
|
|
||||||
GenericArrayType original = (GenericArrayType) toResolve;
|
|
||||||
Type componentType = original.getGenericComponentType();
|
|
||||||
Type newComponentType =
|
|
||||||
resolve(context, contextRawType, componentType, visitedTypeVariables);
|
|
||||||
return componentType == newComponentType ? original : arrayOf(newComponentType);
|
|
||||||
|
|
||||||
} else if (toResolve instanceof ParameterizedType) {
|
|
||||||
ParameterizedType original = (ParameterizedType) toResolve;
|
|
||||||
Type ownerType = original.getOwnerType();
|
|
||||||
Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
|
|
||||||
boolean changed = newOwnerType != ownerType;
|
|
||||||
|
|
||||||
Type[] args = original.getActualTypeArguments();
|
|
||||||
for (int t = 0, length = args.length; t < length; t++) {
|
|
||||||
Type resolvedTypeArgument =
|
|
||||||
resolve(context, contextRawType, args[t], visitedTypeVariables);
|
|
||||||
if (resolvedTypeArgument != args[t]) {
|
|
||||||
if (!changed) {
|
|
||||||
args = args.clone();
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
args[t] = resolvedTypeArgument;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed
|
|
||||||
? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args)
|
|
||||||
: original;
|
|
||||||
|
|
||||||
} else if (toResolve instanceof WildcardType) {
|
|
||||||
WildcardType original = (WildcardType) toResolve;
|
|
||||||
Type[] originalLowerBound = original.getLowerBounds();
|
|
||||||
Type[] originalUpperBound = original.getUpperBounds();
|
|
||||||
|
|
||||||
if (originalLowerBound.length == 1) {
|
|
||||||
Type lowerBound =
|
|
||||||
resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
|
||||||
if (lowerBound != originalLowerBound[0]) {
|
|
||||||
return supertypeOf(lowerBound);
|
|
||||||
}
|
|
||||||
} else if (originalUpperBound.length == 1) {
|
|
||||||
Type upperBound =
|
|
||||||
resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
|
||||||
if (upperBound != originalUpperBound[0]) {
|
|
||||||
return subtypeOf(upperBound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return original;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return toResolve;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
|
||||||
Class<?> declaredByRaw = declaringClassOf(unknown);
|
|
||||||
|
|
||||||
// We can't reduce this further.
|
|
||||||
if (declaredByRaw == null) return unknown;
|
|
||||||
|
|
||||||
Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
|
|
||||||
if (declaredBy instanceof ParameterizedType) {
|
|
||||||
int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
|
|
||||||
return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the generic supertype for {@code supertype}. For example, given a class {@code
|
|
||||||
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
|
|
||||||
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
|
|
||||||
*/
|
|
||||||
public static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
|
|
||||||
if (toResolve == rawType) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we skip searching through interfaces if unknown is an interface
|
|
||||||
if (toResolve.isInterface()) {
|
|
||||||
Class<?>[] interfaces = rawType.getInterfaces();
|
|
||||||
for (int i = 0, length = interfaces.length; i < length; i++) {
|
|
||||||
if (interfaces[i] == toResolve) {
|
|
||||||
return rawType.getGenericInterfaces()[i];
|
|
||||||
} else if (toResolve.isAssignableFrom(interfaces[i])) {
|
|
||||||
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check our supertypes
|
|
||||||
if (!rawType.isInterface()) {
|
|
||||||
while (rawType != Object.class) {
|
|
||||||
Class<?> rawSupertype = rawType.getSuperclass();
|
|
||||||
if (rawSupertype == toResolve) {
|
|
||||||
return rawType.getGenericSuperclass();
|
|
||||||
} else if (toResolve.isAssignableFrom(rawSupertype)) {
|
|
||||||
return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
|
|
||||||
}
|
|
||||||
rawType = rawSupertype;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can't resolve this further
|
|
||||||
return toResolve;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int hashCodeOrZero(@Nullable Object o) {
|
|
||||||
return o != null ? o.hashCode() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String typeToString(Type type) {
|
|
||||||
return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int indexOf(Object[] array, Object toFind) {
|
|
||||||
for (int i = 0; i < array.length; i++) {
|
|
||||||
if (toFind.equals(array[i])) return i;
|
|
||||||
}
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
|
|
||||||
* a class.
|
|
||||||
*/
|
|
||||||
static @Nullable Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
|
|
||||||
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
|
|
||||||
return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void checkNotPrimitive(Type type) {
|
|
||||||
if ((type instanceof Class<?>) && ((Class<?>) type).isPrimitive()) {
|
|
||||||
throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class ParameterizedTypeImpl implements ParameterizedType {
|
|
||||||
private final @Nullable Type ownerType;
|
|
||||||
private final Type rawType;
|
|
||||||
public final Type[] typeArguments;
|
|
||||||
|
|
||||||
public ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) {
|
|
||||||
// Require an owner type if the raw type needs it.
|
|
||||||
if (rawType instanceof Class<?>) {
|
|
||||||
Class<?> enclosingClass = ((Class<?>) rawType).getEnclosingClass();
|
|
||||||
if (ownerType != null) {
|
|
||||||
if (enclosingClass == null || Types.getRawType(ownerType) != enclosingClass) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"unexpected owner type for " + rawType + ": " + ownerType);
|
|
||||||
}
|
|
||||||
} else if (enclosingClass != null) {
|
|
||||||
throw new IllegalArgumentException("unexpected owner type for " + rawType + ": null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ownerType = ownerType == null ? null : canonicalize(ownerType);
|
|
||||||
this.rawType = canonicalize(rawType);
|
|
||||||
this.typeArguments = typeArguments.clone();
|
|
||||||
for (int t = 0; t < this.typeArguments.length; t++) {
|
|
||||||
if (this.typeArguments[t] == null) throw new NullPointerException();
|
|
||||||
checkNotPrimitive(this.typeArguments[t]);
|
|
||||||
this.typeArguments[t] = canonicalize(this.typeArguments[t]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type[] getActualTypeArguments() {
|
|
||||||
return typeArguments.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getRawType() {
|
|
||||||
return rawType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Type getOwnerType() {
|
|
||||||
return ownerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other instanceof ParameterizedType && Types.equals(this, (ParameterizedType) other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1));
|
|
||||||
result.append(typeToString(rawType));
|
|
||||||
|
|
||||||
if (typeArguments.length == 0) {
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append("<").append(typeToString(typeArguments[0]));
|
|
||||||
for (int i = 1; i < typeArguments.length; i++) {
|
|
||||||
result.append(", ").append(typeToString(typeArguments[i]));
|
|
||||||
}
|
|
||||||
return result.append(">").toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class GenericArrayTypeImpl implements GenericArrayType {
|
|
||||||
private final Type componentType;
|
|
||||||
|
|
||||||
public GenericArrayTypeImpl(Type componentType) {
|
|
||||||
this.componentType = canonicalize(componentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getGenericComponentType() {
|
|
||||||
return componentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return o instanceof GenericArrayType && Types.equals(this, (GenericArrayType) o);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return componentType.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return typeToString(componentType) + "[]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
|
|
||||||
* support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper
|
|
||||||
* bound must be Object.class.
|
|
||||||
*/
|
|
||||||
public static final class WildcardTypeImpl implements WildcardType {
|
|
||||||
private final Type upperBound;
|
|
||||||
private final @Nullable Type lowerBound;
|
|
||||||
|
|
||||||
public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
|
|
||||||
if (lowerBounds.length > 1) throw new IllegalArgumentException();
|
|
||||||
if (upperBounds.length != 1) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
if (lowerBounds.length == 1) {
|
|
||||||
if (lowerBounds[0] == null) throw new NullPointerException();
|
|
||||||
checkNotPrimitive(lowerBounds[0]);
|
|
||||||
if (upperBounds[0] != Object.class) throw new IllegalArgumentException();
|
|
||||||
this.lowerBound = canonicalize(lowerBounds[0]);
|
|
||||||
this.upperBound = Object.class;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (upperBounds[0] == null) throw new NullPointerException();
|
|
||||||
checkNotPrimitive(upperBounds[0]);
|
|
||||||
this.lowerBound = null;
|
|
||||||
this.upperBound = canonicalize(upperBounds[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type[] getUpperBounds() {
|
|
||||||
return new Type[] {upperBound};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type[] getLowerBounds() {
|
|
||||||
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other instanceof WildcardType && Types.equals(this, (WildcardType) other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
// This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()).
|
|
||||||
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (lowerBound != null) {
|
|
||||||
return "? super " + typeToString(lowerBound);
|
|
||||||
} else if (upperBound == Object.class) {
|
|
||||||
return "?";
|
|
||||||
} else {
|
|
||||||
return "? extends " + typeToString(upperBound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String typeAnnotatedWithAnnotations(
|
|
||||||
Type type, Set<? extends Annotation> annotations) {
|
|
||||||
return type + (annotations.isEmpty() ? " (with no annotations)" : " annotated " + annotations);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the generated JsonAdapter for classes annotated {@link JsonClass}. This works because it
|
|
||||||
* uses the same naming conventions as {@code JsonClassCodeGenProcessor}.
|
|
||||||
*/
|
|
||||||
public static @Nullable JsonAdapter<?> generatedAdapter(
|
|
||||||
Moshi moshi, Type type, Class<?> rawType) {
|
|
||||||
JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
|
|
||||||
if (jsonClass == null || !jsonClass.generateAdapter()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String adapterClassName = Types.generatedJsonAdapterName(rawType.getName());
|
|
||||||
Class<? extends JsonAdapter<?>> adapterClass = null;
|
|
||||||
try {
|
|
||||||
//noinspection unchecked - We generate types to match.
|
|
||||||
adapterClass =
|
|
||||||
(Class<? extends JsonAdapter<?>>)
|
|
||||||
Class.forName(adapterClassName, true, rawType.getClassLoader());
|
|
||||||
Constructor<? extends JsonAdapter<?>> constructor;
|
|
||||||
Object[] args;
|
|
||||||
if (type instanceof ParameterizedType) {
|
|
||||||
Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
|
|
||||||
try {
|
|
||||||
// Common case first
|
|
||||||
constructor = adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
|
|
||||||
args = new Object[] {moshi, typeArgs};
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
constructor = adapterClass.getDeclaredConstructor(Type[].class);
|
|
||||||
args = new Object[] {typeArgs};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// Common case first
|
|
||||||
constructor = adapterClass.getDeclaredConstructor(Moshi.class);
|
|
||||||
args = new Object[] {moshi};
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
constructor = adapterClass.getDeclaredConstructor();
|
|
||||||
args = new Object[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
return constructor.newInstance(args).nullSafe();
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new RuntimeException("Failed to find the generated JsonAdapter class for " + type, e);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
if (!(type instanceof ParameterizedType) && adapterClass.getTypeParameters().length != 0) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Failed to find the generated JsonAdapter constructor for '"
|
|
||||||
+ type
|
|
||||||
+ "'. Suspiciously, the type was not parameterized but the target class '"
|
|
||||||
+ adapterClass.getCanonicalName()
|
|
||||||
+ "' is generic. Consider using "
|
|
||||||
+ "Types#newParameterizedType() to define these missing type variables.",
|
|
||||||
e);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Failed to find the generated JsonAdapter constructor for " + type, e);
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException("Failed to access the generated JsonAdapter for " + type, e);
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
throw new RuntimeException("Failed to instantiate the generated JsonAdapter for " + type, e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw rethrowCause(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isKotlin(Class<?> targetClass) {
|
|
||||||
return METADATA != null && targetClass.isAnnotationPresent(METADATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reflectively looks up the defaults constructor of a kotlin class.
|
|
||||||
*
|
|
||||||
* @param targetClass the target kotlin class to instantiate.
|
|
||||||
* @param <T> the type of {@code targetClass}.
|
|
||||||
* @return the instantiated {@code targetClass} instance.
|
|
||||||
*/
|
|
||||||
public static <T> Constructor<T> lookupDefaultsConstructor(Class<T> targetClass) {
|
|
||||||
if (DEFAULT_CONSTRUCTOR_MARKER == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"DefaultConstructorMarker not on classpath. Make sure the "
|
|
||||||
+ "Kotlin stdlib is on the classpath.");
|
|
||||||
}
|
|
||||||
Constructor<T> defaultConstructor = findConstructor(targetClass);
|
|
||||||
defaultConstructor.setAccessible(true);
|
|
||||||
return defaultConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Constructor<T> findConstructor(Class<T> targetClass) {
|
|
||||||
for (Constructor<?> constructor : targetClass.getDeclaredConstructors()) {
|
|
||||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
|
||||||
if (paramTypes.length != 0
|
|
||||||
&& paramTypes[paramTypes.length - 1].equals(DEFAULT_CONSTRUCTOR_MARKER)) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (Constructor<T>) constructor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("No defaults constructor found for " + targetClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonDataException missingProperty(
|
|
||||||
String propertyName, String jsonName, JsonReader reader) {
|
|
||||||
String path = reader.getPath();
|
|
||||||
String message;
|
|
||||||
if (jsonName.equals(propertyName)) {
|
|
||||||
message = String.format("Required value '%s' missing at %s", propertyName, path);
|
|
||||||
} else {
|
|
||||||
message =
|
|
||||||
String.format(
|
|
||||||
"Required value '%s' (JSON name '%s') missing at %s", propertyName, jsonName, path);
|
|
||||||
}
|
|
||||||
return new JsonDataException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonDataException unexpectedNull(
|
|
||||||
String propertyName, String jsonName, JsonReader reader) {
|
|
||||||
String path = reader.getPath();
|
|
||||||
String message;
|
|
||||||
if (jsonName.equals(propertyName)) {
|
|
||||||
message = String.format("Non-null value '%s' was null at %s", propertyName, path);
|
|
||||||
} else {
|
|
||||||
message =
|
|
||||||
String.format(
|
|
||||||
"Non-null value '%s' (JSON name '%s') was null at %s", propertyName, jsonName, path);
|
|
||||||
}
|
|
||||||
return new JsonDataException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public due to inline access in MoshiKotlinTypesExtensions
|
|
||||||
public static <T> Class<T> boxIfPrimitive(Class<T> type) {
|
|
||||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE.get(type);
|
|
||||||
return (wrapped == null) ? type : wrapped;
|
|
||||||
}
|
|
||||||
}
|
|
632
moshi/src/main/java/com/squareup/moshi/internal/Util.kt
Normal file
632
moshi/src/main/java/com/squareup/moshi/internal/Util.kt
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, 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
|
||||||
|
*
|
||||||
|
* https://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.
|
||||||
|
*/
|
||||||
|
@file:JvmName("Util")
|
||||||
|
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
|
package com.squareup.moshi.internal
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.JsonQualifier
|
||||||
|
import com.squareup.moshi.JsonReader
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import com.squareup.moshi.asArrayType
|
||||||
|
import com.squareup.moshi.rawType
|
||||||
|
import java.lang.ClassNotFoundException
|
||||||
|
import java.lang.Error
|
||||||
|
import java.lang.IllegalAccessException
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
import java.lang.InstantiationException
|
||||||
|
import java.lang.NoSuchMethodException
|
||||||
|
import java.lang.RuntimeException
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
import java.lang.Void
|
||||||
|
import java.lang.reflect.AnnotatedElement
|
||||||
|
import java.lang.reflect.Constructor
|
||||||
|
import java.lang.reflect.GenericArrayType
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.lang.reflect.TypeVariable
|
||||||
|
import java.lang.reflect.WildcardType
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.LinkedHashSet
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
@JvmField public val NO_ANNOTATIONS: Set<Annotation> = emptySet()
|
||||||
|
@JvmField public val EMPTY_TYPE_ARRAY: Array<Type> = arrayOf()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private val METADATA: Class<out Annotation>? = try {
|
||||||
|
Class.forName(kotlinMetadataClassName) as Class<out Annotation>
|
||||||
|
} catch (ignored: ClassNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
// We look up the constructor marker separately because Metadata might be (justifiably)
|
||||||
|
// stripped by R8/Proguard but the DefaultConstructorMarker is still present.
|
||||||
|
public val DEFAULT_CONSTRUCTOR_MARKER: Class<*>? = try {
|
||||||
|
Class.forName("kotlin.jvm.internal.DefaultConstructorMarker")
|
||||||
|
} catch (ignored: ClassNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A map from primitive types to their corresponding wrapper types. */
|
||||||
|
private val PRIMITIVE_TO_WRAPPER_TYPE: Map<Class<*>, Class<*>> = buildMap(16) {
|
||||||
|
put(Boolean::class.javaPrimitiveType!!, Boolean::class.java)
|
||||||
|
put(Byte::class.javaPrimitiveType!!, Byte::class.java)
|
||||||
|
put(Char::class.javaPrimitiveType!!, Char::class.java)
|
||||||
|
put(Double::class.javaPrimitiveType!!, Double::class.java)
|
||||||
|
put(Float::class.javaPrimitiveType!!, Float::class.java)
|
||||||
|
put(Int::class.javaPrimitiveType!!, Int::class.java)
|
||||||
|
put(Long::class.javaPrimitiveType!!, Long::class.java)
|
||||||
|
put(Short::class.javaPrimitiveType!!, Short::class.java)
|
||||||
|
put(Void.TYPE, Void::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metadata
|
||||||
|
private val kotlinMetadataClassName: String
|
||||||
|
get() = "kotlin.Metadata"
|
||||||
|
|
||||||
|
public fun AnnotatedElement.jsonName(declaredName: String): String {
|
||||||
|
return getAnnotation(Json::class.java).jsonName(declaredName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Json?.jsonName(declaredName: String): String {
|
||||||
|
if (this == null) return declaredName
|
||||||
|
val annotationName: String = name
|
||||||
|
return if (Json.UNSET_NAME == annotationName) declaredName else annotationName
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun typesMatch(pattern: Type, candidate: Type): Boolean {
|
||||||
|
// TODO: permit raw types (like Set.class) to match non-raw candidates (like Set<Long>).
|
||||||
|
return Types.equals(pattern, candidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
public val AnnotatedElement.jsonAnnotations: Set<Annotation>
|
||||||
|
get() = annotations.jsonAnnotations
|
||||||
|
|
||||||
|
public val Array<Annotation>.jsonAnnotations: Set<Annotation>
|
||||||
|
get() {
|
||||||
|
var result: MutableSet<Annotation>? = null
|
||||||
|
for (annotation in this) {
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
if ((annotation as java.lang.annotation.Annotation).annotationType()
|
||||||
|
.isAnnotationPresent(JsonQualifier::class.java)
|
||||||
|
) {
|
||||||
|
if (result == null) result = LinkedHashSet()
|
||||||
|
result.add(annotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Set<Annotation>.isAnnotationPresent(
|
||||||
|
annotationClass: Class<out Annotation>
|
||||||
|
): Boolean {
|
||||||
|
if (isEmpty()) return false // Save an iterator in the common case.
|
||||||
|
for (annotation in this) {
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
if ((annotation as java.lang.annotation.Annotation).annotationType() == annotationClass) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if `annotations` has any annotation whose simple name is Nullable. */
|
||||||
|
public val Array<Annotation>.hasNullable: Boolean
|
||||||
|
get() {
|
||||||
|
for (annotation in this) {
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if `rawType` is built in. We don't reflect on private fields of platform
|
||||||
|
* types because they're unspecified and likely to be different on Java vs. Android.
|
||||||
|
*/
|
||||||
|
public val Class<*>.isPlatformType: Boolean
|
||||||
|
get() {
|
||||||
|
val name = name
|
||||||
|
return (
|
||||||
|
name.startsWith("android.") ||
|
||||||
|
name.startsWith("androidx.") ||
|
||||||
|
name.startsWith("java.") ||
|
||||||
|
name.startsWith("javax.") ||
|
||||||
|
name.startsWith("kotlin.") ||
|
||||||
|
name.startsWith("kotlinx.") ||
|
||||||
|
name.startsWith("scala.")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Throws the cause of `e`, wrapping it if it is checked. */
|
||||||
|
public fun InvocationTargetException.rethrowCause(): RuntimeException {
|
||||||
|
val cause = targetException
|
||||||
|
if (cause is RuntimeException) throw cause
|
||||||
|
if (cause is Error) throw cause
|
||||||
|
throw RuntimeException(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a type that is functionally equal but not necessarily equal according to [[Object.equals()]][Object.equals].
|
||||||
|
*/
|
||||||
|
public fun Type.canonicalize(): Type {
|
||||||
|
return when (this) {
|
||||||
|
is Class<*> -> {
|
||||||
|
if (isArray) GenericArrayTypeImpl(this@canonicalize.componentType.canonicalize()) else this
|
||||||
|
}
|
||||||
|
is ParameterizedType -> {
|
||||||
|
if (this is ParameterizedTypeImpl) return this
|
||||||
|
ParameterizedTypeImpl(ownerType, rawType, *actualTypeArguments)
|
||||||
|
}
|
||||||
|
is GenericArrayType -> {
|
||||||
|
if (this is GenericArrayTypeImpl) return this
|
||||||
|
GenericArrayTypeImpl(genericComponentType)
|
||||||
|
}
|
||||||
|
is WildcardType -> {
|
||||||
|
if (this is WildcardTypeImpl) return this
|
||||||
|
WildcardTypeImpl(upperBounds, lowerBounds)
|
||||||
|
}
|
||||||
|
else -> this // This type is unsupported!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */
|
||||||
|
public fun Type.removeSubtypeWildcard(): Type {
|
||||||
|
if (this !is WildcardType) return this
|
||||||
|
val lowerBounds = lowerBounds
|
||||||
|
if (lowerBounds.isNotEmpty()) return this
|
||||||
|
val upperBounds = upperBounds
|
||||||
|
require(upperBounds.size == 1)
|
||||||
|
return upperBounds[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Type.resolve(context: Type, contextRawType: Class<*>): Type {
|
||||||
|
return this.resolve(context, contextRawType, LinkedHashSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Type.resolve(
|
||||||
|
context: Type,
|
||||||
|
contextRawType: Class<*>,
|
||||||
|
visitedTypeVariables: MutableCollection<TypeVariable<*>>
|
||||||
|
): Type {
|
||||||
|
// This implementation is made a little more complicated in an attempt to avoid object-creation.
|
||||||
|
var toResolve = this
|
||||||
|
while (true) {
|
||||||
|
when {
|
||||||
|
toResolve is TypeVariable<*> -> {
|
||||||
|
val typeVariable = toResolve
|
||||||
|
if (typeVariable in visitedTypeVariables) {
|
||||||
|
// cannot reduce due to infinite recursion
|
||||||
|
return toResolve
|
||||||
|
} else {
|
||||||
|
visitedTypeVariables += typeVariable
|
||||||
|
}
|
||||||
|
toResolve = resolveTypeVariable(context, contextRawType, typeVariable)
|
||||||
|
if (toResolve === typeVariable) return toResolve
|
||||||
|
}
|
||||||
|
toResolve is Class<*> && toResolve.isArray -> {
|
||||||
|
val original = toResolve
|
||||||
|
val componentType: Type = original.componentType
|
||||||
|
val newComponentType = componentType.resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
return if (componentType === newComponentType) original else newComponentType.asArrayType()
|
||||||
|
}
|
||||||
|
toResolve is GenericArrayType -> {
|
||||||
|
val original = toResolve
|
||||||
|
val componentType = original.genericComponentType
|
||||||
|
val newComponentType = componentType.resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
return if (componentType === newComponentType) original else newComponentType.asArrayType()
|
||||||
|
}
|
||||||
|
toResolve is ParameterizedType -> {
|
||||||
|
val original = toResolve
|
||||||
|
val ownerType: Type? = original.ownerType
|
||||||
|
val newOwnerType = ownerType?.let {
|
||||||
|
ownerType.resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
}
|
||||||
|
var changed = newOwnerType !== ownerType
|
||||||
|
var args = original.actualTypeArguments
|
||||||
|
for (t in args.indices) {
|
||||||
|
val resolvedTypeArgument = args[t].resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
if (resolvedTypeArgument !== args[t]) {
|
||||||
|
if (!changed) {
|
||||||
|
args = args.clone()
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
args[t] = resolvedTypeArgument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (changed) ParameterizedTypeImpl(newOwnerType, original.rawType, *args) else original
|
||||||
|
}
|
||||||
|
toResolve is WildcardType -> {
|
||||||
|
val original = toResolve
|
||||||
|
val originalLowerBound = original.lowerBounds
|
||||||
|
val originalUpperBound = original.upperBounds
|
||||||
|
if (originalLowerBound.size == 1) {
|
||||||
|
val lowerBound = originalLowerBound[0].resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
if (lowerBound !== originalLowerBound[0]) {
|
||||||
|
return Types.supertypeOf(lowerBound)
|
||||||
|
}
|
||||||
|
} else if (originalUpperBound.size == 1) {
|
||||||
|
val upperBound = originalUpperBound[0].resolve(context, contextRawType, visitedTypeVariables)
|
||||||
|
if (upperBound !== originalUpperBound[0]) {
|
||||||
|
return Types.subtypeOf(upperBound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return original
|
||||||
|
}
|
||||||
|
else -> return toResolve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun resolveTypeVariable(context: Type, contextRawType: Class<*>, unknown: TypeVariable<*>): Type {
|
||||||
|
val declaredByRaw = declaringClassOf(unknown) ?: return unknown
|
||||||
|
|
||||||
|
// We can't reduce this further.
|
||||||
|
val declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw)
|
||||||
|
if (declaredBy is ParameterizedType) {
|
||||||
|
val index = declaredByRaw.typeParameters.indexOf(unknown)
|
||||||
|
return declaredBy.actualTypeArguments[index]
|
||||||
|
}
|
||||||
|
return unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the generic supertype for `supertype`. For example, given a class `IntegerSet`, the result for when supertype is `Set.class` is `Set<Integer>` and the
|
||||||
|
* result when the supertype is `Collection.class` is `Collection<Integer>`.
|
||||||
|
*/
|
||||||
|
public fun getGenericSupertype(context: Type, rawTypeInitial: Class<*>, toResolve: Class<*>): Type {
|
||||||
|
var rawType = rawTypeInitial
|
||||||
|
if (toResolve == rawType) {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// we skip searching through interfaces if unknown is an interface
|
||||||
|
if (toResolve.isInterface) {
|
||||||
|
val interfaces = rawType.interfaces
|
||||||
|
for (i in interfaces.indices) {
|
||||||
|
if (interfaces[i] == toResolve) {
|
||||||
|
return rawType.genericInterfaces[i]
|
||||||
|
} else if (toResolve.isAssignableFrom(interfaces[i])) {
|
||||||
|
return getGenericSupertype(rawType.genericInterfaces[i], interfaces[i], toResolve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check our supertypes
|
||||||
|
if (!rawType.isInterface) {
|
||||||
|
while (rawType != Any::class.java) {
|
||||||
|
val rawSupertype = rawType.superclass
|
||||||
|
if (rawSupertype == toResolve) {
|
||||||
|
return rawType.genericSuperclass
|
||||||
|
} else if (toResolve.isAssignableFrom(rawSupertype)) {
|
||||||
|
return getGenericSupertype(rawType.genericSuperclass, rawSupertype, toResolve)
|
||||||
|
}
|
||||||
|
rawType = rawSupertype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't resolve this further
|
||||||
|
return toResolve
|
||||||
|
}
|
||||||
|
|
||||||
|
public val Any?.hashCodeOrZero: Int
|
||||||
|
get() {
|
||||||
|
return this?.hashCode() ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Type.typeToString(): String {
|
||||||
|
return if (this is Class<*>) name else toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the declaring class of `typeVariable`, or `null` if it was not declared by
|
||||||
|
* a class.
|
||||||
|
*/
|
||||||
|
public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? {
|
||||||
|
val genericDeclaration = typeVariable.genericDeclaration
|
||||||
|
return if (genericDeclaration is Class<*>) genericDeclaration else null
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Type.checkNotPrimitive() {
|
||||||
|
require(!(this is Class<*> && isPrimitive)) { "Unexpected primitive $this. Use the boxed type." }
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Type.toStringWithAnnotations(annotations: Set<Annotation>): String {
|
||||||
|
return toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the generated JsonAdapter for classes annotated [JsonClass]. This works because it
|
||||||
|
* uses the same naming conventions as `JsonClassCodeGenProcessor`.
|
||||||
|
*/
|
||||||
|
public fun Moshi.generatedAdapter(
|
||||||
|
type: Type,
|
||||||
|
rawType: Class<*>
|
||||||
|
): JsonAdapter<*>? {
|
||||||
|
val jsonClass = rawType.getAnnotation(JsonClass::class.java)
|
||||||
|
if (jsonClass == null || !jsonClass.generateAdapter) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val adapterClassName = Types.generatedJsonAdapterName(rawType.name)
|
||||||
|
var possiblyFoundAdapter: Class<out JsonAdapter<*>>? = null
|
||||||
|
return try {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val adapterClass = Class.forName(adapterClassName, true, rawType.classLoader) as Class<out JsonAdapter<*>>
|
||||||
|
possiblyFoundAdapter = adapterClass
|
||||||
|
var constructor: Constructor<out JsonAdapter<*>>
|
||||||
|
var args: Array<Any>
|
||||||
|
if (type is ParameterizedType) {
|
||||||
|
val typeArgs = type.actualTypeArguments
|
||||||
|
try {
|
||||||
|
// Common case first
|
||||||
|
constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array<Type>::class.java)
|
||||||
|
args = arrayOf(this, typeArgs)
|
||||||
|
} catch (e: NoSuchMethodException) {
|
||||||
|
constructor = adapterClass.getDeclaredConstructor(Array<Type>::class.java)
|
||||||
|
args = arrayOf(typeArgs)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Common case first
|
||||||
|
constructor = adapterClass.getDeclaredConstructor(Moshi::class.java)
|
||||||
|
args = arrayOf(this)
|
||||||
|
} catch (e: NoSuchMethodException) {
|
||||||
|
constructor = adapterClass.getDeclaredConstructor()
|
||||||
|
args = emptyArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constructor.isAccessible = true
|
||||||
|
constructor.newInstance(*args).nullSafe()
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
throw RuntimeException("Failed to find the generated JsonAdapter class for $type", e)
|
||||||
|
} catch (e: NoSuchMethodException) {
|
||||||
|
if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) {
|
||||||
|
throw RuntimeException(
|
||||||
|
"Failed to find the generated JsonAdapter constructor for '$type'. Suspiciously, the type was not parameterized but the target class '${possiblyFoundAdapter.canonicalName}' is generic. Consider using Types#newParameterizedType() to define these missing type variables.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException(
|
||||||
|
"Failed to find the generated JsonAdapter constructor for $type", e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
throw RuntimeException("Failed to access the generated JsonAdapter for $type", e)
|
||||||
|
} catch (e: InstantiationException) {
|
||||||
|
throw RuntimeException("Failed to instantiate the generated JsonAdapter for $type", e)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.rethrowCause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val Class<*>.isKotlin: Boolean
|
||||||
|
get() = METADATA != null && isAnnotationPresent(METADATA)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflectively looks up the defaults constructor of a kotlin class.
|
||||||
|
*
|
||||||
|
* @receiver the target kotlin class to instantiate.
|
||||||
|
* @param T the type of `targetClass`.
|
||||||
|
* @return the instantiated `targetClass` instance.
|
||||||
|
*/
|
||||||
|
public fun <T> Class<T>.lookupDefaultsConstructor(): Constructor<T> {
|
||||||
|
checkNotNull(DEFAULT_CONSTRUCTOR_MARKER) {
|
||||||
|
"DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath."
|
||||||
|
}
|
||||||
|
val defaultConstructor = findConstructor()
|
||||||
|
defaultConstructor.isAccessible = true
|
||||||
|
return defaultConstructor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Class<T>.findConstructor(): Constructor<T> {
|
||||||
|
for (constructor in declaredConstructors) {
|
||||||
|
val paramTypes = constructor.parameterTypes
|
||||||
|
if (paramTypes.isNotEmpty() && paramTypes[paramTypes.size - 1] == DEFAULT_CONSTRUCTOR_MARKER) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return constructor as Constructor<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalStateException("No defaults constructor found for $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun missingProperty(
|
||||||
|
propertyName: String?,
|
||||||
|
jsonName: String?,
|
||||||
|
reader: JsonReader
|
||||||
|
): JsonDataException {
|
||||||
|
val path = reader.path
|
||||||
|
val message = if (jsonName == propertyName) {
|
||||||
|
"Required value '$propertyName' missing at $path"
|
||||||
|
} else {
|
||||||
|
"Required value '$propertyName' (JSON name '$jsonName') missing at $path"
|
||||||
|
}
|
||||||
|
return JsonDataException(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun unexpectedNull(
|
||||||
|
propertyName: String,
|
||||||
|
jsonName: String,
|
||||||
|
reader: JsonReader
|
||||||
|
): JsonDataException {
|
||||||
|
val path = reader.path
|
||||||
|
val message: String = if (jsonName == propertyName) {
|
||||||
|
"Non-null value '$propertyName' was null at $path"
|
||||||
|
} else {
|
||||||
|
"Non-null value '$propertyName' (JSON name '$jsonName') was null at $path"
|
||||||
|
}
|
||||||
|
return JsonDataException(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sneaky way to mark value as known to be not null, allowing smart casts and skipping the null-check intrinsic
|
||||||
|
// Safe to use here because it's already compiled and not being used externally
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
public inline fun <T : Any> knownNotNull(value: T?) {
|
||||||
|
contract {
|
||||||
|
returns() implies (value != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public due to inline access in MoshiKotlinTypesExtensions
|
||||||
|
public fun <T> Class<T>.boxIfPrimitive(): Class<T> {
|
||||||
|
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[this] as Class<T>?
|
||||||
|
return wrapped ?: this
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ParameterizedTypeImpl private constructor(
|
||||||
|
private val ownerType: Type?,
|
||||||
|
private val rawType: Type,
|
||||||
|
@JvmField
|
||||||
|
val typeArguments: Array<Type>
|
||||||
|
) : ParameterizedType {
|
||||||
|
override fun getActualTypeArguments() = typeArguments.clone()
|
||||||
|
|
||||||
|
override fun getRawType() = rawType
|
||||||
|
|
||||||
|
override fun getOwnerType() = ownerType
|
||||||
|
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is ParameterizedType && Types.equals(this, other as ParameterizedType?)
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return typeArguments.contentHashCode() xor rawType.hashCode() xor ownerType.hashCodeOrZero
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val result = StringBuilder(30 * (typeArguments.size + 1))
|
||||||
|
result.append(rawType.typeToString())
|
||||||
|
if (typeArguments.isEmpty()) {
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
result.append("<").append(typeArguments[0].typeToString())
|
||||||
|
for (i in 1 until typeArguments.size) {
|
||||||
|
result.append(", ").append(typeArguments[i].typeToString())
|
||||||
|
}
|
||||||
|
return result.append(">").toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmName("create")
|
||||||
|
@JvmStatic
|
||||||
|
operator fun invoke(
|
||||||
|
ownerType: Type?,
|
||||||
|
rawType: Type,
|
||||||
|
vararg typeArguments: Type
|
||||||
|
): ParameterizedTypeImpl {
|
||||||
|
// Require an owner type if the raw type needs it.
|
||||||
|
if (rawType is Class<*>) {
|
||||||
|
val enclosingClass = rawType.enclosingClass
|
||||||
|
if (ownerType != null) {
|
||||||
|
require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" }
|
||||||
|
} else require(enclosingClass == null) { "unexpected owner type for $rawType: null" }
|
||||||
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val finalTypeArgs = typeArguments.clone() as Array<Type>
|
||||||
|
for (t in finalTypeArgs.indices) {
|
||||||
|
finalTypeArgs[t].checkNotPrimitive()
|
||||||
|
finalTypeArgs[t] = finalTypeArgs[t].canonicalize()
|
||||||
|
}
|
||||||
|
return ParameterizedTypeImpl(ownerType?.canonicalize(), rawType.canonicalize(), finalTypeArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GenericArrayTypeImpl private constructor(private val componentType: Type) : GenericArrayType {
|
||||||
|
override fun getGenericComponentType() = componentType
|
||||||
|
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is GenericArrayType && Types.equals(this, other as GenericArrayType?)
|
||||||
|
|
||||||
|
override fun hashCode() = componentType.hashCode()
|
||||||
|
|
||||||
|
override fun toString() = componentType.typeToString() + "[]"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmName("create")
|
||||||
|
@JvmStatic
|
||||||
|
operator fun invoke(componentType: Type): GenericArrayTypeImpl {
|
||||||
|
return GenericArrayTypeImpl(componentType.canonicalize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
|
||||||
|
* support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper
|
||||||
|
* bound must be Object.class.
|
||||||
|
*/
|
||||||
|
internal class WildcardTypeImpl private constructor(
|
||||||
|
private val upperBound: Type,
|
||||||
|
private val lowerBound: Type?
|
||||||
|
) : WildcardType {
|
||||||
|
|
||||||
|
override fun getUpperBounds() = arrayOf(upperBound)
|
||||||
|
|
||||||
|
override fun getLowerBounds() = lowerBound?.let { arrayOf(it) } ?: EMPTY_TYPE_ARRAY
|
||||||
|
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
override fun equals(other: Any?) = other is WildcardType && Types.equals(this, other as WildcardType?)
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
// This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()).
|
||||||
|
return (if (lowerBound != null) 31 + lowerBound.hashCode() else 1) xor 31 + upperBound.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when {
|
||||||
|
lowerBound != null -> "? super ${lowerBound.typeToString()}"
|
||||||
|
upperBound === Any::class.java -> "?"
|
||||||
|
else -> "? extends ${upperBound.typeToString()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@JvmName("create")
|
||||||
|
operator fun invoke(
|
||||||
|
upperBounds: Array<Type>,
|
||||||
|
lowerBounds: Array<Type>
|
||||||
|
): WildcardTypeImpl {
|
||||||
|
require(lowerBounds.size <= 1)
|
||||||
|
require(upperBounds.size == 1)
|
||||||
|
return if (lowerBounds.size == 1) {
|
||||||
|
lowerBounds[0].checkNotPrimitive()
|
||||||
|
require(!(upperBounds[0] !== Any::class.java))
|
||||||
|
WildcardTypeImpl(
|
||||||
|
lowerBound = lowerBounds[0].canonicalize(),
|
||||||
|
upperBound = Any::class.java
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
upperBounds[0].checkNotPrimitive()
|
||||||
|
WildcardTypeImpl(
|
||||||
|
lowerBound = null,
|
||||||
|
upperBound = upperBounds[0].canonicalize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -95,10 +95,10 @@ final class RecordJsonAdapter<T> extends JsonAdapter<T> {
|
|||||||
MethodHandles.Lookup lookup,
|
MethodHandles.Lookup lookup,
|
||||||
RecordComponent component) {
|
RecordComponent component) {
|
||||||
var componentName = component.getName();
|
var componentName = component.getName();
|
||||||
var jsonName = Util.jsonName(componentName, component);
|
var jsonName = Util.jsonName(component, componentName);
|
||||||
|
|
||||||
var componentType = Util.resolve(type, rawType, component.getGenericType());
|
var componentType = Util.resolve(component.getGenericType(), type, rawType);
|
||||||
Set<? extends Annotation> qualifiers = Util.jsonAnnotations(component);
|
Set<? extends Annotation> qualifiers = Util.getJsonAnnotations(component);
|
||||||
var adapter = moshi.adapter(componentType, qualifiers);
|
var adapter = moshi.adapter(componentType, qualifiers);
|
||||||
|
|
||||||
MethodHandle accessor;
|
MethodHandle accessor;
|
||||||
|
@@ -819,7 +819,7 @@ public final class MoshiTest {
|
|||||||
Moshi moshi = new Moshi.Builder().add(new UppercaseAdapterFactory()).build();
|
Moshi moshi = new Moshi.Builder().add(new UppercaseAdapterFactory()).build();
|
||||||
|
|
||||||
Field uppercaseString = MoshiTest.class.getDeclaredField("uppercaseString");
|
Field uppercaseString = MoshiTest.class.getDeclaredField("uppercaseString");
|
||||||
Set<? extends Annotation> annotations = Util.jsonAnnotations(uppercaseString);
|
Set<? extends Annotation> annotations = Util.getJsonAnnotations(uppercaseString);
|
||||||
JsonAdapter<String> adapter = moshi.<String>adapter(String.class, annotations).lenient();
|
JsonAdapter<String> adapter = moshi.<String>adapter(String.class, annotations).lenient();
|
||||||
assertThat(adapter.toJson("a")).isEqualTo("\"A\"");
|
assertThat(adapter.toJson("a")).isEqualTo("\"A\"");
|
||||||
assertThat(adapter.fromJson("\"b\"")).isEqualTo("B");
|
assertThat(adapter.fromJson("\"b\"")).isEqualTo("B");
|
||||||
@@ -869,7 +869,7 @@ public final class MoshiTest {
|
|||||||
Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings");
|
Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings");
|
||||||
try {
|
try {
|
||||||
moshi.adapter(
|
moshi.adapter(
|
||||||
uppercaseStringsField.getGenericType(), Util.jsonAnnotations(uppercaseStringsField));
|
uppercaseStringsField.getGenericType(), Util.getJsonAnnotations(uppercaseStringsField));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
@@ -886,7 +886,7 @@ public final class MoshiTest {
|
|||||||
Field uppercaseStringField = MoshiTest.class.getDeclaredField("uppercaseString");
|
Field uppercaseStringField = MoshiTest.class.getDeclaredField("uppercaseString");
|
||||||
try {
|
try {
|
||||||
moshi.adapter(
|
moshi.adapter(
|
||||||
uppercaseStringField.getGenericType(), Util.jsonAnnotations(uppercaseStringField));
|
uppercaseStringField.getGenericType(), Util.getJsonAnnotations(uppercaseStringField));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
|
@@ -23,9 +23,9 @@ import com.squareup.moshi.internal.Util;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, Class,
|
* Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type,
|
||||||
* java.lang.reflect.Type)}, described at <a href="https://github.com/google/gson/issues/440">Issue
|
* java.lang.reflect.Type, Class)}, described at <a
|
||||||
* #440</a> and similar issues.
|
* href="https://github.com/google/gson/issues/440">Issue #440</a> and similar issues.
|
||||||
*
|
*
|
||||||
* <p>These tests originally caused {@link StackOverflowError} because of infinite recursion on
|
* <p>These tests originally caused {@link StackOverflowError} because of infinite recursion on
|
||||||
* attempts to resolve generics on types, with an intermediate types like 'Foo2<? extends ? super
|
* attempts to resolve generics on types, with an intermediate types like 'Foo2<? extends ? super
|
||||||
|
Reference in New Issue
Block a user