Skip to content

Commit 39cfcbc

Browse files
committed
Parcelable: Support CharSequence, IBinder/IInterface, objects, enums. Serialize Parcelable efficiently if possible
1 parent 4c07aa8 commit 39cfcbc

23 files changed

+442
-94
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
8484
) {
8585
if (origin is KtClass) {
8686
currentClass = origin
87+
} else {
88+
currentClass = null
8789
}
8890

8991
currentClassName = name
@@ -118,7 +120,13 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
118120
): MethodVisitor {
119121
if (name == "<clinit>" && currentClass != null && currentClassName != null) {
120122
isClinitGenerated = true
121-
return ClinitAwareMethodVisitor(currentClassName!!, super.newMethod(origin, access, name, desc, signature, exceptions))
123+
124+
val descriptor = bindingContext[BindingContext.CLASS, currentClass]
125+
if (descriptor != null && descriptor.isMagicParcelable) {
126+
return ClinitAwareMethodVisitor(
127+
currentClassName!!,
128+
super.newMethod(origin, access, name, desc, signature, exceptions))
129+
}
122130
}
123131

124132
return super.newMethod(origin, access, name, desc, signature, exceptions)
@@ -129,7 +137,7 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
129137
override fun visitInsn(opcode: Int) {
130138
if (opcode == Opcodes.RETURN) {
131139
val iv = InstructionAdapter(this)
132-
val creatorName = "$parcelableName\$CREATOR"
140+
val creatorName = "$parcelableName\$Creator"
133141
val creatorType = Type.getObjectType(creatorName)
134142

135143
iv.anew(creatorType)

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,13 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
170170
) {
171171
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
172172

173-
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.defaultType, "in" to parcelClassType).write(codegen) {
173+
createMethod(creatorClass, CREATE_FROM_PARCEL, parcelableClass.builtIns.anyType, "in" to parcelClassType).write(codegen) {
174174
if (parcelerObject != null) {
175175
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
176176

177177
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
178178
v.load(1, PARCEL_TYPE)
179-
v.invokevirtual(companionAsmType.internalName, "create",
180-
"(${PARCEL_TYPE.descriptor})${containerAsmType.descriptor}", false)
179+
v.invokevirtual(companionAsmType.internalName, "create", "(${PARCEL_TYPE.descriptor})Landroid/os/Parcelable;", false)
181180
}
182181
else {
183182
v.anew(containerAsmType)
@@ -203,7 +202,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
203202

204203
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) {
205204
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
206-
val creatorAsmType = Type.getObjectType(parcelableAsmType.internalName + "\$CREATOR")
205+
val creatorAsmType = Type.getObjectType(parcelableAsmType.internalName + "\$Creator")
207206
codegen.v.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_STATIC or ACC_PUBLIC or ACC_FINAL, "CREATOR",
208207
creatorAsmType.descriptor, null, null)
209208
}
@@ -217,7 +216,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
217216
properties: List<Pair<String, KotlinType>>
218217
) {
219218
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
220-
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$CREATOR")
219+
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$Creator")
221220

222221
val creatorClass = ClassDescriptorImpl(
223222
parcelableClass.containingDeclaration, Name.identifier("Creator"), Modality.FINAL, ClassKind.CLASS, emptyList(),

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@
1616

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

19+
import org.jetbrains.kotlin.android.parcel.isMagicParcelable
1920
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
21+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
22+
import org.jetbrains.kotlin.descriptors.ClassKind
23+
import org.jetbrains.kotlin.descriptors.Modality
24+
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
25+
import org.jetbrains.kotlin.name.Name
26+
import org.jetbrains.kotlin.resolve.DescriptorUtils
2027
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
28+
import org.jetbrains.kotlin.synthetic.isVisibleOutside
2129
import org.jetbrains.kotlin.types.KotlinType
30+
import org.jetbrains.kotlin.types.TypeUtils
2231
import org.jetbrains.kotlin.types.typeUtil.builtIns
2332
import org.jetbrains.org.objectweb.asm.Type
2433
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
@@ -98,6 +107,14 @@ interface ParcelSerializer {
98107
Method("writeBundle"),
99108
Method("readBundle"))
100109

110+
type.isIBinder() -> NullCompliantObjectParcelSerializer(asmType,
111+
Method("writeStrongBinder", "(Landroid/os/IBinder;)V"),
112+
Method("readStrongBinder", "()Landroid/os/IBinder;"))
113+
114+
type.isIInterface() -> NullCompliantObjectParcelSerializer(asmType,
115+
Method("writeStrongInterface", "(Landroid/os/IInterface;)V"),
116+
Method("readStrongInterface", "()Landroid/os/IInterface;"))
117+
101118
asmType.isPersistableBundle() -> NullCompliantObjectParcelSerializer(asmType,
102119
Method("writeBundle"),
103120
Method("readBundle"))
@@ -118,14 +135,42 @@ interface ParcelSerializer {
118135
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
119136
}
120137

138+
type.isCharSequence() -> CharSequenceParcelSerializer(asmType)
139+
121140
type.isException() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
122141
Method("writeException"),
123142
Method("readException")))
124143

144+
// Write at least a nullability byte.
145+
// We don't want parcel to be empty in case if all constructor parameters are objects
146+
type.isNamedObject() -> NullAwareParcelSerializerWrapper(ObjectParcelSerializer(asmType, type, typeMapper))
147+
148+
type.isEnum() -> wrapToNullAwareIfNeeded(type, EnumParcelSerializer(asmType))
149+
125150
asmType.isFileDescriptor() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
126151
Method("writeRawFileDescriptor"),
127152
Method("readRawFileDescriptor")))
128153

