Skip to content

Commit 90b3de8

Browse files
committed
Parcelable: Support Parcelizer interface in order to be able to customize serialization
1 parent e5f5ce5 commit 90b3de8

File tree

11 files changed

+386
-23
lines changed

11 files changed

+386
-23
lines changed

.idea/dictionaries/yan.xml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
package org.jetbrains.kotlin.android.parcel
1818

19+
import kotlinx.android.parcel.Parceler
1920
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
2021
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
22+
import org.jetbrains.kotlin.android.parcel.serializers.PARCEL_TYPE
2123
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
24+
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
2225
import org.jetbrains.kotlin.codegen.ExpressionCodegen
2326
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
2427
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
@@ -33,13 +36,15 @@ import org.jetbrains.kotlin.codegen.OwnerKind
3336
import org.jetbrains.kotlin.codegen.context.ClassContext
3437
import org.jetbrains.kotlin.codegen.writeSyntheticClassMetadata
3538
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
39+
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
3640
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.*
3741
import org.jetbrains.kotlin.name.FqName
3842
import org.jetbrains.kotlin.resolve.DescriptorFactory
3943
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
4044
import org.jetbrains.kotlin.resolve.descriptorUtil.module
4145
import org.jetbrains.kotlin.resolve.scopes.MemberScope
4246
import org.jetbrains.kotlin.types.KotlinType
47+
import org.jetbrains.kotlin.types.TypeUtils
4348
import org.jetbrains.kotlin.types.Variance
4449
import org.jetbrains.org.objectweb.asm.Opcodes.*
4550
import org.jetbrains.org.objectweb.asm.Type
@@ -48,6 +53,9 @@ import java.io.FileDescriptor
4853
class ParcelableCodegenExtension : ExpressionCodegenExtension {
4954
private companion object {
5055
private val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
56+
private val PARCELER_FQNAME = FqName(Parceler::class.java.canonicalName)
57+
58+
fun KotlinType.isParceler() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQNAME
5159
}
5260

5361
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
@@ -60,32 +68,54 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
6068
val parcelClassType = ParcelableResolveExtension.resolveParcelClassType(parcelableClass.module)
6169
val parcelAsmType = codegen.typeMapper.mapType(parcelClassType)
6270

71+
val parcelerObject = parcelableClass.companionObjectDescriptor?.takeIf {
72+
TypeUtils.getAllSupertypes(it.defaultType).any { it.isParceler() }
73+
}
74+
6375
with (parcelableClass) {
6476
writeDescribeContentsFunction(codegen, propertiesToSerialize)
65-
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType)
77+
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType, parcelerObject)
6678
}
6779

6880
writeCreatorAccessField(codegen, parcelableClass)
69-
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, propertiesToSerialize)
81+
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, parcelerObject, propertiesToSerialize)
82+
}
83+
84+
private fun getCompanionClassType(containerAsmType: Type, parcelerObject: ClassDescriptor): Pair<Type, String> {
85+
val shortName = parcelerObject.name
86+
return Pair(Type.getObjectType(containerAsmType.internalName + "\$$shortName"), shortName.asString())
7087
}
7188

