mirror of
https://github.com/HighCapable/moshi-companion.git
synced 2025-10-19 00:59:27 +08:00
Initial commit
This commit is contained in:
29
companion-api/build.gradle.kts
Normal file
29
companion-api/build.gradle.kts
Normal file
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.maven.publish)
|
||||
}
|
||||
|
||||
group = property.project.groupName
|
||||
version = property.project.version
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.moshi.kotlin)
|
||||
implementation(libs.kavaref.core)
|
||||
implementation(libs.kavaref.extension)
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Moshi Companion - Companion to Moshi with more practical features.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/moshi-companion
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/2.
|
||||
*/
|
||||
package com.highcapable.moshi.companion.api
|
||||
|
||||
import com.highcapable.kavaref.KavaRef.Companion.resolve
|
||||
import com.highcapable.kavaref.extension.classOf
|
||||
import com.highcapable.kavaref.extension.toClassOrNull
|
||||
import com.highcapable.kavaref.resolver.ConstructorResolver
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* [MoshiCompanion] registered [JsonAdapter.Factory] implementation that loads
|
||||
* generated [JsonAdapter]s from the given [MoshiCompanion.AdapterRegistry].
|
||||
*/
|
||||
internal class AdapterRegistryFactory(private val adapterRegistry: MoshiCompanion.AdapterRegistry) : JsonAdapter.Factory {
|
||||
|
||||
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||
val rawType = type.toClassOrNull() ?: return null
|
||||
|
||||
// Check if there is annotation for `@JsonClass` and `generateAdapter = true`.
|
||||
val jsonClass = rawType.getAnnotation(classOf<JsonClass>())
|
||||
if (jsonClass == null || !jsonClass.generateAdapter) return null
|
||||
|
||||
var possiblyFoundAdapter: Class<out JsonAdapter<*>>? = null
|
||||
|
||||
return try {
|
||||
val adapterClass = adapterRegistry.adapters.firstNotNullOfOrNull { (regType, regAdapter) ->
|
||||
if (Types.equals(rawType, regType))
|
||||
regAdapter
|
||||
else null
|
||||
} ?: run {
|
||||
// Fallback to reflective lookup.
|
||||
val rawAdapterClassName = Types.generatedJsonAdapterName(rawType.name)
|
||||
rawAdapterClassName.toClassOrNull<JsonAdapter<*>>() ?: throw ClassNotFoundException()
|
||||
}
|
||||
possiblyFoundAdapter = adapterClass
|
||||
|
||||
val constructor: ConstructorResolver<out JsonAdapter<*>>
|
||||
val args: Array<Any>
|
||||
|
||||
if (type is ParameterizedType) {
|
||||
val typeArgs = type.actualTypeArguments
|
||||
|
||||
// Common case first.
|
||||
val twoParams = adapterClass.resolve()
|
||||
.optional(silent = true)
|
||||
.constructor {
|
||||
parameters(Moshi::class, Array<Type>::class)
|
||||
}.firstOrNull()
|
||||
val oneParam = adapterClass.resolve()
|
||||
.optional(silent = true)
|
||||
.constructor { parameters(Moshi::class) }
|
||||
.firstOrNull()
|
||||
|
||||
constructor = twoParams ?: oneParam ?: throw NoSuchMethodException()
|
||||
args = if (twoParams != null)
|
||||
arrayOf(moshi, typeArgs)
|
||||
else arrayOf(moshi)
|
||||
} else {
|
||||
val oneParam = adapterClass.resolve()
|
||||
.optional(silent = true)
|
||||
.constructor { parameters(Moshi::class) }
|
||||
.firstOrNull()
|
||||
val noParams = adapterClass.resolve()
|
||||
.optional(silent = true)
|
||||
.constructor { emptyParameters() }
|
||||
.firstOrNull()
|
||||
|
||||
constructor = oneParam ?: noParams ?: throw NoSuchMethodException()
|
||||
args = if (oneParam != null)
|
||||
arrayOf(moshi)
|
||||
else emptyArray()
|
||||
}
|
||||
|
||||
constructor.createQuietly(*args)?.nullSafe()
|
||||
} catch (_: ClassNotFoundException) {
|
||||
// If it is not found at all, return null and let other factory handle it.
|
||||
null
|
||||
} 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. " +
|
||||
"Target adapter class is '${possiblyFoundAdapter?.canonicalName}'.",
|
||||
e
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Failed to create JsonAdapter for $type.", e)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Moshi Companion - Companion to Moshi with more practical features.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/moshi-companion
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/2.
|
||||
*/
|
||||
package com.highcapable.moshi.companion.api
|
||||
|
||||
import com.highcapable.moshi.companion.api.MoshiCompanion.AdapterRegistry
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Moshi.Builder
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Moshi Companion core API.
|
||||
*/
|
||||
object MoshiCompanion {
|
||||
|
||||
/**
|
||||
* Add an [AdapterRegistry] on this [Moshi.Builder] instance to register all [JsonAdapter]s
|
||||
* declared in the [AdapterRegistry.adapters] map when [Moshi.Builder.build] is called.
|
||||
* @see Moshi.Builder.addRegistry
|
||||
* @param builder the instance.
|
||||
* @param adapterRegistry the [AdapterRegistry] instance to set.
|
||||
* @return [Moshi.Builder]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun addRegistry(builder: Builder, adapterRegistry: AdapterRegistry) = apply {
|
||||
builder.add(AdapterRegistryFactory(adapterRegistry))
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry of [JsonAdapter]s to be registered with a [Moshi] instance.
|
||||
*
|
||||
* To use, create a subclass and implement [adapters]. Then add an instance of your subclass on
|
||||
* [Moshi.Builder.addRegistry].
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* class MyAdapterRegistry : MoshiCompanion.AdapterRegistry {
|
||||
*
|
||||
* override val adapters = mapOf(
|
||||
* MyType::class.java to MyTypeJsonAdapter::class.java,
|
||||
* MyType.SubType::class.java to MyType_SubTypeJsonAdapter::class.java
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* val myRegistry = MyAdapterRegistry()
|
||||
*
|
||||
* val builder = Moshi.Builder().addRegistry(myRegistry)
|
||||
* val moshi = builder.build()
|
||||
* ```
|
||||
*/
|
||||
interface AdapterRegistry {
|
||||
|
||||
/**
|
||||
* A map of [Type] to [JsonAdapter] to be registered.
|
||||
*/
|
||||
val adapters: Map<Type, Class<out JsonAdapter<*>>>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an [AdapterRegistry] on this [Moshi.Builder] instance to register all [JsonAdapter]s
|
||||
* declared in the [AdapterRegistry.adapters] map when [Moshi.Builder.build] is called.
|
||||
* @see MoshiCompanion.addRegistry
|
||||
* @receiver [Moshi.Builder] instance.
|
||||
* @param adapterRegistry the [AdapterRegistry] instance to set.
|
||||
* @return [Moshi.Builder]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun Builder.addRegistry(adapterRegistry: AdapterRegistry) = apply {
|
||||
add(AdapterRegistryFactory(adapterRegistry))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON adapter for reified type parameter [T], creating it if necessary.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* val moshi = Moshi.Builder().build()
|
||||
* val adapter = moshi.typeAdapter<List<String>>()
|
||||
* ```
|
||||
* @receiver [Moshi] instance.
|
||||
* @see typeRef
|
||||
* @return [JsonAdapter]<[T]>
|
||||
*/
|
||||
inline fun <reified T : Any> Moshi.typeAdapter(): JsonAdapter<T> = adapter(typeRef<T>().type)
|
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Moshi Companion - Companion to Moshi with more practical features.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/moshi-companion
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/6.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.moshi.companion.api
|
||||
|
||||
import com.highcapable.kavaref.extension.classOf
|
||||
import com.highcapable.kavaref.extension.toClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Type reference class for getting generic parameter [T] type.
|
||||
*
|
||||
* The purpose of this class is to retain erased generics at runtime.
|
||||
* @see typeRef
|
||||
*/
|
||||
abstract class TypeRef<T : Any> {
|
||||
|
||||
/**
|
||||
* Get the generic parameter [T] type.
|
||||
* @return [Type]
|
||||
*/
|
||||
val type by lazy {
|
||||
when (val superclass = javaClass.genericSuperclass) {
|
||||
is ParameterizedType ->
|
||||
if (superclass.rawType == classOf<TypeRef<*>>())
|
||||
superclass.actualTypeArguments.firstOrNull() ?: error("Type argument cannot be null.")
|
||||
else error("Must only create direct subclasses of TypeRef.")
|
||||
classOf<TypeRef<*>>() -> error("TypeRef must be created with a type argument: object : TypeRef<...>() {}.")
|
||||
else -> error("Must only create direct subclasses of TypeRef.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw class type of the generic parameter [T].
|
||||
* @return [Class]
|
||||
*/
|
||||
val rawType by lazy {
|
||||
when (val currentType = type) {
|
||||
is Class<*> -> currentType
|
||||
is ParameterizedType -> currentType.toClass()
|
||||
else -> classOf<Any>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified [other] type can be assigned to this type.
|
||||
* @param other the type to check.
|
||||
* @return `true` if the specified type can be assigned to this type, `false` otherwise.
|
||||
*/
|
||||
fun isAssignableFrom(other: Type) = when {
|
||||
type is Class<*> && other is Class<*> -> rawType.isAssignableFrom(other)
|
||||
else -> type == other
|
||||
}
|
||||
|
||||
override fun toString() = type.toString()
|
||||
override fun equals(other: Any?) = other is TypeRef<*> && type == other.type
|
||||
override fun hashCode() = type.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [TypeRef] instance with the reified type parameter [T].
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* val typeRef = typeRef<List<String>>()
|
||||
* // This will be of type `List<String>`.
|
||||
* val type = typeRef.type
|
||||
* // This will be of type `List`.
|
||||
* val rawType = typeRef.rawType
|
||||
* ```
|
||||
* @see TypeRef
|
||||
* @return [TypeRef]<[T]>
|
||||
*/
|
||||
inline fun <reified T : Any> typeRef() = object : TypeRef<T>() {}
|
Reference in New Issue
Block a user