154+
type.isParcelable() -> {
155+
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
156+
if (clazz != null && clazz.modality == Modality.FINAL) {
157+
val creatorVar = clazz.staticScope.getContributedVariables(
158+
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
159+
160+
val creatorAsmType = when {
161+
creatorVar != null -> typeMapper.mapType(creatorVar.type)
162+
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
163+
else -> null
164+
}
165+
166+
creatorAsmType?.let { EfficientParcelableParcelSerializer(asmType, creatorAsmType) }
167+
?: GenericParcelableParcelSerializer(asmType)
168+
}
169+
else {
170+
GenericParcelableParcelSerializer(asmType)
171+
}
172+
}
173+
129174
type.isSerializable() -> NullCompliantObjectParcelSerializer(asmType,
130175
Method("writeSerializable"),
131176
Method("readSerializable"))
@@ -151,6 +196,19 @@ interface ParcelSerializer {
151196
private fun Type.isSparseArray() = this.descriptor == "Landroid/util/SparseArray;"
152197
private fun KotlinType.isSerializable() = matchesFqNameWithSupertypes("java.io.Serializable")
153198
private fun KotlinType.isException() = matchesFqNameWithSupertypes("java.lang.Exception")
199+
private fun KotlinType.isIBinder() = matchesFqNameWithSupertypes("android.os.IBinder")
200+
private fun KotlinType.isIInterface() = matchesFqNameWithSupertypes("android.os.IInterface")
201+
private fun KotlinType.isParcelable() = matchesFqNameWithSupertypes("android.os.Parcelable")
202+
private fun KotlinType.isCharSequence() = matchesFqName("kotlin.CharSequence") || matchesFqName("java.lang.CharSequence")
203+
204+
private fun KotlinType.isNamedObject(): Boolean {
205+
val classDescriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return false
206+
if (!classDescriptor.visibility.isVisibleOutside()) return false
207+
if (DescriptorUtils.isAnonymousObject(classDescriptor)) return false
208+
return classDescriptor.kind == ClassKind.OBJECT
209+
}
210+
211+
private fun KotlinType.isEnum() = (constructor.declarationDescriptor as? ClassDescriptor)?.kind == ClassKind.ENUM_CLASS
154212

155213
private fun Type.isPrimitive(): Boolean = when (this.sort) {
156214
Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT, Type.FLOAT, Type.LONG, Type.DOUBLE -> true
@@ -174,7 +232,7 @@ interface ParcelSerializer {
174232
return true
175233
}
176234

177-
return this.constructor.supertypes.any { it.matchesFqName(fqName) }
235+
return TypeUtils.getAllSupertypes(this).any { it.matchesFqName(fqName) }
178236
}
179237

180238
private fun KotlinType.matchesFqName(fqName: String): Boolean {

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

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

19+
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
20+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
21+
import org.jetbrains.kotlin.types.KotlinType
1922
import org.jetbrains.org.objectweb.asm.Label
2023
import org.jetbrains.org.objectweb.asm.Type
2124
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
@@ -383,6 +386,94 @@ internal class SparseArrayParcelSerializer(override val asmType: Type, private v
383386
}
384387
}
385388

389+
internal class ObjectParcelSerializer(
390+
override val asmType: Type,
391+
private val type: KotlinType,
392+
private val typeMapper: KotlinTypeMapper
393+
) : ParcelSerializer {
394+
override fun writeValue(v: InstructionAdapter) {
395+
v.pop2()
396+
}
397+
398+
override fun readValue(v: InstructionAdapter) {
399+
v.pop()
400+
401+
// Handle companion object
402+
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
403+
if (clazz != null && clazz.isCompanionObject) {
404+
val outerClass = clazz.containingDeclaration as? ClassDescriptor
405+
if (outerClass != null) {
406+
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
407+
return
408+
}
409+
}
410+
411+
v.getstatic(asmType.internalName, "INSTANCE", asmType.descriptor)
412+
}
413+
}
414+
415+
internal class EnumParcelSerializer(override val asmType: Type) : ParcelSerializer {
416+
override fun writeValue(v: InstructionAdapter) {
417+
v.invokevirtual(asmType.internalName, "name", "()Ljava/lang/String;", false)
418+
v.invokevirtual(PARCEL_TYPE.internalName, "writeString", "(Ljava/lang/String;)V", false)
419+
}
420+
421+
override fun readValue(v: InstructionAdapter) {
422+
v.invokevirtual(PARCEL_TYPE.internalName, "readString", "()Ljava/lang/String;", false)
423+
v.invokestatic(asmType.internalName, "valueOf", "(Ljava/lang/String;)${asmType.descriptor}", false)
424+
}
425+
}
426+
427+
internal class CharSequenceParcelSerializer(override val asmType: Type) : ParcelSerializer {
428+
override fun writeValue(v: InstructionAdapter) {
429+
// -> parcel, seq
430+
v.swap() // -> seq, parcel
431+
v.aconst(0) // -> seq, parcel, flags
432+
v.invokestatic("android/text/TextUtils", "writeToParcel", "(Ljava/lang/CharSequence;Landroid/os/Parcel;I)V", false)
433+
}
434+
435+
override fun readValue(v: InstructionAdapter) {
436+
// -> parcel
437+
v.getstatic("android/text/TextUtils", "CHAR_SEQUENCE_CREATOR", "Landroid/os/Parcelable\$Creator;") // -> parcel, creator
438+
v.swap() // -> creator, parcel
439+
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
440+
v.castIfNeeded(asmType)
441+
}
442+
}
443+
444+
internal class EfficientParcelableParcelSerializer(override val asmType: Type, private val creatorAsmType: Type) : ParcelSerializer {
445+
override fun writeValue(v: InstructionAdapter) {
446+
// -> parcel, parcelable
447+
v.swap() // -> parcelable, parcel
448+
v.aconst(0) // -> parcelable, parcel, flags
449+
v.invokeinterface("android/os/Parcelable", "writeToParcel", "(Landroid/os/Parcel;I)V")
450+
}
451+
452+
override fun readValue(v: InstructionAdapter) {
453+
// -> parcel
454+
v.getstatic(asmType.internalName, "CREATOR", creatorAsmType.descriptor) // -> parcel, creator
455+
v.swap() // -> creator, parcel
456+
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
457+
v.castIfNeeded(asmType)
458+
}
459+
}
460+
461+
internal class GenericParcelableParcelSerializer(override val asmType: Type) : ParcelSerializer {
462+
override fun writeValue(v: InstructionAdapter) {
463+
// -> parcel, parcelable
464+
v.aconst(0) // -> parcel, parcelable, flags
465+
v.invokevirtual(PARCEL_TYPE.internalName, "writeParcelable", "(Landroid/os/Parcelable;I)V", false)
466+
}
467+
468+
override fun readValue(v: InstructionAdapter) {
469+
// -> parcel
470+
v.aconst(asmType) // -> parcel, type
471+
v.invokevirtual("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false) // -> parcel, classloader
472+
v.invokevirtual(PARCEL_TYPE.internalName, "readParcelable", "(Ljava/lang/ClassLoader;)Landroid/os/Parcelable;", false)
473+
v.castIfNeeded(asmType)
474+
}
475+
}
476+
386477
internal class NullAwareParcelSerializerWrapper(val delegate: ParcelSerializer) : ParcelSerializer {
387478
override val asmType: Type
388479
get() = delegate.asmType
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
import android.text.SpannableString
10+
11+
@MagicParcel
12+
data class Test(val simple: CharSequence, val spanned: CharSequence) : Parcelable
13+
14+
fun box() = parcelTest { parcel ->
15+
val test = Test("John", SpannableString("Smith"))
16+
test.writeToParcel(parcel, 0)
17+
18+
val bytes = parcel.marshall()
19+
parcel.unmarshall(bytes, 0, bytes.size)
20+
21+
val test2 = readFromParcel<Test>(parcel)
22+
23+
assert(test.simple.toString() == test2.simple.toString())
24+
assert(test.spanned.toString() == test2.spanned.toString())
25+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
enum class Color {
11+
BLACK, WHITE
12+
}
13+
14+
@MagicParcel
15+
data class Test(val name: String, val color: Color) : Parcelable
16+
17+
fun box() = parcelTest { parcel ->
18+
val test = Test("John", Color.WHITE)
19+
test.writeToParcel(parcel, 0)
20+
21+
val bytes = parcel.marshall()
22+
parcel.unmarshall(bytes, 0, bytes.size)
23+
24+
val test2 = readFromParcel<Test>(parcel)
25+
assert(test == test2)
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
// Starts with A, should be loaded before other classes
11+
abstract class AParcelable : Parcelable
12+
13+
@MagicParcel
14+
data class P1(val a: String) : AParcelable()
15+
16+
sealed class Sealed : AParcelable()
17+
18+
@MagicParcel
19+
data class Sealed1(val a: Int) : Sealed()
20+
21+
@MagicParcel
22+
data class Test(val a: P1, val b: AParcelable, val c: Sealed, val d: Sealed1) : Parcelable
23+
24+
fun box() = parcelTest { parcel ->
25+
val test = Test(P1(""), P1("My"), Sealed1(1), Sealed1(5))
26+
test.writeToParcel(parcel, 0)
27+
28+
val bytes = parcel.marshall()
29+
parcel.unmarshall(bytes, 0, bytes.size)
30+
31+
val test2 = readFromParcel<Test>(parcel)
32+
assert(test == test2)
33+
}

0 commit comments

Comments
 (0)