7289
private fun ClassDescriptor.writeWriteToParcel(
7390
codegen: ImplementationBodyCodegen,
7491
properties: List<Pair<String, KotlinType>>,
75-
parcelAsmType: Type
92+
parcelAsmType: Type,
93+
parcelerObject: ClassDescriptor?
7694
): Unit? {
7795
val containerAsmType = codegen.typeMapper.mapType(this.defaultType)
7896

7997
return findFunction(WRITE_TO_PARCEL)?.write(codegen) {
80-
for ((fieldName, type) in properties) {
81-
val asmType = codegen.typeMapper.mapType(type)
98+
if (parcelerObject != null) {
99+
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
82100

83-
v.load(1, parcelAsmType)
101+
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
84102
v.load(0, containerAsmType)
85-
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
103+
v.load(1, PARCEL_TYPE)
104+
v.load(2, Type.INT_TYPE)
105+
v.invokevirtual(companionAsmType.internalName, "write",
106+
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false)
107+
}
108+
else {
109+
for ((fieldName, type) in properties) {
110+
val asmType = codegen.typeMapper.mapType(type)
86111

87-
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
88-
serializer.writeValue(v)
112+
v.load(1, parcelAsmType)
113+
v.load(0, containerAsmType)
114+
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
115+
116+
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
117+
serializer.writeValue(v)
118+
}
89119
}
90120

91121
v.areturn(Type.VOID_TYPE)
@@ -135,26 +165,38 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
135165
creatorClass: ClassDescriptorImpl,
136166
parcelClassType: KotlinType,
137167
parcelAsmType: Type,
168+
parcelerObject: ClassDescriptor?,
138169
properties: List<Pair<String, KotlinType>>
139170
) {
140171
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
141172

142173
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.defaultType, "in" to parcelClassType).write(codegen) {
143-
v.anew(containerAsmType)
144-
v.dup()
174+
if (parcelerObject != null) {
175+
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
145176

146-
val asmConstructorParameters = StringBuilder()
177+
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
178+
v.load(1, PARCEL_TYPE)
179+
v.invokevirtual(companionAsmType.internalName, "create",
180+
"(${PARCEL_TYPE.descriptor})${containerAsmType.descriptor}", false)
181+
}
182+
else {
183+
v.anew(containerAsmType)
184+
v.dup()
185+
186+
val asmConstructorParameters = StringBuilder()
147187

148-
for ((_, type) in properties) {
149-
val asmType = codegen.typeMapper.mapType(type)
150-
asmConstructorParameters.append(asmType.descriptor)
188+
for ((_, type) in properties) {
189+
val asmType = codegen.typeMapper.mapType(type)
190+
asmConstructorParameters.append(asmType.descriptor)
191+
192+
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
193+
v.load(1, parcelAsmType)
194+
serializer.readValue(v)
195+
}
151196

152-
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
153-
v.load(1, parcelAsmType)
154-
serializer.readValue(v)
197+
v.invokespecial(containerAsmType.internalName, "<init>", "($asmConstructorParameters)V", false)
155198
}
156199

157-
v.invokespecial(containerAsmType.internalName, "<init>", "($asmConstructorParameters)V", false)
158200
v.areturn(containerAsmType)
159201
}
160202
}
@@ -171,6 +213,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
171213
parcelableClass: ClassDescriptor,
172214
parcelClassType: KotlinType,
173215
parcelAsmType: Type,
216+
parcelerObject: ClassDescriptor?,
174217
properties: List<Pair<String, KotlinType>>
175218
) {
176219
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
@@ -201,8 +244,8 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
201244
writeSyntheticClassMetadata(classBuilderForCreator, codegen.state)
202245

203246
writeCreatorConstructor(codegenForCreator, creatorClass, creatorAsmType)
204-
writeNewArrayMethod(codegenForCreator, parcelableClass, creatorClass)
205-
writeCreateFromParcel(codegenForCreator, parcelableClass, creatorClass, parcelClassType, parcelAsmType, properties)
247+
writeNewArrayMethod(codegenForCreator, parcelableClass, creatorClass, parcelerObject)
248+
writeCreateFromParcel(codegenForCreator, parcelableClass, creatorClass, parcelClassType, parcelAsmType, parcelerObject, properties)
206249

207250
classBuilderForCreator.done()
208251
}
@@ -221,7 +264,8 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
221264
private fun writeNewArrayMethod(
222265
codegen: ImplementationBodyCodegen,
223266
parcelableClass: ClassDescriptor,
224-
creatorClass: ClassDescriptorImpl
267+
creatorClass: ClassDescriptorImpl,
268+
parcelerObject: ClassDescriptor?
225269
) {
226270
val builtIns = parcelableClass.builtIns
227271
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass)
@@ -230,6 +274,29 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
230274
builtIns.getArrayType(Variance.INVARIANT, parcelableClass.defaultType),
231275
"size" to builtIns.intType
232276
).write(codegen) {
277+
if (parcelerObject != null) {
278+
val newArrayMethod = parcelerObject.unsubstitutedMemberScope
279+
.getContributedFunctions(Name.identifier("newArray"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS)
280+
.filter {
281+
it.typeParameters.isEmpty()
282+
&& it.kind == CallableMemberDescriptor.Kind.DECLARATION
283+
&& (it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type))
284+
&& !((it.containingDeclaration as? ClassDescriptor)?.defaultType?.isParceler() ?: true)
285+
}.firstOrNull()
286+
287+
if (newArrayMethod != null) {
288+
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
289+
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
290+
291+
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
292+
v.load(1, Type.INT_TYPE)
293+
v.invokevirtual(companionAsmType.internalName, "newArray", "(I)[${containerAsmType.descriptor}", false)
294+
v.areturn(Type.getType("[L$parcelableAsmType;"))
295+
296+
return@write
297+
}
298+
}
299+
233300
v.load(1, Type.INT_TYPE)
234301
v.newarray(parcelableAsmType)
235302
v.areturn(Type.getType("[L$parcelableAsmType;"))

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import org.jetbrains.org.objectweb.asm.Label
2020
import org.jetbrains.org.objectweb.asm.Type
2121
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
2222

23-
private val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
23+
internal val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
2424

2525
internal object GenericParcelSerializer : ParcelSerializer {
2626
override val asmType: Type = Type.getObjectType("java/lang/Object")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// WITH_RUNTIME
2+
3+
@file:JvmName("TestKt")
4+
package test
5+
6+
import kotlinx.android.parcel.*
7+
import android.os.Parcel
8+
import android.os.Parcelable
9+
10+
@MagicParcel
11+
data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable {
12+
private companion object : Parceler<User> {
13+
override fun User.write(parcel: Parcel, flags: Int) {
14+
parcel.writeString(firstName)
15+
parcel.writeString(secondName)
16+
}
17+
18+
override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), 0)
19+
}
20+
}
21+
22+
fun box() = parcelTest { parcel ->
23+
val user = User("John", "Smith", 20)
24+
user.writeToParcel(parcel, 0)
25+
26+
val bytes = parcel.marshall()
27+
parcel.unmarshall(bytes, 0, bytes.size)
28+
29+
val user2 = readFromParcel<User>(parcel)
30+
31+
assert(user.firstName == user2.firstName)
32+
assert(user.secondName == user2.secondName)
33+
assert(user2.age == 0)
34+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// CURIOUS_ABOUT writeToParcel, createFromParcel, newArray
2+
3+
import kotlinx.android.parcel.*
4+
import android.os.Parcel
5+
import android.os.Parcelable
6+
7+
@MagicParcel
8+
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
9+
private companion object : Parceler<User> {
10+
override fun User.write(parcel: Parcel, flags: Int) {
11+
parcel.writeString(firstName)
12+
parcel.writeString(lastName)
13+
parcel.writeInt(age)
14+
}
15+
16+
override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), parcel.readInt())
17+
}
18+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
public static class User$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
2+
public void <init>()
3+
4+
public final User createFromParcel(android.os.Parcel p0) {
5+
LABEL (L0)
6+
ALOAD (1)
7+
LDC (in)
8+
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
9+
GETSTATIC (Companion, LUser$Companion;)
10+
ALOAD (1)
11+
INVOKEVIRTUAL (User$Companion, create, (Landroid/os/Parcel;)LUser;)
12+
ARETURN
13+
LABEL (L1)
14+
}
15+
16+
public final User[] newArray(int p0) {
17+
LABEL (L0)
18+
ILOAD (1)
19+
ANEWARRAY
20+
ARETURN
21+
LABEL (L1)
22+
}
23+
}
24+
25+
final class User$Companion : java/lang/Object, kotlinx/android/parcel/Parceler {
26+
private void <init>()
27+
28+
public void <init>(kotlin.jvm.internal.DefaultConstructorMarker p0)
29+
30+
public User create(android.os.Parcel p0)
31+
32+
public android.os.Parcelable create(android.os.Parcel p0)
33+
34+
public User[] newArray(int p0) {
35+
LABEL (L0)
36+
LINENUMBER (9)
37+
ALOAD (0)
38+
ILOAD (1)
39+
INVOKESTATIC (kotlinx/android/parcel/Parceler$DefaultImpls, newArray, (Lkotlinx/android/parcel/Parceler;I)[Landroid/os/Parcelable;)
40+
CHECKCAST
41+
ARETURN
42+
LABEL (L1)
43+
}
44+
45+
public android.os.Parcelable[] newArray(int p0) {
46+
LABEL (L0)
47+
LINENUMBER (9)
48+
ALOAD (0)
49+
ILOAD (1)
50+
INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;)
51+
CHECKCAST
52+
ARETURN
53+
}
54+
55+
public void write(User p0, android.os.Parcel p1, int p2)
56+
57+
public void write(android.os.Parcelable p0, android.os.Parcel p1, int p2)
58+
}
59+
60+
public final class User : java/lang/Object, android/os/Parcelable {
61+
public final static User$CREATOR CREATOR
62+
63+
public final static User$Companion Companion
64+
65+
private final int age
66+
67+
private final java.lang.String firstName
68+
69+
private final java.lang.String lastName
70+
71+
static void <clinit>()
72+
73+
public void <init>(java.lang.String p0, java.lang.String p1, int p2)
74+
75+
public final int describeContents()
76+
77+
public final int getAge()
78+
79+
public final java.lang.String getFirstName()
80+
81+
public final java.lang.String getLastName()
82+
83+
public final void writeToParcel(android.os.Parcel p0, int p1) {
84+
LABEL (L0)
85+
ALOAD (1)
86+
LDC (parcel)
87+
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
88+
GETSTATIC (Companion, LUser$Companion;)
89+
ALOAD (0)
90+
ALOAD (1)
91+
ILOAD (2)
92+
INVOKEVIRTUAL (User$Companion, write, (LUser;Landroid/os/Parcel;I)V)
93+
RETURN
94+
LABEL (L1)
95+
}
96+
}

0 commit comments

Comments
 (0)