mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Promote Kotlin type-inferring APIs to the main Moshi package (round 2!) (#1202)
* Make moshi-root a kotlin project * Move moshi kotlin extensions to moshi core * Add appropriate experimental annotations * Add nextAdapter helper * Add explicit return type on addAdapter * Expression body for adapter * Use nextAdapter helper * Opportunistically fix a couple Util warnings * Add Types extensions * Spotless * Use extensions in more places for added coverage * Apply java versions on any java plugin type This way the kotlin projects get this too * Fix circularAdapters test? * Use java 8 in java for code gen too * Fixup with CircularAdaptersTest * Add coverage for remaining * Remove nextAdapter * Remove leftover function * Use asserts left checkNotNull for the contract * boxIfPrimitive * Fixup docs * Copyright fixes * Add parameterized addAdapter * Switch to using native javaType API * Spotless * Back to 2019 * Spotless * Use rawType extension * Fix rebase issues
This commit is contained in:
@@ -120,6 +120,7 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply with "java" instead of just "java-library" so kotlin projects get it too
|
||||
pluginManager.withPlugin("java") {
|
||||
configure<JavaPluginExtension> {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||
|
@@ -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"
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,11 @@ plugins {
|
||||
id("com.github.johnrengelman.shadow") version "6.0.0"
|
||||
}
|
||||
|
||||
configure<JavaPluginExtension> {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
|
@@ -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
|
||||
|
@@ -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>(TestClass::class.java)
|
||||
val instance = Moshi.Builder().build().adapter<TestClass>()
|
||||
.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>(TestClass::class.java)
|
||||
val instance = Moshi.Builder().build().adapter<TestClass>()
|
||||
.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>(TestClass::class.java)
|
||||
val instance = Moshi.Builder().build().adapter<TestClass>()
|
||||
.fromJson(json)!!
|
||||
check(instance == expected) {
|
||||
"No match:\nActual : $instance\nExpected: $expected"
|
||||
|
@@ -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<out Annotation>,
|
||||
annotations: Set<Annotation>,
|
||||
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<out Annotation>
|
||||
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<Int>())
|
||||
.add(supertypeOf<Int>(), moshi.adapter<Int>())
|
||||
.build()
|
||||
.adapter<TypeAliasUnwrapping>()
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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<NullableTypeParams<Int>>(
|
||||
Types.newParameterizedTypeWithOwner(
|
||||
GeneratedAdaptersTest::class.java,
|
||||
NullableTypeParams::class.java,
|
||||
Int::class.javaObjectType
|
||||
)
|
||||
)
|
||||
val adapter = moshi.adapter<NullableTypeParams<Int>>()
|
||||
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<MultipleTransientConstructorParameters>()
|
||||
|
||||
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) {
|
||||
|
@@ -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<MultipleMasks>()
|
||||
.fromJson(json)!!
|
||||
|
||||
assertEquals(instance.arg2, 2)
|
||||
|
@@ -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 <reified T> Moshi.adapter(): JsonAdapter<T> {
|
||||
return adapter(typeOf<T>())
|
||||
}
|
||||
|
||||
inline fun <reified T> Moshi.Builder.addAdapter(adapter: JsonAdapter<T>) = add(typeOf<T>().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 <T> Moshi.adapter(ktype: KType): JsonAdapter<T> {
|
||||
val adapter = adapter<T>(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 <T, reified R> List<T>.toTypedArray(mapper: (T) -> R): Array<R> {
|
||||
return Array(size) {
|
||||
mapper.invoke(get(it))
|
||||
}
|
||||
}
|
@@ -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<java.lang.Object, java.lang.Object, java.lang.Object> 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<Box<String>>(
|
||||
Types.newParameterizedTypeWithOwner(
|
||||
KotlinJsonAdapterTest::class.java,
|
||||
Box::class.java,
|
||||
String::class.java
|
||||
)
|
||||
)
|
||||
val stringBoxAdapter = moshi.adapter<Box<String>>()
|
||||
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<NestedGenerics<String, Int, Box<String>>>(type).indent(" ")
|
||||
val adapter = moshi.adapter<NestedGenerics<String, Int, Box<String>>>().indent(" ")
|
||||
val json =
|
||||
"""
|
||||
|{
|
||||
|
@@ -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<KotlinCompile>()
|
||||
.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)
|
||||
|
@@ -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
|
||||
|
@@ -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 <reified T> Moshi.adapter(): JsonAdapter<T> = adapter(typeOf<T>())
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
inline fun <reified T> Moshi.Builder.addAdapter(adapter: JsonAdapter<T>): Moshi.Builder = add(typeOf<T>().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 <T> Moshi.adapter(ktype: KType): JsonAdapter<T> {
|
||||
val adapter = adapter<T>(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()
|
||||
}
|
||||
}
|
@@ -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 <reified T : Annotation> Set<Annotation>.nextAnnotations(): Set<Annotation>? = 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 <reified T> subtypeOf(): WildcardType {
|
||||
var type = typeOf<T>().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 <reified T> supertypeOf(): WildcardType {
|
||||
var type = typeOf<T>().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)
|
@@ -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<? extends Annotation> METADATA;
|
||||
|
||||
/** A map from primitive types to their corresponding wrapper types. */
|
||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
|
||||
|
||||
static {
|
||||
Class<? extends Annotation> metadata = null;
|
||||
try {
|
||||
@@ -67,6 +72,20 @@ public final class Util {
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
DEFAULT_CONSTRUCTOR_MARKER = defaultConstructorMarker;
|
||||
|
||||
Map<Class<?>, Class<?>> primToWrap = new LinkedHashMap<>(16);
|
||||
|
||||
primToWrap.put(boolean.class, Boolean.class);
|
||||
primToWrap.put(byte.class, Byte.class);
|
||||
primToWrap.put(char.class, Character.class);
|
||||
primToWrap.put(double.class, Double.class);
|
||||
primToWrap.put(float.class, Float.class);
|
||||
primToWrap.put(int.class, Integer.class);
|
||||
primToWrap.put(long.class, Long.class);
|
||||
primToWrap.put(short.class, Short.class);
|
||||
primToWrap.put(void.class, Void.class);
|
||||
|
||||
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
|
||||
}
|
||||
|
||||
// Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metada
|
||||
@@ -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<TypeVariable>());
|
||||
return resolve(context, contextRawType, toResolve, new LinkedHashSet<TypeVariable<?>>());
|
||||
}
|
||||
|
||||
private static Type resolve(
|
||||
Type context,
|
||||
Class<?> contextRawType,
|
||||
Type toResolve,
|
||||
Collection<TypeVariable> visitedTypeVariables) {
|
||||
Collection<TypeVariable<?>> 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 <T> Class<T> boxIfPrimitive(Class<T> type) {
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE.get(type);
|
||||
return (wrapped == null) ? type : wrapped;
|
||||
}
|
||||
}
|
||||
|
@@ -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<TestAnnotation2>()
|
||||
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<List<String>>()
|
||||
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<Int>() {
|
||||
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<Int>().fromJson("5"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAdapterInferred_parameterized() {
|
||||
// An adapter that always returns listOf(-1)
|
||||
val customIntListAdapter = object : JsonAdapter<List<Int>>() {
|
||||
override fun fromJson(reader: JsonReader): List<Int>? {
|
||||
reader.skipValue()
|
||||
return listOf(-1)
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: List<Int>?) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
val moshi = Moshi.Builder()
|
||||
.addAdapter(customIntListAdapter)
|
||||
.build()
|
||||
|
||||
assertEquals(listOf(-1), moshi.adapter<List<Int>>().fromJson("[5]"))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user