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:
Zac Sweers
2022-01-06 14:54:13 -05:00
committed by GitHub
parent e5e4c48767
commit e2c346e1de
21 changed files with 790 additions and 875 deletions

View File

@@ -78,7 +78,7 @@ final class FallbackEnum {
nameStrings = new String[constants.length];
for (int i = 0; i < constants.length; i++) {
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);
} catch (NoSuchFieldException e) {

View File

@@ -68,7 +68,7 @@ public final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
nameStrings = new String[constants.length];
for (int i = 0; i < constants.length; i++) {
String constantName = constants[i].name();
nameStrings[i] = jsonName(constantName, enumType.getField(constantName));
nameStrings[i] = jsonName(enumType.getField(constantName), constantName);
}
options = JsonReader.Options.of(nameStrings);
} catch (NoSuchFieldException e) {

View File

@@ -39,7 +39,6 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
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.ParameterProperty
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
@@ -47,7 +46,7 @@ import java.lang.reflect.Constructor
import java.lang.reflect.Type
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_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren
@@ -61,8 +60,8 @@ public class AdapterGenerator(
private companion object {
private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
"%T.DEFAULT_CONSTRUCTOR_MARKER",
Util::class
"%M",
MemberName(MOSHI_UTIL_PACKAGE, "DEFAULT_CONSTRUCTOR_MARKER")
)
private val CN_MOSHI = Moshi::class.asClassName()
private val CN_TYPE = Type::class.asClassName()
@@ -656,8 +655,8 @@ public class AdapterGenerator(
private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
return CodeBlock.of(
"%T.unexpectedNull(%S, %S, %N)",
MOSHI_UTIL,
"%M(%S, %S, %N)",
MemberName(MOSHI_UTIL_PACKAGE, "unexpectedNull"),
property.localName,
property.jsonName,
reader
@@ -699,8 +698,8 @@ public class AdapterGenerator(
private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
val missingPropertyBlock =
CodeBlock.of(
"%T.missingProperty(%S, %S, %N)",
MOSHI_UTIL,
"%M(%S, %S, %N)",
MemberName(MOSHI_UTIL_PACKAGE, "missingProperty"),
property.localName,
property.jsonName,
readerParam

View File

@@ -1270,7 +1270,7 @@ class GeneratedAdaptersTest {
@Test fun customGenerator_withClassPresent() {
val moshi = Moshi.Builder().build()
val adapter = moshi.adapter<CustomGeneratedClass>()
val unwrapped = (adapter as NullSafeJsonAdapter<CustomGeneratedClass>).delegate()
val unwrapped = (adapter as NullSafeJsonAdapter<CustomGeneratedClass>).delegate
assertThat(unwrapped).isInstanceOf(
GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter::class.java
)

View File

@@ -22,9 +22,12 @@ import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.Util
import com.squareup.moshi.internal.Util.generatedAdapter
import com.squareup.moshi.internal.Util.resolve
import com.squareup.moshi.internal.generatedAdapter
import com.squareup.moshi.internal.isPlatformType
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 java.lang.reflect.Modifier
import java.lang.reflect.Type
@@ -86,7 +89,7 @@ internal class KotlinJsonAdapter<T>(
values[propertyIndex] = binding.adapter.fromJson(reader)
if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
throw Util.unexpectedNull(
throw unexpectedNull(
binding.property.name,
binding.jsonName,
reader
@@ -102,7 +105,7 @@ internal class KotlinJsonAdapter<T>(
when {
constructor.parameters[i].isOptional -> isFullInitialized = false
constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
else -> throw Util.missingProperty(
else -> throw missingProperty(
constructor.parameters[i].name,
allBindings[i]?.jsonName,
reader
@@ -195,9 +198,9 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
if (rawType.isInterface) return null
if (rawType.isEnum) return null
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
if (Util.isPlatformType(rawType)) return null
if (rawType.isPlatformType) return null
try {
val generatedAdapter = generatedAdapter(moshi, type, rawType)
val generatedAdapter = moshi.generatedAdapter(type, rawType)
if (generatedAdapter != null) {
return generatedAdapter
}
@@ -288,10 +291,10 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
}
else -> error("Not possible!")
}
val resolvedPropertyType = resolve(type, rawType, propertyType)
val resolvedPropertyType = propertyType.resolve(type, rawType)
val adapter = moshi.adapter<Any?>(
resolvedPropertyType,
Util.jsonAnnotations(allAnnotations.toTypedArray()),
allAnnotations.toTypedArray().jsonAnnotations,
property.name
)

View File

@@ -52,12 +52,12 @@ tasks.withType<Test>().configureEach {
tasks.withType<KotlinCompile>()
.configureEach {
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)) {
args += "-Xopt-in=kotlin.ExperimentalStdlibApi"
toAdd += "-Xopt-in=kotlin.ExperimentalStdlibApi"
}
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
freeCompilerArgs += args
freeCompilerArgs += toAdd
}
}

View File

@@ -15,7 +15,7 @@
*/
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.Type
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 {
var type = typeOf<T>().javaType
if (type is Class<*>) {
type = Util.boxIfPrimitive(type)
type = type.boxIfPrimitive()
}
return Types.subtypeOf(type)
}
@@ -55,7 +55,7 @@ public inline fun <reified T> subtypeOf(): WildcardType {
public inline fun <reified T> supertypeOf(): WildcardType {
var type = typeOf<T>().javaType
if (type is Class<*>) {
type = Util.boxIfPrimitive(type)
type = type.boxIfPrimitive()
}
return Types.supertypeOf(type)
}

View File

@@ -16,8 +16,8 @@
package com.squareup.moshi;
import static com.squareup.moshi.internal.Util.canonicalize;
import static com.squareup.moshi.internal.Util.jsonAnnotations;
import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations;
import static com.squareup.moshi.internal.Util.getJsonAnnotations;
import static com.squareup.moshi.internal.Util.toStringWithAnnotations;
import com.squareup.moshi.internal.Util;
import java.io.IOException;
@@ -57,7 +57,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
"No "
+ missingAnnotation
+ " adapter for "
+ typeAnnotatedWithAnnotations(type, annotations),
+ toStringWithAnnotations(type, annotations),
e);
}
} else {
@@ -172,7 +172,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
&& parametersAreJsonAdapters(2, parameterTypes)) {
// void pointToJson(JsonWriter jsonWriter, Point point) {
// 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(
parameterTypes[1],
qualifierAnnotations,
@@ -190,10 +190,10 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
} else if (parameterTypes.length == 1 && returnType != void.class) {
// 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 =
jsonAnnotations(parameterAnnotations[0]);
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
getJsonAnnotations(parameterAnnotations[0]);
boolean nullable = Util.getHasNullable(parameterAnnotations[0]);
return new AdapterMethod(
parameterTypes[0],
qualifierAnnotations,
@@ -251,7 +251,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
static AdapterMethod fromAdapter(Object adapter, Method method) {
method.setAccessible(true);
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();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
@@ -273,8 +273,8 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
} else if (parameterTypes.length == 1 && returnType != void.class) {
// Point pointFromJson(List<Integer> o) {
final Set<? extends Annotation> qualifierAnnotations =
jsonAnnotations(parameterAnnotations[0]);
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
getJsonAnnotations(parameterAnnotations[0]);
boolean nullable = Util.getHasNullable(parameterAnnotations[0]);
return new AdapterMethod(
returnType, returnTypeAnnotations, adapter, method, parameterTypes.length, 1, nullable) {
JsonAdapter<Object> delegate;
@@ -354,7 +354,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) {
Type type = ((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0];
Set<? extends Annotation> jsonAnnotations = jsonAnnotations(parameterAnnotations[i]);
Set<? extends Annotation> jsonAnnotations = getJsonAnnotations(parameterAnnotations[i]);
jsonAdapters[i - adaptersOffset] =
Types.equals(this.type, type) && annotations.equals(jsonAnnotations)
? moshi.nextAdapter(factory, type, jsonAnnotations)

View File

@@ -139,8 +139,8 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
if (jsonAnnotation != null && jsonAnnotation.ignore()) continue;
// Look up a type adapter for this type.
Type fieldType = resolve(type, rawType, field.getGenericType());
Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
Type fieldType = resolve(field.getGenericType(), type, rawType);
Set<? extends Annotation> annotations = Util.getJsonAnnotations(field);
String fieldName = field.getName();
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations, fieldName);
@@ -148,7 +148,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
field.setAccessible(true);
// 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<?> replaced = fieldBindings.put(jsonName, fieldBinding);
if (replaced != null) {

View File

@@ -16,12 +16,12 @@
package com.squareup.moshi
import com.squareup.moshi.Types.createJsonQualifierImplementation
import com.squareup.moshi.internal.Util
import com.squareup.moshi.internal.Util.canonicalize
import com.squareup.moshi.internal.Util.isAnnotationPresent
import com.squareup.moshi.internal.Util.removeSubtypeWildcard
import com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations
import com.squareup.moshi.internal.Util.typesMatch
import com.squareup.moshi.internal.NO_ANNOTATIONS
import com.squareup.moshi.internal.canonicalize
import com.squareup.moshi.internal.isAnnotationPresent
import com.squareup.moshi.internal.removeSubtypeWildcard
import com.squareup.moshi.internal.toStringWithAnnotations
import com.squareup.moshi.internal.typesMatch
import java.lang.reflect.Type
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. */
@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
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
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>,
fieldName: String?
): JsonAdapter<T> {
val cleanedType = removeSubtypeWildcard(canonicalize(type))
val cleanedType = type.canonicalize().removeSubtypeWildcard()
// If there's an equivalent adapter in the cache, we're done!
val cacheKey = cacheKey(cleanedType, annotations)
@@ -107,7 +107,7 @@ public class Moshi internal constructor(builder: Builder) {
success = true
return result
}
throw IllegalArgumentException("No JsonAdapter for ${typeAnnotatedWithAnnotations(type, annotations)}")
throw IllegalArgumentException("No JsonAdapter for ${type.toStringWithAnnotations(annotations)}")
} catch (e: IllegalArgumentException) {
throw lookupChain.exceptionWithLookupStack(e)
} finally {
@@ -121,7 +121,7 @@ public class Moshi internal constructor(builder: Builder) {
type: Type,
annotations: Set<Annotation>
): JsonAdapter<T> {
val cleanedType = removeSubtypeWildcard(canonicalize(type))
val cleanedType = type.canonicalize().removeSubtypeWildcard()
val skipPastIndex = factories.indexOf(skipPast)
require(skipPastIndex != -1) { "Unable to skip past unknown factory $skipPast" }
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>?
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. */
@@ -354,7 +354,7 @@ public class Moshi internal constructor(builder: Builder) {
require(annotation.isAnnotationPresent(JsonQualifier::class.java)) { "$annotation does not have @JsonQualifier" }
require(annotation.declaredMethods.isEmpty()) { "Use JsonAdapter.Factory for annotations with elements" }
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
} else {
null

View File

@@ -275,7 +275,7 @@ final class StandardJsonAdapters {
for (int i = 0; i < constants.length; i++) {
T constant = constants[i];
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);
} catch (NoSuchFieldException e) {

View File

@@ -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.resolve;
import com.squareup.moshi.internal.Util.GenericArrayTypeImpl;
import com.squareup.moshi.internal.Util.ParameterizedTypeImpl;
import com.squareup.moshi.internal.Util.WildcardTypeImpl;
import com.squareup.moshi.internal.GenericArrayTypeImpl;
import com.squareup.moshi.internal.ParameterizedTypeImpl;
import com.squareup.moshi.internal.WildcardTypeImpl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -110,7 +110,7 @@ public final class Types {
if (typeArguments.length == 0) {
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) {
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}. */
public static GenericArrayType arrayOf(Type componentType) {
return new GenericArrayTypeImpl(componentType);
return GenericArrayTypeImpl.create(componentType);
}
/**
@@ -143,7 +143,7 @@ public final class Types {
} else {
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 {
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) {
@@ -362,12 +362,12 @@ public final class Types {
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
return resolve(
context, contextRawType, getGenericSupertype(context, contextRawType, supertype));
getGenericSupertype(context, contextRawType, supertype), context, contextRawType);
}
static Type getGenericSuperclass(Type type) {
Class<?> rawType = Types.getRawType(type);
return resolve(type, rawType, rawType.getGenericSuperclass());
return resolve(rawType.getGenericSuperclass(), type, rawType);
}
/**

View File

@@ -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()";
}
}

View File

@@ -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()"
}

View File

@@ -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()";
}
}

View File

@@ -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()"
}

View File

@@ -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;
}
}

View 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()
)
}
}
}
}

View File

@@ -95,10 +95,10 @@ final class RecordJsonAdapter<T> extends JsonAdapter<T> {
MethodHandles.Lookup lookup,
RecordComponent component) {
var componentName = component.getName();
var jsonName = Util.jsonName(componentName, component);
var jsonName = Util.jsonName(component, componentName);
var componentType = Util.resolve(type, rawType, component.getGenericType());
Set<? extends Annotation> qualifiers = Util.jsonAnnotations(component);
var componentType = Util.resolve(component.getGenericType(), type, rawType);
Set<? extends Annotation> qualifiers = Util.getJsonAnnotations(component);
var adapter = moshi.adapter(componentType, qualifiers);
MethodHandle accessor;

View File

@@ -819,7 +819,7 @@ public final class MoshiTest {
Moshi moshi = new Moshi.Builder().add(new UppercaseAdapterFactory()).build();
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();
assertThat(adapter.toJson("a")).isEqualTo("\"A\"");
assertThat(adapter.fromJson("\"b\"")).isEqualTo("B");
@@ -869,7 +869,7 @@ public final class MoshiTest {
Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings");
try {
moshi.adapter(
uppercaseStringsField.getGenericType(), Util.jsonAnnotations(uppercaseStringsField));
uppercaseStringsField.getGenericType(), Util.getJsonAnnotations(uppercaseStringsField));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected)
@@ -886,7 +886,7 @@ public final class MoshiTest {
Field uppercaseStringField = MoshiTest.class.getDeclaredField("uppercaseString");
try {
moshi.adapter(
uppercaseStringField.getGenericType(), Util.jsonAnnotations(uppercaseStringField));
uppercaseStringField.getGenericType(), Util.getJsonAnnotations(uppercaseStringField));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected)

View File

@@ -23,9 +23,9 @@ import com.squareup.moshi.internal.Util;
import org.junit.Test;
/**
* Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, Class,
* java.lang.reflect.Type)}, described at <a href="https://github.com/google/gson/issues/440">Issue
* #440</a> and similar issues.
* Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type,
* java.lang.reflect.Type, Class)}, described at <a
* 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
* attempts to resolve generics on types, with an intermediate types like 'Foo2&lt;? extends ? super