Parcelable: Support CharSequence, IBinder/IInterface, objects, enums. Serialize Parcelable efficiently if possible
This commit is contained in:
+10
-2
@@ -84,6 +84,8 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
|
||||
) {
|
||||
if (origin is KtClass) {
|
||||
currentClass = origin
|
||||
} else {
|
||||
currentClass = null
|
||||
}
|
||||
|
||||
currentClassName = name
|
||||
@@ -118,7 +120,13 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
|
||||
): MethodVisitor {
|
||||
if (name == "<clinit>" && currentClass != null && currentClassName != null) {
|
||||
isClinitGenerated = true
|
||||
return ClinitAwareMethodVisitor(currentClassName!!, super.newMethod(origin, access, name, desc, signature, exceptions))
|
||||
|
||||
val descriptor = bindingContext[BindingContext.CLASS, currentClass]
|
||||
if (descriptor != null && descriptor.isMagicParcelable) {
|
||||
return ClinitAwareMethodVisitor(
|
||||
currentClassName!!,
|
||||
super.newMethod(origin, access, name, desc, signature, exceptions))
|
||||
}
|
||||
}
|
||||
|
||||
return super.newMethod(origin, access, name, desc, signature, exceptions)
|
||||
@@ -129,7 +137,7 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
|
||||
override fun visitInsn(opcode: Int) {
|
||||
if (opcode == Opcodes.RETURN) {
|
||||
val iv = InstructionAdapter(this)
|
||||
val creatorName = "$parcelableName\$CREATOR"
|
||||
val creatorName = "$parcelableName\$Creator"
|
||||
val creatorType = Type.getObjectType(creatorName)
|
||||
|
||||
iv.anew(creatorType)
|
||||
|
||||
+4
-5
@@ -170,14 +170,13 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
||||
) {
|
||||
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
|
||||
|
||||
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.defaultType, "in" to parcelClassType).write(codegen) {
|
||||
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.builtIns.anyType, "in" to parcelClassType).write(codegen) {
|
||||
if (parcelerObject != null) {
|
||||
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
|
||||
|
||||
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
|
||||
v.load(1, PARCEL_TYPE)
|
||||
v.invokevirtual(companionAsmType.internalName, "create",
|
||||
"(${PARCEL_TYPE.descriptor})${containerAsmType.descriptor}", false)
|
||||
v.invokevirtual(companionAsmType.internalName, "create", "(${PARCEL_TYPE.descriptor})Landroid/os/Parcelable;", false)
|
||||
}
|
||||
else {
|
||||
v.anew(containerAsmType)
|
||||
@@ -203,7 +202,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
||||
|
||||
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) {
|
||||
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
|
||||
val creatorAsmType = Type.getObjectType(parcelableAsmType.internalName + "\$CREATOR")
|
||||
val creatorAsmType = Type.getObjectType(parcelableAsmType.internalName + "\$Creator")
|
||||
codegen.v.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_STATIC or ACC_PUBLIC or ACC_FINAL, "CREATOR",
|
||||
creatorAsmType.descriptor, null, null)
|
||||
}
|
||||
@@ -217,7 +216,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
||||
properties: List<Pair<String, KotlinType>>
|
||||
) {
|
||||
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
|
||||
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$CREATOR")
|
||||
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$Creator")
|
||||
|
||||
val creatorClass = ClassDescriptorImpl(
|
||||
parcelableClass.containingDeclaration, Name.identifier("Creator"), Modality.FINAL, ClassKind.CLASS, emptyList(),
|
||||
|
||||
+59
-1
@@ -16,9 +16,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.parcel.serializers
|
||||
|
||||
import org.jetbrains.kotlin.android.parcel.isMagicParcelable
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
@@ -98,6 +107,14 @@ interface ParcelSerializer {
|
||||
Method("writeBundle"),
|
||||
Method("readBundle"))
|
||||
|
||||
type.isIBinder() -> NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeStrongBinder", "(Landroid/os/IBinder;)V"),
|
||||
Method("readStrongBinder", "()Landroid/os/IBinder;"))
|
||||
|
||||
type.isIInterface() -> NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeStrongInterface", "(Landroid/os/IInterface;)V"),
|
||||
Method("readStrongInterface", "()Landroid/os/IInterface;"))
|
||||
|
||||
asmType.isPersistableBundle() -> NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeBundle"),
|
||||
Method("readBundle"))
|
||||
@@ -118,14 +135,42 @@ interface ParcelSerializer {
|
||||
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
|
||||
}
|
||||
|
||||
type.isCharSequence() -> CharSequenceParcelSerializer(asmType)
|
||||
|
||||
type.isException() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeException"),
|
||||
Method("readException")))
|
||||
|
||||
// Write at least a nullability byte.
|
||||
// We don't want parcel to be empty in case if all constructor parameters are objects
|
||||
type.isNamedObject() -> NullAwareParcelSerializerWrapper(ObjectParcelSerializer(asmType, type, typeMapper))
|
||||
|
||||
type.isEnum() -> wrapToNullAwareIfNeeded(type, EnumParcelSerializer(asmType))
|
||||
|
||||
asmType.isFileDescriptor() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeRawFileDescriptor"),
|
||||
Method("readRawFileDescriptor")))
|
||||
|
||||
type.isParcelable() -> {
|
||||
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
|
||||
if (clazz != null && clazz.modality == Modality.FINAL) {
|
||||
val creatorVar = clazz.staticScope.getContributedVariables(
|
||||
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
|
||||
|
||||
val creatorAsmType = when {
|
||||
creatorVar != null -> typeMapper.mapType(creatorVar.type)
|
||||
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
|
||||
else -> null
|
||||
}
|
||||
|
||||
creatorAsmType?.let { EfficientParcelableParcelSerializer(asmType, creatorAsmType) }
|
||||
?: GenericParcelableParcelSerializer(asmType)
|
||||
}
|
||||
else {
|
||||
GenericParcelableParcelSerializer(asmType)
|
||||
}
|
||||
}
|
||||
|
||||
type.isSerializable() -> NullCompliantObjectParcelSerializer(asmType,
|
||||
Method("writeSerializable"),
|
||||
Method("readSerializable"))
|
||||
@@ -151,6 +196,19 @@ interface ParcelSerializer {
|
||||
private fun Type.isSparseArray() = this.descriptor == "Landroid/util/SparseArray;"
|
||||
private fun KotlinType.isSerializable() = matchesFqNameWithSupertypes("java.io.Serializable")
|
||||
private fun KotlinType.isException() = matchesFqNameWithSupertypes("java.lang.Exception")
|
||||
private fun KotlinType.isIBinder() = matchesFqNameWithSupertypes("android.os.IBinder")
|
||||
private fun KotlinType.isIInterface() = matchesFqNameWithSupertypes("android.os.IInterface")
|
||||
private fun KotlinType.isParcelable() = matchesFqNameWithSupertypes("android.os.Parcelable")
|
||||
private fun KotlinType.isCharSequence() = matchesFqName("kotlin.CharSequence") || matchesFqName("java.lang.CharSequence")
|
||||
|
||||
private fun KotlinType.isNamedObject(): Boolean {
|
||||
val classDescriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return false
|
||||
if (!classDescriptor.visibility.isVisibleOutside()) return false
|
||||
if (DescriptorUtils.isAnonymousObject(classDescriptor)) return false
|
||||
return classDescriptor.kind == ClassKind.OBJECT
|
||||
}
|
||||
|
||||
private fun KotlinType.isEnum() = (constructor.declarationDescriptor as? ClassDescriptor)?.kind == ClassKind.ENUM_CLASS
|
||||
|
||||
private fun Type.isPrimitive(): Boolean = when (this.sort) {
|
||||
Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT, Type.FLOAT, Type.LONG, Type.DOUBLE -> true
|
||||
@@ -174,7 +232,7 @@ interface ParcelSerializer {
|
||||
return true
|
||||
}
|
||||
|
||||
return this.constructor.supertypes.any { it.matchesFqName(fqName) }
|
||||
return TypeUtils.getAllSupertypes(this).any { it.matchesFqName(fqName) }
|
||||
}
|
||||
|
||||
private fun KotlinType.matchesFqName(fqName: String): Boolean {
|
||||
|
||||
+91
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.parcel.serializers
|
||||
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
@@ -383,6 +386,94 @@ internal class SparseArrayParcelSerializer(override val asmType: Type, private v
|
||||
}
|
||||
}
|
||||
|
||||
internal class ObjectParcelSerializer(
|
||||
override val asmType: Type,
|
||||
private val type: KotlinType,
|
||||
private val typeMapper: KotlinTypeMapper
|
||||
) : ParcelSerializer {
|
||||
override fun writeValue(v: InstructionAdapter) {
|
||||
v.pop2()
|
||||
}
|
||||
|
||||
override fun readValue(v: InstructionAdapter) {
|
||||
v.pop()
|
||||
|
||||
// Handle companion object
|
||||
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
|
||||
if (clazz != null && clazz.isCompanionObject) {
|
||||
val outerClass = clazz.containingDeclaration as? ClassDescriptor
|
||||
if (outerClass != null) {
|
||||
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v.getstatic(asmType.internalName, "INSTANCE", asmType.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
internal class EnumParcelSerializer(override val asmType: Type) : ParcelSerializer {
|
||||
override fun writeValue(v: InstructionAdapter) {
|
||||
v.invokevirtual(asmType.internalName, "name", "()Ljava/lang/String;", false)
|
||||
v.invokevirtual(PARCEL_TYPE.internalName, "writeString", "(Ljava/lang/String;)V", false)
|
||||
}
|
||||
|
||||
override fun readValue(v: InstructionAdapter) {
|
||||
v.invokevirtual(PARCEL_TYPE.internalName, "readString", "()Ljava/lang/String;", false)
|
||||
v.invokestatic(asmType.internalName, "valueOf", "(Ljava/lang/String;)${asmType.descriptor}", false)
|
||||
}
|
||||
}
|
||||
|
||||
internal class CharSequenceParcelSerializer(override val asmType: Type) : ParcelSerializer {
|
||||
override fun writeValue(v: InstructionAdapter) {
|
||||
// -> parcel, seq
|
||||
v.swap() // -> seq, parcel
|
||||
v.aconst(0) // -> seq, parcel, flags
|
||||
v.invokestatic("android/text/TextUtils", "writeToParcel", "(Ljava/lang/CharSequence;Landroid/os/Parcel;I)V", false)
|
||||
}
|
||||
|
||||
override fun readValue(v: InstructionAdapter) {
|
||||
// -> parcel
|
||||
v.getstatic("android/text/TextUtils", "CHAR_SEQUENCE_CREATOR", "Landroid/os/Parcelable\$Creator;") // -> parcel, creator
|
||||
v.swap() // -> creator, parcel
|
||||
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
|
||||
v.castIfNeeded(asmType)
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfficientParcelableParcelSerializer(override val asmType: Type, private val creatorAsmType: Type) : ParcelSerializer {
|
||||
override fun writeValue(v: InstructionAdapter) {
|
||||
// -> parcel, parcelable
|
||||
v.swap() // -> parcelable, parcel
|
||||
v.aconst(0) // -> parcelable, parcel, flags
|
||||
v.invokeinterface("android/os/Parcelable", "writeToParcel", "(Landroid/os/Parcel;I)V")
|
||||
}
|
||||
|
||||
override fun readValue(v: InstructionAdapter) {
|
||||
// -> parcel
|
||||
v.getstatic(asmType.internalName, "CREATOR", creatorAsmType.descriptor) // -> parcel, creator
|
||||
v.swap() // -> creator, parcel
|
||||
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
|
||||
v.castIfNeeded(asmType)
|
||||
}
|
||||
}
|
||||
|
||||
internal class GenericParcelableParcelSerializer(override val asmType: Type) : ParcelSerializer {
|
||||
override fun writeValue(v: InstructionAdapter) {
|
||||
// -> parcel, parcelable
|
||||
v.aconst(0) // -> parcel, parcelable, flags
|
||||
v.invokevirtual(PARCEL_TYPE.internalName, "writeParcelable", "(Landroid/os/Parcelable;I)V", false)
|
||||
}
|
||||
|
||||
override fun readValue(v: InstructionAdapter) {
|
||||
// -> parcel
|
||||
v.aconst(asmType) // -> parcel, type
|
||||
v.invokevirtual("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false) // -> parcel, classloader
|
||||
v.invokevirtual(PARCEL_TYPE.internalName, "readParcelable", "(Ljava/lang/ClassLoader;)Landroid/os/Parcelable;", false)
|
||||
v.castIfNeeded(asmType)
|
||||
}
|
||||
}
|
||||
|
||||
internal class NullAwareParcelSerializerWrapper(val delegate: ParcelSerializer) : ParcelSerializer {
|
||||
override val asmType: Type
|
||||
get() = delegate.asmType
|
||||
|
||||
Reference in New Issue
Block a user