Parcelable: Support Parcelizer interface in order to be able to customize serialization
This commit is contained in:
+89
-22
@@ -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;"))
|
||||
|
||||
+1
-1
@@ -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")
|
||||
|
||||
+34
@@ -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)
|
||||
}
|
||||
Vendored
+18
@@ -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())
|
||||
}
|
||||
}
|
||||
Vendored
+96
@@ -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)
|
||||
}
|
||||
}
|
||||
+20
@@ -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>
|
||||
}
|
||||
}
|
||||
+73
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user