Parcelable: Support Parcelizer interface in order to be able to customize serialization

This commit is contained in:
Yan Zhulanow
2017-06-29 22:57:07 +03:00
parent aa5f9ee3ec
commit 3062e72282
11 changed files with 386 additions and 23 deletions
@@ -16,9 +16,12 @@
package org.jetbrains.kotlin.android.parcel
import kotlinx.android.parcel.Parceler
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.android.parcel.serializers.PARCEL_TYPE
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
@@ -33,6 +36,7 @@ import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.context.ClassContext
import org.jetbrains.kotlin.codegen.writeSyntheticClassMetadata
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorFactory
@@ -40,6 +44,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
@@ -48,6 +53,9 @@ import java.io.FileDescriptor
class ParcelableCodegenExtension : ExpressionCodegenExtension {
private companion object {
private val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
private val PARCELER_FQNAME = FqName(Parceler::class.java.canonicalName)
fun KotlinType.isParceler() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQNAME
}
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
@@ -60,32 +68,54 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
val parcelClassType = ParcelableResolveExtension.resolveParcelClassType(parcelableClass.module)
val parcelAsmType = codegen.typeMapper.mapType(parcelClassType)
val parcelerObject = parcelableClass.companionObjectDescriptor?.takeIf {
TypeUtils.getAllSupertypes(it.defaultType).any { it.isParceler() }
}
with (parcelableClass) {
writeDescribeContentsFunction(codegen, propertiesToSerialize)
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType)
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType, parcelerObject)
}
writeCreatorAccessField(codegen, parcelableClass)
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, propertiesToSerialize)
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, parcelerObject, propertiesToSerialize)
}
private fun getCompanionClassType(containerAsmType: Type, parcelerObject: ClassDescriptor): Pair<Type, String> {
val shortName = parcelerObject.name
return Pair(Type.getObjectType(containerAsmType.internalName + "\$$shortName"), shortName.asString())
}
private fun ClassDescriptor.writeWriteToParcel(
codegen: ImplementationBodyCodegen,
properties: List<Pair<String, KotlinType>>,
parcelAsmType: Type
parcelAsmType: Type,
parcelerObject: ClassDescriptor?
): Unit? {
val containerAsmType = codegen.typeMapper.mapType(this.defaultType)
return findFunction(WRITE_TO_PARCEL)?.write(codegen) {
for ((fieldName, type) in properties) {
val asmType = codegen.typeMapper.mapType(type)
if (parcelerObject != null) {
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
v.load(1, parcelAsmType)
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(0, containerAsmType)
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
v.load(1, PARCEL_TYPE)
v.load(2, Type.INT_TYPE)
v.invokevirtual(companionAsmType.internalName, "write",
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false)
}
else {
for ((fieldName, type) in properties) {
val asmType = codegen.typeMapper.mapType(type)
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
serializer.writeValue(v)
v.load(1, parcelAsmType)
v.load(0, containerAsmType)
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
serializer.writeValue(v)
}
}
v.areturn(Type.VOID_TYPE)
@@ -135,26 +165,38 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
creatorClass: ClassDescriptorImpl,
parcelClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<Pair<String, KotlinType>>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.defaultType, "in" to parcelClassType).write(codegen) {
v.anew(containerAsmType)
v.dup()
if (parcelerObject != null) {
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
val asmConstructorParameters = StringBuilder()
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(1, PARCEL_TYPE)
v.invokevirtual(companionAsmType.internalName, "create",
"(${PARCEL_TYPE.descriptor})${containerAsmType.descriptor}", false)
}
else {
v.anew(containerAsmType)
v.dup()
for ((_, type) in properties) {
val asmType = codegen.typeMapper.mapType(type)
asmConstructorParameters.append(asmType.descriptor)
val asmConstructorParameters = StringBuilder()
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
v.load(1, parcelAsmType)
serializer.readValue(v)
for ((_, type) in properties) {
val asmType = codegen.typeMapper.mapType(type)
asmConstructorParameters.append(asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
v.load(1, parcelAsmType)
serializer.readValue(v)
}
v.invokespecial(containerAsmType.internalName, "<init>", "($asmConstructorParameters)V", false)
}
v.invokespecial(containerAsmType.internalName, "<init>", "($asmConstructorParameters)V", false)
v.areturn(containerAsmType)
}
}
@@ -171,6 +213,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
parcelableClass: ClassDescriptor,
parcelClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<Pair<String, KotlinType>>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
@@ -201,8 +244,8 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
writeSyntheticClassMetadata(classBuilderForCreator, codegen.state)
writeCreatorConstructor(codegenForCreator, creatorClass, creatorAsmType)
writeNewArrayMethod(codegenForCreator, parcelableClass, creatorClass)
writeCreateFromParcel(codegenForCreator, parcelableClass, creatorClass, parcelClassType, parcelAsmType, properties)
writeNewArrayMethod(codegenForCreator, parcelableClass, creatorClass, parcelerObject)
writeCreateFromParcel(codegenForCreator, parcelableClass, creatorClass, parcelClassType, parcelAsmType, parcelerObject, properties)
classBuilderForCreator.done()
}
@@ -221,7 +264,8 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
private fun writeNewArrayMethod(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
creatorClass: ClassDescriptorImpl
creatorClass: ClassDescriptorImpl,
parcelerObject: ClassDescriptor?
) {
val builtIns = parcelableClass.builtIns
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass)
@@ -230,6 +274,29 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
builtIns.getArrayType(Variance.INVARIANT, parcelableClass.defaultType),
"size" to builtIns.intType
).write(codegen) {
if (parcelerObject != null) {
val newArrayMethod = parcelerObject.unsubstitutedMemberScope
.getContributedFunctions(Name.identifier("newArray"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS)
.filter {
it.typeParameters.isEmpty()
&& it.kind == CallableMemberDescriptor.Kind.DECLARATION
&& (it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type))
&& !((it.containingDeclaration as? ClassDescriptor)?.defaultType?.isParceler() ?: true)
}.firstOrNull()
if (newArrayMethod != null) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(1, Type.INT_TYPE)
v.invokevirtual(companionAsmType.internalName, "newArray", "(I)[${containerAsmType.descriptor}", false)
v.areturn(Type.getType("[L$parcelableAsmType;"))
return@write
}
}
v.load(1, Type.INT_TYPE)
v.newarray(parcelableAsmType)
v.areturn(Type.getType("[L$parcelableAsmType;"))
@@ -20,7 +20,7 @@ import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
private val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
internal val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
internal object GenericParcelSerializer : ParcelSerializer {
override val asmType: Type = Type.getObjectType("java/lang/Object")
@@ -0,0 +1,34 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(secondName)
}
override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), 0)
}
}
fun box() = parcelTest { parcel ->
val user = User("John", "Smith", 20)
user.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val user2 = readFromParcel<User>(parcel)
assert(user.firstName == user2.firstName)
assert(user.secondName == user2.secondName)
assert(user2.age == 0)
}
@@ -0,0 +1,18 @@
// CURIOUS_ABOUT writeToParcel, createFromParcel, newArray
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
parcel.writeInt(age)
}
override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), parcel.readInt())
}
}
@@ -0,0 +1,96 @@
public static class User$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final User createFromParcel(android.os.Parcel p0) {
LABEL (L0)
ALOAD (1)
LDC (in)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
GETSTATIC (Companion, LUser$Companion;)
ALOAD (1)
INVOKEVIRTUAL (User$Companion, create, (Landroid/os/Parcel;)LUser;)
ARETURN
LABEL (L1)
}
public final User[] newArray(int p0) {
LABEL (L0)
ILOAD (1)
ANEWARRAY
ARETURN
LABEL (L1)
}
}
final class User$Companion : java/lang/Object, kotlinx/android/parcel/Parceler {
private void <init>()
public void <init>(kotlin.jvm.internal.DefaultConstructorMarker p0)
public User create(android.os.Parcel p0)
public android.os.Parcelable create(android.os.Parcel p0)
public User[] newArray(int p0) {
LABEL (L0)
LINENUMBER (9)
ALOAD (0)
ILOAD (1)
INVOKESTATIC (kotlinx/android/parcel/Parceler$DefaultImpls, newArray, (Lkotlinx/android/parcel/Parceler;I)[Landroid/os/Parcelable;)
CHECKCAST
ARETURN
LABEL (L1)
}
public android.os.Parcelable[] newArray(int p0) {
LABEL (L0)
LINENUMBER (9)
ALOAD (0)
ILOAD (1)
INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;)
CHECKCAST
ARETURN
}
public void write(User p0, android.os.Parcel p1, int p2)
public void write(android.os.Parcelable p0, android.os.Parcel p1, int p2)
}
public final class User : java/lang/Object, android/os/Parcelable {
public final static User$CREATOR CREATOR
public final static User$Companion Companion
private final int age
private final java.lang.String firstName
private final java.lang.String lastName
static void <clinit>()
public void <init>(java.lang.String p0, java.lang.String p1, int p2)
public final int describeContents()
public final int getAge()
public final java.lang.String getFirstName()
public final java.lang.String getLastName()
public final void writeToParcel(android.os.Parcel p0, int p1) {
LABEL (L0)
ALOAD (1)
LDC (parcel)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
GETSTATIC (Companion, LUser$Companion;)
ALOAD (0)
ALOAD (1)
ILOAD (2)
INVOKEVIRTUAL (User$Companion, write, (LUser;Landroid/os/Parcel;I)V)
RETURN
LABEL (L1)
}
}
@@ -0,0 +1,20 @@
// CURIOUS_ABOUT newArray
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
parcel.writeInt(age)
}
override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), parcel.readInt())
override fun newArray(size: Int) = arrayOfNulls<User>(size) as Array<User>
}
}
@@ -0,0 +1,73 @@
public static class User$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final User createFromParcel(android.os.Parcel p0)
public final User[] newArray(int p0) {
LABEL (L0)
GETSTATIC (Companion, LUser$Companion;)
ILOAD (1)
INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;)
ARETURN
LABEL (L1)
}
}
final class User$Companion : java/lang/Object, kotlinx/android/parcel/Parceler {
private void <init>()
public void <init>(kotlin.jvm.internal.DefaultConstructorMarker p0)
public User create(android.os.Parcel p0)
public android.os.Parcelable create(android.os.Parcel p0)
public User[] newArray(int p0) {
LABEL (L0)
LINENUMBER (18)
ILOAD (1)
ANEWARRAY
ARETURN
LABEL (L1)
}
public android.os.Parcelable[] newArray(int p0) {
LABEL (L0)
LINENUMBER (9)
ALOAD (0)
ILOAD (1)
INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;)
CHECKCAST
ARETURN
}
public void write(User p0, android.os.Parcel p1, int p2)
public void write(android.os.Parcelable p0, android.os.Parcel p1, int p2)
}
public final class User : java/lang/Object, android/os/Parcelable {
public final static User$CREATOR CREATOR
public final static User$Companion Companion
private final int age
private final java.lang.String firstName
private final java.lang.String lastName
static void <clinit>()
public void <init>(java.lang.String p0, java.lang.String p1, int p2)
public final int describeContents()
public final int getAge()
public final java.lang.String getFirstName()
public final java.lang.String getLastName()
public final void writeToParcel(android.os.Parcel p0, int p1)
}