diff --git a/build.gradle.kts b/build.gradle.kts index d8be5db..8b1467e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -120,6 +120,7 @@ subprojects { } } + // Apply with "java" instead of just "java-library" so kotlin projects get it too pluginManager.withPlugin("java") { configure { sourceCompatibility = JavaVersion.VERSION_1_7 diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index aeba4f5..aae1f54 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -36,6 +36,7 @@ object Dependencies { object Kotlin { const val version = "1.4.10" + const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib:$version" const val metadata = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0" } diff --git a/kotlin/codegen/build.gradle.kts b/kotlin/codegen/build.gradle.kts index 88a41bd..d8aa2fa 100644 --- a/kotlin/codegen/build.gradle.kts +++ b/kotlin/codegen/build.gradle.kts @@ -25,6 +25,11 @@ plugins { id("com.github.johnrengelman.shadow") version "6.0.0" } +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + tasks.withType().configureEach { kotlinOptions { jvmTarget = "1.8" diff --git a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt index 3b310ab..cdf3b1c 100644 --- a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt +++ b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt @@ -21,10 +21,10 @@ import com.squareup.moshi.JsonDataException 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.rawType import java.lang.reflect.Modifier import java.lang.reflect.Type import java.util.AbstractMap.SimpleEntry @@ -184,7 +184,7 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory { JsonAdapter<*>? { if (annotations.isNotEmpty()) return null - val rawType = Types.getRawType(type) + val rawType = type.rawType if (rawType.isInterface) return null if (rawType.isEnum) return null if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt index 5ee9cea..4b21e85 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt @@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter import org.junit.Test class DefaultConstructorTest { @@ -25,7 +26,7 @@ class DefaultConstructorTest { val expected = TestClass("requiredClass") val json = """{"required":"requiredClass"}""" - val instance = Moshi.Builder().build().adapter(TestClass::class.java) + val instance = Moshi.Builder().build().adapter() .fromJson(json)!! check(instance == expected) { "No match:\nActual : $instance\nExpected: $expected" @@ -36,7 +37,7 @@ class DefaultConstructorTest { val expected = TestClass("requiredClass", "customOptional", 4, "setDynamic", 5, 6) val json = """{"required":"requiredClass","optional":"customOptional","optional2":4,"dynamicSelfReferenceOptional":"setDynamic","dynamicOptional":5,"dynamicInlineOptional":6}""" - val instance = Moshi.Builder().build().adapter(TestClass::class.java) + val instance = Moshi.Builder().build().adapter() .fromJson(json)!! check(instance == expected) { "No match:\nActual : $instance\nExpected: $expected" @@ -47,7 +48,7 @@ class DefaultConstructorTest { val expected = TestClass("requiredClass", "customOptional") val json = """{"required":"requiredClass","optional":"customOptional"}""" - val instance = Moshi.Builder().build().adapter(TestClass::class.java) + val instance = Moshi.Builder().build().adapter() .fromJson(json)!! check(instance == expected) { "No match:\nActual : $instance\nExpected: $expected" diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt index 4b4b1b9..da0571f 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt @@ -24,9 +24,10 @@ import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi import com.squareup.moshi.ToJson -import com.squareup.moshi.Types +import com.squareup.moshi.adapter import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.squareup.moshi.kotlin.reflect.adapter +import com.squareup.moshi.rawType +import com.squareup.moshi.supertypeOf import org.intellij.lang.annotations.Language import org.junit.Assert.fail import org.junit.Test @@ -62,11 +63,11 @@ class DualKotlinTest(useReflection: Boolean) { object : Factory { override fun create( type: Type, - annotations: MutableSet, + annotations: Set, moshi: Moshi ): JsonAdapter<*>? { // Prevent falling back to generated adapter lookup - val rawType = Types.getRawType(type) + val rawType = type.rawType val metadataClass = Class.forName("kotlin.Metadata") as Class check(rawType.isEnum || !rawType.isAnnotationPresent(metadataClass)) { "Unhandled Kotlin type in reflective test! $rawType" @@ -446,7 +447,7 @@ class DualKotlinTest(useReflection: Boolean) { @Test fun typeAliasUnwrapping() { val adapter = moshi .newBuilder() - .add(Types.supertypeOf(Int::class.javaObjectType), moshi.adapter()) + .add(supertypeOf(), moshi.adapter()) .build() .adapter() diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt index 51c4582..a736342 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/ComplexGenericsInheritanceTest.kt @@ -20,7 +20,7 @@ package com.squareup.moshi.kotlin.codegen import com.google.common.truth.Truth.assertThat import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.adapter +import com.squareup.moshi.adapter import org.intellij.lang.annotations.Language import org.junit.Test diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt index 49fee27..27286e8 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt @@ -26,9 +26,8 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi import com.squareup.moshi.ToJson -import com.squareup.moshi.Types +import com.squareup.moshi.adapter import com.squareup.moshi.internal.NullSafeJsonAdapter -import com.squareup.moshi.kotlin.reflect.adapter import org.intellij.lang.annotations.Language import org.junit.Assert.assertNull import org.junit.Assert.fail @@ -299,13 +298,7 @@ class GeneratedAdaptersTest { @Test fun nullableTypeParams() { - val adapter = moshi.adapter>( - Types.newParameterizedTypeWithOwner( - GeneratedAdaptersTest::class.java, - NullableTypeParams::class.java, - Int::class.javaObjectType - ) - ) + val adapter = moshi.adapter>() val nullSerializing = adapter.serializeNulls() val nullableTypeParams = NullableTypeParams( @@ -597,7 +590,7 @@ class GeneratedAdaptersTest { @Test fun multipleTransientConstructorParameters() { val moshi = Moshi.Builder().build() - val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java) + val jsonAdapter = moshi.adapter() val encoded = MultipleTransientConstructorParameters(3, 5, 7) assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""") @@ -1346,6 +1339,7 @@ class GeneratedAdaptersTest { @Test fun typesSizeCheckMessages_noArgs() { try { + // Note: This is impossible to do if you use the reified adapter extension! moshi.adapter(MultipleGenerics::class.java) fail("Should have failed to construct the adapter due to missing generics") } catch (e: RuntimeException) { diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt index 0aa86fb..b7946fe 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt @@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin.codegen import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter import org.intellij.lang.annotations.Language import org.junit.Assert.assertEquals import org.junit.Test @@ -41,7 +42,7 @@ class MultipleMasksTest { val json = """{"arg50":500,"arg3":34,"arg11":11,"arg65":67}""" - val instance = Moshi.Builder().build().adapter(MultipleMasks::class.java) + val instance = Moshi.Builder().build().adapter() .fromJson(json)!! assertEquals(instance.arg2, 2) diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/-MoshiKotlinExtensions.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/-MoshiKotlinExtensions.kt deleted file mode 100644 index 60dfb67..0000000 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/-MoshiKotlinExtensions.kt +++ /dev/null @@ -1,106 +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.kotlin.reflect - -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.Types -import com.squareup.moshi.internal.NonNullJsonAdapter -import com.squareup.moshi.internal.NullSafeJsonAdapter -import java.lang.reflect.Type -import kotlin.reflect.KClass -import kotlin.reflect.KType -import kotlin.reflect.KTypeParameter -import kotlin.reflect.KTypeProjection -import kotlin.reflect.KVariance -import kotlin.reflect.typeOf - -/** - * @return a [JsonAdapter] for [T], creating it if necessary. Note that while nullability of [T] - * itself is handled, nested types (such as in generics) are not resolved. - */ -inline fun Moshi.adapter(): JsonAdapter { - return adapter(typeOf()) -} - -inline fun Moshi.Builder.addAdapter(adapter: JsonAdapter) = add(typeOf().toType(), adapter) - -/** - * @return a [JsonAdapter] for [ktype], creating it if necessary. Note that while nullability of - * [ktype] itself is handled, nested types (such as in generics) are not resolved. - */ -fun Moshi.adapter(ktype: KType): JsonAdapter { - val adapter = adapter(ktype.toType()) - return if (adapter is NullSafeJsonAdapter || adapter is NonNullJsonAdapter) { - // TODO CR - Assume that these know what they're doing? Or should we defensively avoid wrapping for matching nullability? - adapter - } else if (ktype.isMarkedNullable) { - adapter.nullSafe() - } else { - adapter.nonNull() - } -} - -@PublishedApi -internal fun KType.toType(allowPrimitives: Boolean = true): Type { - classifier?.let { - when (it) { - is KTypeParameter -> throw IllegalArgumentException("Type parameters are not supported") - is KClass<*> -> { - val javaType = if (allowPrimitives) { - it.java - } else { - it.javaObjectType - } - if (javaType.isArray) { - return Types.arrayOf(javaType.componentType) - } - - return if (arguments.isEmpty()) { - javaType - } else { - val typeArguments = arguments.toTypedArray { it.toType() } - val enclosingClass = javaType.enclosingClass - return if (enclosingClass != null) { - Types.newParameterizedTypeWithOwner(enclosingClass, javaType, *typeArguments) - } else { - Types.newParameterizedType(javaType, *typeArguments) - } - } - } - else -> throw IllegalArgumentException("Unsupported classifier: $this") - } - } - - // Can happen for intersection types - throw IllegalArgumentException("Unrepresentable type: $this") -} - -internal fun KTypeProjection.toType(): Type { - val javaType = type?.toType(allowPrimitives = false) ?: return Any::class.java - return when (variance) { - null -> Any::class.java - KVariance.INVARIANT -> javaType - KVariance.IN -> Types.subtypeOf(javaType) - KVariance.OUT -> Types.supertypeOf(javaType) - } -} - -private inline fun List.toTypedArray(mapper: (T) -> R): Array { - return Array(size) { - mapper.invoke(get(it)) - } -} diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt index 0ba039d..5b8edae 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt @@ -26,7 +26,7 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi import com.squareup.moshi.ToJson -import com.squareup.moshi.Types +import com.squareup.moshi.adapter import org.assertj.core.api.Assertions import org.junit.Assert.fail import org.junit.Test @@ -448,7 +448,7 @@ class KotlinJsonAdapterTest { fail() } catch (e: IllegalArgumentException) { assertThat(e).hasMessageThat().isEqualTo( - "Platform class kotlin.Triple in kotlin.Triple requires explicit JsonAdapter to be registered" + "Platform class kotlin.Triple in kotlin.Triple requires explicit JsonAdapter to be registered" ) } } @@ -857,13 +857,7 @@ class KotlinJsonAdapterTest { @Test fun genericTypes() { val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val stringBoxAdapter = moshi.adapter>( - Types.newParameterizedTypeWithOwner( - KotlinJsonAdapterTest::class.java, - Box::class.java, - String::class.java - ) - ) + val stringBoxAdapter = moshi.adapter>() assertThat(stringBoxAdapter.fromJson("""{"data":"hello"}""")).isEqualTo(Box("hello")) assertThat(stringBoxAdapter.toJson(Box("hello"))).isEqualTo("""{"data":"hello"}""") } @@ -872,18 +866,7 @@ class KotlinJsonAdapterTest { @Test fun nestedGenericTypes() { val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val type = Types.newParameterizedTypeWithOwner( - KotlinJsonAdapterTest::class.java, - NestedGenerics::class.java, - String::class.java, - Int::class.javaObjectType, - Types.newParameterizedTypeWithOwner( - KotlinJsonAdapterTest::class.java, - Box::class.java, - String::class.java - ) - ) - val adapter = moshi.adapter>>(type).indent(" ") + val adapter = moshi.adapter>>().indent(" ") val json = """ |{ diff --git a/moshi/build.gradle.kts b/moshi/build.gradle.kts index 6b69167..1ea5be5 100644 --- a/moshi/build.gradle.kts +++ b/moshi/build.gradle.kts @@ -14,15 +14,30 @@ * limitations under the License. */ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { - `java-library` + kotlin("jvm") id("com.vanniktech.maven.publish") } +tasks.withType() + .matching { it.name.contains("test", true) } + .configureEach { + kotlinOptions { + @Suppress("SuspiciousCollectionReassignment") // It's not suspicious + freeCompilerArgs += listOf( + "-Xopt-in=kotlin.ExperimentalStdlibApi" + ) + } + } + dependencies { compileOnly(Dependencies.jsr305) + compileOnly(Dependencies.Kotlin.stdlib) api(Dependencies.okio) + testImplementation(Dependencies.Kotlin.stdlib) testCompileOnly(Dependencies.jsr305) testImplementation(Dependencies.Testing.junit) testImplementation(Dependencies.Testing.truth) diff --git a/moshi/gradle.properties b/moshi/gradle.properties index 4938af0..b84f977 100644 --- a/moshi/gradle.properties +++ b/moshi/gradle.properties @@ -18,3 +18,7 @@ POM_NAME=Moshi POM_ARTIFACT_ID=moshi POM_PACKAGING=jar AUTOMATIC_MODULE_NAME=com.squareup.moshi + +# Kotlin adds the stdlib dep by default in 1.4.0+, but we want to effectively make it compileOnly +# for our case to avoid imposing it on consumers. +kotlin.stdlib.default.dependency=false diff --git a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinExtensions.kt b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinExtensions.kt new file mode 100644 index 0000000..3a5989a --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinExtensions.kt @@ -0,0 +1,49 @@ +/* + * 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 + +import com.squareup.moshi.internal.NonNullJsonAdapter +import com.squareup.moshi.internal.NullSafeJsonAdapter +import kotlin.reflect.KType +import kotlin.reflect.javaType +import kotlin.reflect.typeOf + +/** + * @return a [JsonAdapter] for [T], creating it if necessary. Note that while nullability of [T] + * itself is handled, nested types (such as in generics) are not resolved. + */ +@ExperimentalStdlibApi +inline fun Moshi.adapter(): JsonAdapter = adapter(typeOf()) + +@ExperimentalStdlibApi +inline fun Moshi.Builder.addAdapter(adapter: JsonAdapter): Moshi.Builder = add(typeOf().javaType, adapter) + +/** + * @return a [JsonAdapter] for [ktype], creating it if necessary. Note that while nullability of + * [ktype] itself is handled, nested types (such as in generics) are not resolved. + */ +@ExperimentalStdlibApi +fun Moshi.adapter(ktype: KType): JsonAdapter { + val adapter = adapter(ktype.javaType) + return if (adapter is NullSafeJsonAdapter || adapter is NonNullJsonAdapter) { + // TODO CR - Assume that these know what they're doing? Or should we defensively avoid wrapping for matching nullability? + adapter + } else if (ktype.isMarkedNullable) { + adapter.nullSafe() + } else { + adapter.nonNull() + } +} diff --git a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt new file mode 100644 index 0000000..6bfd38e --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 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 + +import com.squareup.moshi.internal.Util +import java.lang.reflect.GenericArrayType +import java.lang.reflect.Type +import java.lang.reflect.WildcardType +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.javaType +import kotlin.reflect.typeOf + +/** Returns the raw [Class] type of this type. */ +val Type.rawType: Class<*> get() = Types.getRawType(this) + +/** + * Checks if [this] contains [T]. Returns the subset of [this] without [T], or null if + * [this] does not contain [T]. + */ +inline fun Set.nextAnnotations(): Set? = Types.nextAnnotations(this, T::class.java) + +/** + * Returns a type that represents an unknown type that extends [T]. For example, if + * [T] is [CharSequence], this returns `out CharSequence`. If + * [T] is [Any], this returns `*`, which is shorthand for `out Any?`. + */ +@ExperimentalStdlibApi +inline fun subtypeOf(): WildcardType { + var type = typeOf().javaType + if (type is Class<*>) { + type = Util.boxIfPrimitive(type) + } + return Types.subtypeOf(type) +} + +/** + * Returns a type that represents an unknown supertype of [T] bound. For example, if [T] is + * [String], this returns `in String`. + */ +@ExperimentalStdlibApi +inline fun supertypeOf(): WildcardType { + var type = typeOf().javaType + if (type is Class<*>) { + type = Util.boxIfPrimitive(type) + } + return Types.supertypeOf(type) +} + +/** Returns a [GenericArrayType] with [this] as its [GenericArrayType.getGenericComponentType]. */ +@ExperimentalStdlibApi +fun KType.asArrayType(): GenericArrayType = javaType.asArrayType() + +/** Returns a [GenericArrayType] with [this] as its [GenericArrayType.getGenericComponentType]. */ +fun KClass<*>.asArrayType(): GenericArrayType = java.asArrayType() + +/** Returns a [GenericArrayType] with [this] as its [GenericArrayType.getGenericComponentType]. */ +fun Type.asArrayType(): GenericArrayType = Types.arrayOf(this) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.java index 4412665..b517c98 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.java +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.java @@ -39,7 +39,9 @@ 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; @@ -50,6 +52,9 @@ public final class Util { @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 { @@ -67,6 +72,20 @@ public final class Util { } 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 @@ -182,14 +201,14 @@ public final class Util { } public static Type resolve(Type context, Class contextRawType, Type toResolve) { - return resolve(context, contextRawType, toResolve, new LinkedHashSet()); + return resolve(context, contextRawType, toResolve, new LinkedHashSet>()); } private static Type resolve( Type context, Class contextRawType, Type toResolve, - Collection visitedTypeVariables) { + Collection> visitedTypeVariables) { // This implementation is made a little more complicated in an attempt to avoid object-creation. while (true) { if (toResolve instanceof TypeVariable) { @@ -643,4 +662,12 @@ public final class Util { } 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/test/java/com/squareup/moshi/KotlinExtensionsTest.kt b/moshi/src/test/java/com/squareup/moshi/KotlinExtensionsTest.kt new file mode 100644 index 0000000..47c277e --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/KotlinExtensionsTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 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 + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.reflect.typeOf + +@JsonQualifier +@Retention(RUNTIME) +annotation class TestAnnotation1 + +@JsonQualifier +@Retention(RUNTIME) +annotation class TestAnnotation2 + +@TestAnnotation1 +@TestAnnotation2 +class KotlinExtensionsTest { + + @Test + fun nextAnnotationsShouldWork() { + val annotations = KotlinExtensionsTest::class.java.annotations + .filterTo(mutableSetOf()) { + it.annotationClass.java.isAnnotationPresent(JsonQualifier::class.java) + } + assertEquals(2, annotations.size) + val next = annotations.nextAnnotations() + checkNotNull(next) + assertEquals(1, next.size) + assertTrue(next.first() is TestAnnotation1) + } + + @Test + fun arrayType() { + val stringArray = String::class.asArrayType() + check(stringArray.genericComponentType == String::class.java) + + val stringListType = typeOf>() + val stringListArray = stringListType.asArrayType() + val expected = Types.arrayOf(Types.newParameterizedType(List::class.java, String::class.java)) + assertEquals(stringListArray, expected) + } + + @Test + fun addAdapterInferred() { + // An adapter that always returns -1 + val customIntdapter = object : JsonAdapter() { + override fun fromJson(reader: JsonReader): Int? { + reader.skipValue() + return -1 + } + + override fun toJson(writer: JsonWriter, value: Int?) { + throw NotImplementedError() + } + } + val moshi = Moshi.Builder() + .addAdapter(customIntdapter) + .build() + + assertEquals(-1, moshi.adapter().fromJson("5")) + } + + @Test + fun addAdapterInferred_parameterized() { + // An adapter that always returns listOf(-1) + val customIntListAdapter = object : JsonAdapter>() { + override fun fromJson(reader: JsonReader): List? { + reader.skipValue() + return listOf(-1) + } + + override fun toJson(writer: JsonWriter, value: List?) { + throw NotImplementedError() + } + } + val moshi = Moshi.Builder() + .addAdapter(customIntListAdapter) + .build() + + assertEquals(listOf(-1), moshi.adapter>().fromJson("[5]")) + } +}