diff --git a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java index 287e32c1b..72f8c857a 100644 --- a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java +++ b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java @@ -78,7 +78,7 @@ public JsonAdapter create( 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) { diff --git a/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java b/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java index 058231f90..6f9e03823 100644 --- a/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java +++ b/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java @@ -68,7 +68,7 @@ public EnumJsonAdapter withUnknownFallback(@Nullable T fallbackValue) { 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) { diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index 9878ce672..d8c421ee1 100644 --- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -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 diff --git a/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt index e447b2bc5..f42dff60b 100644 --- a/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt +++ b/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt @@ -1270,7 +1270,7 @@ class GeneratedAdaptersTest { @Test fun customGenerator_withClassPresent() { val moshi = Moshi.Builder().build() val adapter = moshi.adapter() - val unwrapped = (adapter as NullSafeJsonAdapter).delegate() + val unwrapped = (adapter as NullSafeJsonAdapter).delegate assertThat(unwrapped).isInstanceOf( GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter::class.java ) diff --git a/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt index 0da0d91b5..11ff644ab 100644 --- a/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt +++ b/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt @@ -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( 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( 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( resolvedPropertyType, - Util.jsonAnnotations(allAnnotations.toTypedArray()), + allAnnotations.toTypedArray().jsonAnnotations, property.name ) diff --git a/moshi/build.gradle.kts b/moshi/build.gradle.kts index 038367366..c3d4d884f 100644 --- a/moshi/build.gradle.kts +++ b/moshi/build.gradle.kts @@ -52,12 +52,12 @@ tasks.withType().configureEach { tasks.withType() .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 } } diff --git a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt index 5be471a37..96f387dc2 100644 --- a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt +++ b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt @@ -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 Set.nextAnnotations(): Se public inline fun subtypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = Util.boxIfPrimitive(type) + type = type.boxIfPrimitive() } return Types.subtypeOf(type) } @@ -55,7 +55,7 @@ public inline fun subtypeOf(): WildcardType { public inline fun supertypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = Util.boxIfPrimitive(type) + type = type.boxIfPrimitive() } return Types.supertypeOf(type) } diff --git a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java index 84c8df700..90ef68252 100644 --- a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java @@ -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 @@ static AdapterMethod toAdapter(Object adapter, Method method) { && parametersAreJsonAdapters(2, parameterTypes)) { // void pointToJson(JsonWriter jsonWriter, Point point) { // void pointToJson(JsonWriter jsonWriter, Point point, JsonAdapter adapter, ...) { - Set qualifierAnnotations = jsonAnnotations(parameterAnnotations[1]); + Set qualifierAnnotations = getJsonAnnotations(parameterAnnotations[1]); return new AdapterMethod( parameterTypes[1], qualifierAnnotations, @@ -190,10 +190,10 @@ public void toJson(Moshi moshi, JsonWriter writer, @Nullable Object value) } else if (parameterTypes.length == 1 && returnType != void.class) { // List pointToJson(Point point) { - final Set returnTypeAnnotations = jsonAnnotations(method); + final Set returnTypeAnnotations = Util.getJsonAnnotations(method); final Set 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 @@ private static boolean parametersAreJsonAdapters(int offset, Type[] parameterTyp static AdapterMethod fromAdapter(Object adapter, Method method) { method.setAccessible(true); final Type returnType = method.getGenericReturnType(); - final Set returnTypeAnnotations = jsonAnnotations(method); + final Set returnTypeAnnotations = Util.getJsonAnnotations(method); final Type[] parameterTypes = method.getGenericParameterTypes(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); @@ -273,8 +273,8 @@ public Object fromJson(Moshi moshi, JsonReader reader) } else if (parameterTypes.length == 1 && returnType != void.class) { // Point pointFromJson(List o) { final Set 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 delegate; @@ -354,7 +354,7 @@ public void bind(Moshi moshi, JsonAdapter.Factory factory) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) { Type type = ((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0]; - Set jsonAnnotations = jsonAnnotations(parameterAnnotations[i]); + Set jsonAnnotations = getJsonAnnotations(parameterAnnotations[i]); jsonAdapters[i - adaptersOffset] = Types.equals(this.type, type) && annotations.equals(jsonAnnotations) ? moshi.nextAdapter(factory, type, jsonAnnotations) diff --git a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java index 73b65e350..9437c4d78 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java @@ -139,8 +139,8 @@ private void createFieldBindings( if (jsonAnnotation != null && jsonAnnotation.ignore()) continue; // Look up a type adapter for this type. - Type fieldType = resolve(type, rawType, field.getGenericType()); - Set annotations = Util.jsonAnnotations(field); + Type fieldType = resolve(field.getGenericType(), type, rawType); + Set annotations = Util.getJsonAnnotations(field); String fieldName = field.getName(); JsonAdapter adapter = moshi.adapter(fieldType, annotations, fieldName); @@ -148,7 +148,7 @@ private void createFieldBindings( 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 fieldBinding = new FieldBinding<>(jsonName, field, adapter); FieldBinding replaced = fieldBindings.put(jsonName, fieldBinding); if (replaced != null) { diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.kt b/moshi/src/main/java/com/squareup/moshi/Moshi.kt index 72b414efd..9d2ce40fa 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.kt +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.kt @@ -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 adapter(type: Type): JsonAdapter = adapter(type, Util.NO_ANNOTATIONS) + public fun adapter(type: Type): JsonAdapter = adapter(type, NO_ANNOTATIONS) @CheckReturnValue - public fun adapter(type: Class): JsonAdapter = adapter(type, Util.NO_ANNOTATIONS) + public fun adapter(type: Class): JsonAdapter = adapter(type, NO_ANNOTATIONS) @CheckReturnValue public fun adapter(type: Type, annotationType: Class): JsonAdapter = @@ -78,7 +78,7 @@ public class Moshi internal constructor(builder: Builder) { annotations: Set, fieldName: String? ): JsonAdapter { - 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 ): JsonAdapter { - 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? 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 diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index 000c85859..05dc09651 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -275,7 +275,7 @@ static final class EnumJsonAdapter> extends JsonAdapter { 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) { diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index f03be62d6..19332e7d7 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -19,9 +19,9 @@ 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 static ParameterizedType newParameterizedType(Type rawType, Type... typeA 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 static ParameterizedType newParameterizedTypeWithOwner( 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 static WildcardType subtypeOf(Type bound) { } else { upperBounds = new Type[] {bound}; } - return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY); + return WildcardTypeImpl.create(upperBounds, EMPTY_TYPE_ARRAY); } /** @@ -157,7 +157,7 @@ public static WildcardType supertypeOf(Type bound) { } 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 @@ static Type[] mapKeyAndValueTypes(Type context, Class contextRawType) { 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); } /** diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java deleted file mode 100644 index c0de42afc..000000000 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java +++ /dev/null @@ -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 extends JsonAdapter { - - private final JsonAdapter delegate; - - public NonNullJsonAdapter(JsonAdapter delegate) { - this.delegate = delegate; - } - - public JsonAdapter 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()"; - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt new file mode 100644 index 000000000..42a3a46d7 --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -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(public val delegate: JsonAdapter) : JsonAdapter() { + 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()" +} diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java deleted file mode 100644 index 0ed2b85f2..000000000 --- a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java +++ /dev/null @@ -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 extends JsonAdapter { - - private final JsonAdapter delegate; - - public NullSafeJsonAdapter(JsonAdapter delegate) { - this.delegate = delegate; - } - - public JsonAdapter 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()"; - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt new file mode 100644 index 000000000..8df2f20cd --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt @@ -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(public val delegate: JsonAdapter) : JsonAdapter() { + 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()" +} diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.java deleted file mode 100644 index d8edb504c..000000000 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.java +++ /dev/null @@ -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 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 METADATA; - - /** A map from primitive types to their corresponding wrapper types. */ - private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; - - static { - Class metadata = null; - try { - //noinspection unchecked - metadata = (Class) 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> 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). - return Types.equals(pattern, candidate); - } - - public static Set jsonAnnotations(AnnotatedElement annotatedElement) { - return jsonAnnotations(annotatedElement.getAnnotations()); - } - - public static Set jsonAnnotations(Annotation[] annotations) { - Set 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 annotations, Class 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>()); - } - - private static Type resolve( - Type context, - Class contextRawType, - Type toResolve, - Collection> 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} and the - * result when the supertype is {@code Collection.class} is {@code Collection}. - */ - 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 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> adapterClass = null; - try { - //noinspection unchecked - We generate types to match. - adapterClass = - (Class>) - Class.forName(adapterClassName, true, rawType.getClassLoader()); - Constructor> 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 the type of {@code targetClass}. - * @return the instantiated {@code targetClass} instance. - */ - public static Constructor lookupDefaultsConstructor(Class targetClass) { - if (DEFAULT_CONSTRUCTOR_MARKER == null) { - throw new IllegalStateException( - "DefaultConstructorMarker not on classpath. Make sure the " - + "Kotlin stdlib is on the classpath."); - } - Constructor defaultConstructor = findConstructor(targetClass); - defaultConstructor.setAccessible(true); - return defaultConstructor; - } - - private static Constructor findConstructor(Class 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) 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 Class boxIfPrimitive(Class type) { - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); - return (wrapped == null) ? type : wrapped; - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt new file mode 100644 index 000000000..77f629c31 --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -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 = emptySet() +@JvmField public val EMPTY_TYPE_ARRAY: Array = arrayOf() + +@Suppress("UNCHECKED_CAST") +private val METADATA: Class? = try { + Class.forName(kotlinMetadataClassName) as Class +} 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<*>> = 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). + return Types.equals(pattern, candidate) +} + +public val AnnotatedElement.jsonAnnotations: Set + get() = annotations.jsonAnnotations + +public val Array.jsonAnnotations: Set + get() { + var result: MutableSet? = 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.isAnnotationPresent( + annotationClass: Class +): 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.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> +): 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` and the + * result when the supertype is `Collection.class` is `Collection`. + */ +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): 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>? = null + return try { + @Suppress("UNCHECKED_CAST") + val adapterClass = Class.forName(adapterClassName, true, rawType.classLoader) as Class> + possiblyFoundAdapter = adapterClass + var constructor: Constructor> + var args: Array + if (type is ParameterizedType) { + val typeArgs = type.actualTypeArguments + try { + // Common case first + constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array::class.java) + args = arrayOf(this, typeArgs) + } catch (e: NoSuchMethodException) { + constructor = adapterClass.getDeclaredConstructor(Array::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 Class.lookupDefaultsConstructor(): Constructor { + 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 Class.findConstructor(): Constructor { + 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 + } + } + 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 knownNotNull(value: T?) { + contract { + returns() implies (value != null) + } +} + +// Public due to inline access in MoshiKotlinTypesExtensions +public fun Class.boxIfPrimitive(): Class { + // cast is safe: long.class and Long.class are both of type Class + @Suppress("UNCHECKED_CAST") + val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[this] as Class? + return wrapped ?: this +} + +internal class ParameterizedTypeImpl private constructor( + private val ownerType: Type?, + private val rawType: Type, + @JvmField + val typeArguments: Array +) : 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 + 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, + lowerBounds: Array + ): 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() + ) + } + } + } +} diff --git a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java index 973ad8185..2e77e39b9 100644 --- a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java +++ b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java @@ -95,10 +95,10 @@ private static ComponentBinding createComponentBinding( 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 qualifiers = Util.jsonAnnotations(component); + var componentType = Util.resolve(component.getGenericType(), type, rawType); + Set qualifiers = Util.getJsonAnnotations(component); var adapter = moshi.adapter(componentType, qualifiers); MethodHandle accessor; diff --git a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java index f00d52dde..ca7016dd0 100644 --- a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java @@ -819,7 +819,7 @@ public void delegatingJsonAdapterFactory() throws Exception { Moshi moshi = new Moshi.Builder().add(new UppercaseAdapterFactory()).build(); Field uppercaseString = MoshiTest.class.getDeclaredField("uppercaseString"); - Set annotations = Util.jsonAnnotations(uppercaseString); + Set annotations = Util.getJsonAnnotations(uppercaseString); JsonAdapter adapter = moshi.adapter(String.class, annotations).lenient(); assertThat(adapter.toJson("a")).isEqualTo("\"A\""); assertThat(adapter.fromJson("\"b\"")).isEqualTo("B"); @@ -869,7 +869,7 @@ public void collectionsDoNotKeepAnnotations() throws Exception { 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 void noTypeAdapterForQualifiedPlatformType() throws Exception { 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) diff --git a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java index a390606f5..b1a020392 100644 --- a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java +++ b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java @@ -23,9 +23,9 @@ import org.junit.Test; /** - * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, Class, - * java.lang.reflect.Type)}, described at Issue - * #440 and similar issues. + * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, + * java.lang.reflect.Type, Class)}, described at Issue #440 and similar issues. * *

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