CORDA-3745: Modify DJVM serializers to support Enum Evolution. ()

This commit is contained in:
Chris Rankin 2020-04-30 14:59:10 +01:00 committed by GitHub
parent 2ce76e407d
commit 107819f5b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 240 additions and 57 deletions

View File

@ -4,6 +4,10 @@ import net.corda.core.internal.BasicVerifier
import net.corda.core.internal.Verifier import net.corda.core.internal.Verifier
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
import net.corda.core.serialization.CordaSerializationTransformRenames
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -38,6 +42,10 @@ class DeterministicVerifierFactoryService(
whitelist = Whitelist.MINIMAL, whitelist = Whitelist.MINIMAL,
visibleAnnotations = setOf( visibleAnnotations = setOf(
CordaSerializable::class.java, CordaSerializable::class.java,
CordaSerializationTransformEnumDefault::class.java,
CordaSerializationTransformEnumDefaults::class.java,
CordaSerializationTransformRename::class.java,
CordaSerializationTransformRenames::class.java,
ConstructorForDeserialization::class.java, ConstructorForDeserialization::class.java,
DeprecatedConstructorForDeserialization::class.java DeprecatedConstructorForDeserialization::class.java
), ),

View File

@ -98,7 +98,8 @@ class SandboxSerializerFactoryFactory(
localSerializerFactory = localSerializerFactory, localSerializerFactory = localSerializerFactory,
classLoader = classLoader, classLoader = classLoader,
mustPreserveDataWhenEvolving = context.preventDataLoss, mustPreserveDataWhenEvolving = context.preventDataLoss,
primitiveTypes = primitiveTypes primitiveTypes = primitiveTypes,
baseTypes = localTypes
) )
val remoteSerializerFactory = DefaultRemoteSerializerFactory( val remoteSerializerFactory = DefaultRemoteSerializerFactory(

View File

@ -61,8 +61,9 @@ fun createSandboxSerializationEnv(
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>> val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>>
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
val enumConstants = taskFactory.apply(DescribeEnum::class.java) val enumConstants = taskFactory.apply(DescribeEnum::class.java) as Function<Class<*>, Array<out Any>>
.andThen(taskFactory.apply(GetEnumNames::class.java)) @Suppress("unchecked_cast")
val enumConstantNames = enumConstants.andThen(taskFactory.apply(GetEnumNames::class.java))
.andThen { (it as Array<out Any>).map(Any::toString) } as Function<Class<*>, List<String>> .andThen { (it as Array<out Any>).map(Any::toString) } as Function<Class<*>, List<String>>
val sandboxLocalTypes = BaseLocalTypes( val sandboxLocalTypes = BaseLocalTypes(
@ -72,7 +73,8 @@ fun createSandboxSerializationEnv(
mapClass = classLoader.toSandboxClass(Map::class.java), mapClass = classLoader.toSandboxClass(Map::class.java),
stringClass = classLoader.toSandboxClass(String::class.java), stringClass = classLoader.toSandboxClass(String::class.java),
isEnum = isEnumPredicate, isEnum = isEnumPredicate,
enumConstants = enumConstants enumConstants = enumConstants,
enumConstantNames = enumConstantNames
) )
val schemeBuilder = SandboxSerializationSchemeBuilder( val schemeBuilder = SandboxSerializationSchemeBuilder(
classLoader = classLoader, classLoader = classLoader,

View File

@ -0,0 +1,155 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
import net.corda.core.serialization.CordaSerializationTransformRenames
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.serialization.serialize
import net.corda.serialization.djvm.EvolvedEnum.ONE
import net.corda.serialization.djvm.EvolvedEnum.TWO
import net.corda.serialization.djvm.EvolvedEnum.THREE
import net.corda.serialization.djvm.EvolvedEnum.FOUR
import net.corda.serialization.djvm.OriginalEnum.One
import net.corda.serialization.djvm.OriginalEnum.Two
import net.corda.serialization.djvm.SandboxType.KOTLIN
import net.corda.serialization.internal.amqp.CompositeType
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.RestrictedType
import net.corda.serialization.internal.amqp.Transform
import net.corda.serialization.internal.amqp.TransformTypes
import net.corda.serialization.internal.amqp.TypeNotation
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.fail
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.ArgumentsProvider
import org.junit.jupiter.params.provider.ArgumentsSource
import java.util.EnumMap
import java.util.function.Function
import java.util.stream.Stream
@ExtendWith(LocalSerialization::class)
class DeserializeEnumWithEvolutionTest : TestBase(KOTLIN) {
class EvolutionArgumentProvider : ArgumentsProvider {
override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
return Stream.of(
Arguments.of(ONE, One),
Arguments.of(TWO, Two),
Arguments.of(THREE, One),
Arguments.of(FOUR, Two)
)
}
}
private fun String.devolve() = replace("Evolved", "Original")
private fun devolveType(type: TypeNotation): TypeNotation {
return when (type) {
is CompositeType -> type.copy(
name = type.name.devolve(),
fields = type.fields.map { it.copy(type = it.type.devolve()) }
)
is RestrictedType -> type.copy(name = type.name.devolve())
else -> type
}
}
private fun SerializedBytes<*>.devolve(context: SerializationContext): SerializedBytes<Any> {
val envelope = DeserializationInput.getEnvelope(this, context.encodingWhitelist).apply {
val schemaTypes = schema.types.map(::devolveType)
with(schema.types as MutableList<TypeNotation>) {
clear()
addAll(schemaTypes)
}
val transforms = transformsSchema.types.asSequence().associateTo(LinkedHashMap()) {
it.key.devolve() to it.value
}
with(transformsSchema.types as MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
clear()
putAll(transforms)
}
}
return SerializedBytes(envelope.write())
}
@ParameterizedTest
@ArgumentsSource(EvolutionArgumentProvider::class)
fun `test deserialising evolved enum`(value: EvolvedEnum, expected: OriginalEnum) {
val context = (_contextSerializationEnv.get() ?: fail("No serialization environment!")).p2pContext
val evolvedData = value.serialize()
val originalData = evolvedData.devolve(context)
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
val sandboxOriginal = originalData.deserializeFor(classLoader)
assertEquals("sandbox." + OriginalEnum::class.java.name, sandboxOriginal::class.java.name)
assertEquals(expected.toString(), sandboxOriginal.toString())
}
}
@ParameterizedTest
@ArgumentsSource(EvolutionArgumentProvider::class)
fun `test deserialising data with evolved enum`(value: EvolvedEnum, expected: OriginalEnum) {
val context = (_contextSerializationEnv.get() ?: fail("No serialization environment!")).p2pContext
val evolvedData = EvolvedData(value).serialize()
val originalData = evolvedData.devolve(context)
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
val sandboxOriginal = originalData.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val result = taskFactory.compose(classLoader.createSandboxFunction())
.apply(ShowOriginalData::class.java)
.apply(sandboxOriginal) ?: fail("Result cannot be null")
assertThat(result.toString())
.isEqualTo(ShowOriginalData().apply(OriginalData(expected)))
}
}
class ShowOriginalData : Function<OriginalData, String> {
override fun apply(input: OriginalData): String {
return with(input) {
"Name='${value.name}', Ordinal='${value.ordinal}'"
}
}
}
}
@CordaSerializable
enum class OriginalEnum {
One,
Two
}
@CordaSerializable
data class OriginalData(val value: OriginalEnum)
@CordaSerializable
@CordaSerializationTransformRenames(
CordaSerializationTransformRename(from = "One", to = "ONE"),
CordaSerializationTransformRename(from = "Two", to = "TWO")
)
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault(new = "THREE", old = "One"),
CordaSerializationTransformEnumDefault(new = "FOUR", old = "Two")
)
enum class EvolvedEnum {
ONE,
TWO,
THREE,
FOUR
}
@CordaSerializable
data class EvolvedData(val value: EvolvedEnum)

View File

@ -4,22 +4,14 @@ import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.serialization.djvm.SandboxType.KOTLIN import net.corda.serialization.djvm.SandboxType.KOTLIN
import net.corda.serialization.internal.SectionId
import net.corda.serialization.internal.amqp.CompositeType import net.corda.serialization.internal.amqp.CompositeType
import net.corda.serialization.internal.amqp.DeserializationInput import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.Envelope
import net.corda.serialization.internal.amqp.TypeNotation import net.corda.serialization.internal.amqp.TypeNotation
import net.corda.serialization.internal.amqp.alsoAsByteBuffer
import net.corda.serialization.internal.amqp.amqpMagic
import net.corda.serialization.internal.amqp.withDescribed
import net.corda.serialization.internal.amqp.withList
import org.apache.qpid.proton.codec.Data
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.fail import org.junit.jupiter.api.fail
import java.io.ByteArrayOutputStream
import java.util.function.Function import java.util.function.Function
@ExtendWith(LocalSerialization::class) @ExtendWith(LocalSerialization::class)
@ -37,12 +29,8 @@ class SafeDeserialisationTest : TestBase(KOTLIN) {
val innocentData = innocent.serialize() val innocentData = innocent.serialize()
val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply { val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply {
val innocentType = schema.types[0] as CompositeType val innocentType = schema.types[0] as CompositeType
(schema.types as MutableList<TypeNotation>)[0] = CompositeType( (schema.types as MutableList<TypeNotation>)[0] = innocentType.copy(
name = innocentType.name.replace("Innocent", "VeryEvil"), name = innocentType.name.replace("Innocent", "VeryEvil")
label = innocentType.label,
provides = innocentType.provides,
descriptor = innocentType.descriptor,
fields = innocentType.fields
) )
} }
val evilData = SerializedBytes<Any>(envelope.write()) val evilData = SerializedBytes<Any>(envelope.write())
@ -68,23 +56,6 @@ class SafeDeserialisationTest : TestBase(KOTLIN) {
} }
} }
private fun Envelope.write(): ByteArray {
val data = Data.Factory.create()
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
withList {
putObject(obj)
putObject(schema)
putObject(transformsSchema)
}
}
return ByteArrayOutputStream().use {
amqpMagic.writeTo(it)
SectionId.DATA_AND_STOP.writeTo(it)
it.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode)
it.toByteArray()
}
}
class ShowInnocentData : Function<InnocentData, String> { class ShowInnocentData : Function<InnocentData, String> {
override fun apply(data: InnocentData): String { override fun apply(data: InnocentData): String {
return "${data::class.java.name}: ${data.message}, ${data.number}" return "${data::class.java.name}: ${data.message}, ${data.number}"

View File

@ -2,6 +2,10 @@ package net.corda.serialization.djvm
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
import net.corda.core.serialization.CordaSerializationTransformRenames
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.djvm.SandboxConfiguration import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.SandboxRuntimeContext import net.corda.djvm.SandboxRuntimeContext
@ -51,6 +55,10 @@ abstract class TestBase(type: SandboxType) {
whitelist = MINIMAL, whitelist = MINIMAL,
visibleAnnotations = setOf( visibleAnnotations = setOf(
CordaSerializable::class.java, CordaSerializable::class.java,
CordaSerializationTransformEnumDefault::class.java,
CordaSerializationTransformEnumDefaults::class.java,
CordaSerializationTransformRename::class.java,
CordaSerializationTransformRenames::class.java,
ConstructorForDeserialization::class.java, ConstructorForDeserialization::class.java,
DeprecatedConstructorForDeserialization::class.java DeprecatedConstructorForDeserialization::class.java
), ),

View File

@ -0,0 +1,28 @@
@file:JvmName("TestHelpers")
package net.corda.serialization.djvm
import net.corda.serialization.internal.SectionId
import net.corda.serialization.internal.amqp.Envelope
import net.corda.serialization.internal.amqp.alsoAsByteBuffer
import net.corda.serialization.internal.amqp.amqpMagic
import net.corda.serialization.internal.amqp.withDescribed
import net.corda.serialization.internal.amqp.withList
import org.apache.qpid.proton.codec.Data
import java.io.ByteArrayOutputStream
fun Envelope.write(): ByteArray {
val data = Data.Factory.create()
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
withList {
putObject(obj)
putObject(schema)
putObject(transformsSchema)
}
}
return ByteArrayOutputStream().use {
amqpMagic.writeTo(it)
SectionId.DATA_AND_STOP.writeTo(it)
it.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode)
it.toByteArray()
}
}

View File

@ -1,6 +1,7 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.model.BaseLocalTypes
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.UnsupportedOperationException import java.lang.UnsupportedOperationException
import java.lang.reflect.Type import java.lang.reflect.Type
@ -34,6 +35,7 @@ import java.lang.reflect.Type
class EnumEvolutionSerializer( class EnumEvolutionSerializer(
override val type: Type, override val type: Type,
factory: LocalSerializerFactory, factory: LocalSerializerFactory,
private val baseLocalTypes: BaseLocalTypes,
private val conversions: Map<String, String>, private val conversions: Map<String, String>,
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> { private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
override val typeDescriptor = factory.createDescriptor(type) override val typeDescriptor = factory.createDescriptor(type)
@ -46,7 +48,7 @@ class EnumEvolutionSerializer(
val converted = conversions[enumName] ?: throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName") val converted = conversions[enumName] ?: throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")
val ordinal = ordinals[converted] ?: throw AMQPNotSerializableException(type, "Ordinal not found for enum value $type::$converted") val ordinal = ordinals[converted] ?: throw AMQPNotSerializableException(type, "Ordinal not found for enum value $type::$converted")
return type.asClass().enumConstants[ordinal] return baseLocalTypes.enumConstants.apply(type.asClass())[ordinal]
} }
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {

View File

@ -40,7 +40,8 @@ class DefaultEvolutionSerializerFactory(
private val localSerializerFactory: LocalSerializerFactory, private val localSerializerFactory: LocalSerializerFactory,
private val classLoader: ClassLoader, private val classLoader: ClassLoader,
private val mustPreserveDataWhenEvolving: Boolean, private val mustPreserveDataWhenEvolving: Boolean,
override val primitiveTypes: Map<Class<*>, Class<*>> override val primitiveTypes: Map<Class<*>, Class<*>>,
private val baseTypes: BaseLocalTypes
): EvolutionSerializerFactory { ): EvolutionSerializerFactory {
// Invert the "primitive -> boxed primitive" mapping. // Invert the "primitive -> boxed primitive" mapping.
private val primitiveBoxedTypes: Map<Class<*>, Class<*>> private val primitiveBoxedTypes: Map<Class<*>, Class<*>>
@ -172,7 +173,7 @@ class DefaultEvolutionSerializerFactory(
if (constantsAreReordered(localOrdinals, convertedOrdinals)) throw EvolutionSerializationException(this, if (constantsAreReordered(localOrdinals, convertedOrdinals)) throw EvolutionSerializationException(this,
"Constants have been reordered, additions must be appended to the end") "Constants have been reordered, additions must be appended to the end")
return EnumEvolutionSerializer(localTypeInformation.observedType, localSerializerFactory, conversions, localOrdinals) return EnumEvolutionSerializer(localTypeInformation.observedType, localSerializerFactory, baseTypes, conversions, localOrdinals)
} }
private fun constantsAreReordered(localOrdinals: Map<String, Int>, convertedOrdinals: Map<Int, String>): Boolean = private fun constantsAreReordered(localOrdinals: Map<String, Int>, convertedOrdinals: Map<Int, String>): Boolean =

View File

@ -97,10 +97,8 @@ object SerializerFactoryBuilder {
mustPreserveDataWhenEvolving: Boolean): SerializerFactory { mustPreserveDataWhenEvolving: Boolean): SerializerFactory {
val customSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry) val customSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry)
val localTypeModel = ConfigurableLocalTypeModel( val typeModelConfiguration = WhitelistBasedTypeModelConfiguration(whitelist, customSerializerRegistry)
WhitelistBasedTypeModelConfiguration( val localTypeModel = ConfigurableLocalTypeModel(typeModelConfiguration)
whitelist,
customSerializerRegistry))
val fingerPrinter = overrideFingerPrinter ?: val fingerPrinter = overrideFingerPrinter ?:
TypeModellingFingerPrinter(customSerializerRegistry) TypeModellingFingerPrinter(customSerializerRegistry)
@ -124,7 +122,8 @@ object SerializerFactoryBuilder {
localSerializerFactory, localSerializerFactory,
classCarpenter.classloader, classCarpenter.classloader,
mustPreserveDataWhenEvolving, mustPreserveDataWhenEvolving,
javaPrimitiveTypes javaPrimitiveTypes,
typeModelConfiguration.baseTypes
) else NoEvolutionSerializerFactory ) else NoEvolutionSerializerFactory
val remoteSerializerFactory = DefaultRemoteSerializerFactory( val remoteSerializerFactory = DefaultRemoteSerializerFactory(

View File

@ -46,7 +46,7 @@ abstract class Transform : DescribedType {
* descendants of this class * descendants of this class
*/ */
override fun newInstance(obj: Any?): Transform { override fun newInstance(obj: Any?): Transform {
val described = Transform.checkDescribed(obj) as List<*> val described = checkDescribed(obj) as List<*>
return when (described[0]) { return when (described[0]) {
EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described) EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described)
RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described) RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described)
@ -195,18 +195,24 @@ object TransformsAnnotationProcessor {
* Obtain all of the transforms applied for the given [Class]. * Obtain all of the transforms applied for the given [Class].
*/ */
fun getTransformsSchema(type: Class<*>): TransformsMap { fun getTransformsSchema(type: Class<*>): TransformsMap {
val result = TransformsMap(TransformTypes::class.java) return when {
// We only have transforms for enums at present. // This only detects Enum classes that are outside the DJVM sandbox.
if (!type.isEnum) return result type.isEnum -> getEnumTransformsSchema(type)
// We only have transforms for enums at present.
else -> TransformsMap(TransformTypes::class.java)
}
}
fun getEnumTransformsSchema(type: Class<*>): TransformsMap {
val result = TransformsMap(TransformTypes::class.java)
supportedTransforms.forEach { supportedTransform -> supportedTransforms.forEach { supportedTransform ->
val annotationContainer = type.getAnnotation(supportedTransform.type) ?: return@forEach val annotationContainer = type.getAnnotation(supportedTransform.type) ?: return@forEach
result.processAnnotations( result.processAnnotations(
type, type,
supportedTransform.enum, supportedTransform.enum,
supportedTransform.getAnnotations(annotationContainer)) supportedTransform.getAnnotations(annotationContainer))
} }
return result return result
} }

View File

@ -61,7 +61,8 @@ private val DEFAULT_BASE_TYPES = BaseLocalTypes(
mapClass = Map::class.java, mapClass = Map::class.java,
stringClass = String::class.java, stringClass = String::class.java,
isEnum = Predicate { clazz -> clazz.isEnum }, isEnum = Predicate { clazz -> clazz.isEnum },
enumConstants = Function { clazz -> enumConstants = Function { clazz -> clazz.enumConstants },
enumConstantNames = Function { clazz ->
(clazz as Class<out Enum<*>>).enumConstants.map(Enum<*>::name) (clazz as Class<out Enum<*>>).enumConstants.map(Enum<*>::name)
} }
) )

View File

@ -115,13 +115,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown) baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown)
type === baseTypes.stringClass -> Atomic(type, typeIdentifier) type === baseTypes.stringClass -> Atomic(type, typeIdentifier)
type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier) type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier)
baseTypes.isEnum.test(type) -> baseTypes.enumConstants.apply(type).let { enumConstants -> baseTypes.isEnum.test(type) -> baseTypes.enumConstantNames.apply(type).let { enumConstantNames ->
AnEnum( AnEnum(
type, type,
typeIdentifier, typeIdentifier,
enumConstants, enumConstantNames,
buildInterfaceInformation(type), buildInterfaceInformation(type),
getEnumTransforms(type, enumConstants) getEnumTransforms(type, enumConstantNames)
) )
} }
type.kotlinObjectInstance != null -> Singleton( type.kotlinObjectInstance != null -> Singleton(
@ -145,7 +145,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
private fun getEnumTransforms(type: Class<*>, enumConstants: List<String>): EnumTransforms { private fun getEnumTransforms(type: Class<*>, enumConstants: List<String>): EnumTransforms {
try { try {
val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant to index }.toMap() val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant to index }.toMap()
return EnumTransforms.build(TransformsAnnotationProcessor.getTransformsSchema(type), constants) return EnumTransforms.build(TransformsAnnotationProcessor.getEnumTransformsSchema(type), constants)
} catch (e: InvalidEnumTransformsException) { } catch (e: InvalidEnumTransformsException) {
throw NotSerializableDetailedException(type.name, e.message!!) throw NotSerializableDetailedException(type.name, e.message!!)
} }

View File

@ -136,5 +136,6 @@ class BaseLocalTypes(
val mapClass: Class<*>, val mapClass: Class<*>,
val stringClass: Class<*>, val stringClass: Class<*>,
val isEnum: Predicate<Class<*>>, val isEnum: Predicate<Class<*>>,
val enumConstants: Function<Class<*>, List<String>> val enumConstants: Function<Class<*>, Array<out Any>>,
val enumConstantNames: Function<Class<*>, List<String>>
) )