Parcelable: Support CharSequence, IBinder/IInterface, objects, enums. Serialize Parcelable efficiently if possible

This commit is contained in:
Yan Zhulanow
2017-06-30 22:39:30 +03:00
parent 19eb30b3ae
commit 4200629347
23 changed files with 442 additions and 94 deletions
@@ -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)
@@ -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(),
@@ -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 {
@@ -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