Parcelable: Add Parcelable functionality to Android Extensions plugin

This commit is contained in:
Yan Zhulanow
2017-06-16 16:52:29 +03:00
parent edd8a0a64e
commit c23bca6afe
47 changed files with 2979 additions and 3 deletions
@@ -0,0 +1,144 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.synthetic.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.android.parcel.isMagicParcelable
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension {
override fun interceptClassBuilderFactory(
interceptedFactory: ClassBuilderFactory,
bindingContext: BindingContext,
diagnostics: DiagnosticSink
): ClassBuilderFactory {
return ParcelableClinitClassBuilderFactory(interceptedFactory, bindingContext)
}
private inner class ParcelableClinitClassBuilderFactory(
private val delegateFactory: ClassBuilderFactory,
val bindingContext: BindingContext
) : ClassBuilderFactory {
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
return AndroidOnDestroyCollectorClassBuilder(delegateFactory.newClassBuilder(origin), bindingContext)
}
override fun getClassBuilderMode() = delegateFactory.classBuilderMode
override fun asText(builder: ClassBuilder?): String? {
return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun asBytes(builder: ClassBuilder?): ByteArray? {
return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun close() {
delegateFactory.close()
}
}
private inner class AndroidOnDestroyCollectorClassBuilder(
internal val delegateClassBuilder: ClassBuilder,
val bindingContext: BindingContext
) : DelegatingClassBuilder() {
private var currentClass: KtClass? = null
private var currentClassName: String? = null
private var isClinitGenerated = false
override fun getDelegate() = delegateClassBuilder
override fun defineClass(
origin: PsiElement?,
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array<out String>
) {
if (origin is KtClass) {
currentClass = origin
}
currentClassName = name
isClinitGenerated = false
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun done() {
if (!isClinitGenerated && currentClass != null && currentClassName != null) {
val descriptor = bindingContext[BindingContext.CLASS, currentClass]
if (descriptor != null && descriptor.isMagicParcelable) {
val baseVisitor = super.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_STATIC, "<clinit>", "()V", null, null)
val visitor = ClinitAwareMethodVisitor(currentClassName!!, baseVisitor)
visitor.visitCode()
visitor.visitInsn(Opcodes.RETURN)
visitor.visitEnd()
}
}
super.done()
}
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
if (name == "<clinit>" && currentClass != null && currentClassName != null) {
isClinitGenerated = true
return ClinitAwareMethodVisitor(currentClassName!!, super.newMethod(origin, access, name, desc, signature, exceptions))
}
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
}
private class ClinitAwareMethodVisitor(val parcelableName: String, mv: MethodVisitor) : MethodVisitor(Opcodes.ASM5, mv) {
override fun visitInsn(opcode: Int) {
if (opcode == Opcodes.RETURN) {
val iv = InstructionAdapter(this)
val creatorName = "$parcelableName\$CREATOR"
val creatorType = Type.getObjectType(creatorName)
iv.anew(creatorType)
iv.dup()
iv.invokespecial(creatorName, "<init>", "()V", false)
iv.putstatic(parcelableName, "CREATOR", creatorType.descriptor)
}
super.visitInsn(opcode)
}
}
}
@@ -0,0 +1,252 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy.CodegenBased
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.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorFactory
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.Variance
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import java.io.FileDescriptor
class ParcelableCodegenExtension : ExpressionCodegenExtension {
private companion object {
private val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
}
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
val parcelableClass = codegen.descriptor
if (!parcelableClass.isMagicParcelable) return
assert(parcelableClass.kind == ClassKind.CLASS || parcelableClass.kind == ClassKind.OBJECT)
val propertiesToSerialize = getPropertiesToSerialize(codegen, parcelableClass)
val parcelClassType = ParcelableResolveExtension.resolveParcelClassType(parcelableClass.module)
val parcelAsmType = codegen.typeMapper.mapType(parcelClassType)
with (parcelableClass) {
writeDescribeContentsFunction(codegen, propertiesToSerialize)
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType)
}
writeCreatorAccessField(codegen, parcelableClass)
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, propertiesToSerialize)
}
private fun ClassDescriptor.writeWriteToParcel(
codegen: ImplementationBodyCodegen,
properties: List<Pair<String, KotlinType>>,
parcelAsmType: Type
): 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)
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)
}
}
private fun ClassDescriptor.writeDescribeContentsFunction(
codegen: ImplementationBodyCodegen,
propertiesToSerialize: List<Pair<String, KotlinType>>
): Unit? {
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.second.containsFileDescriptor() }
return findFunction(DESCRIBE_CONTENTS)?.write(codegen) {
v.aconst(if (hasFileDescriptorAnywhere) 1 /* CONTENTS_FILE_DESCRIPTOR */ else 0)
v.areturn(Type.INT_TYPE)
}
}
private fun KotlinType.containsFileDescriptor(): Boolean {
val declarationDescriptor = this.constructor.declarationDescriptor
if (declarationDescriptor != null) {
if (declarationDescriptor.fqNameSafe == FILE_DESCRIPTOR_FQNAME) {
return true
}
}
return this.arguments.any { it.type.containsFileDescriptor() }
}
private fun getPropertiesToSerialize(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor
): List<Pair<String, KotlinType>> {
val constructor = parcelableClass.constructors.first { it.isPrimary }
val propertiesToSerialize = constructor.valueParameters.map { param ->
codegen.bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, param]
?: error("Value parameter should have 'val' or 'var' keyword")
}
return propertiesToSerialize.map { it.name.asString() /* TODO */ to it.type }
}
private fun writeCreateFromParcel(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
creatorClass: ClassDescriptorImpl,
parcelClassType: KotlinType,
parcelAsmType: Type,
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()
val asmConstructorParameters = StringBuilder()
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.areturn(containerAsmType)
}
}
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) {
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
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)
}
private fun writeCreatorClass(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
parcelClassType: KotlinType,
parcelAsmType: Type,
properties: List<Pair<String, KotlinType>>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$CREATOR")
val creatorClass = ClassDescriptorImpl(
parcelableClass.containingDeclaration, Name.identifier("Creator"), Modality.FINAL, ClassKind.CLASS, emptyList(),
parcelableClass.source, false)
creatorClass.initialize(
MemberScope.Empty, emptySet(),
DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source))
val classBuilderForCreator = codegen.state.factory.newVisitor(
JvmDeclarationOrigin.NO_ORIGIN,
Type.getObjectType(creatorAsmType.internalName),
codegen.myClass.containingKtFile)
val classContextForCreator = ClassContext(
codegen.typeMapper, creatorClass, OwnerKind.IMPLEMENTATION, codegen.context.parentContext, null)
val codegenForCreator = ImplementationBodyCodegen(
codegen.myClass, classContextForCreator, classBuilderForCreator, codegen.state, codegen.parentCodegen, false)
classBuilderForCreator.defineClass(null, V1_6, ACC_PUBLIC or ACC_STATIC,
creatorAsmType.internalName, null, "java/lang/Object",
arrayOf("android/os/Parcelable\$Creator"))
writeSyntheticClassMetadata(classBuilderForCreator, codegen.state)
writeCreatorConstructor(codegenForCreator, creatorClass, creatorAsmType)
writeNewArrayMethod(codegenForCreator, parcelableClass, creatorClass)
writeCreateFromParcel(codegenForCreator, parcelableClass, creatorClass, parcelClassType, parcelAsmType, properties)
classBuilderForCreator.done()
}
private fun writeCreatorConstructor(codegen: ImplementationBodyCodegen, creatorClass: ClassDescriptor, creatorAsmType: Type) {
DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source)
.apply {
returnType = creatorClass.defaultType
}.write(codegen) {
v.load(0, creatorAsmType)
v.invokespecial("java/lang/Object", "<init>", "()V", false)
v.areturn(Type.VOID_TYPE)
}
}
private fun writeNewArrayMethod(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
creatorClass: ClassDescriptorImpl
) {
val builtIns = parcelableClass.builtIns
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass)
createMethod(creatorClass, NEW_ARRAY,
builtIns.getArrayType(Variance.INVARIANT, parcelableClass.defaultType),
"size" to builtIns.intType
).write(codegen) {
v.load(1, Type.INT_TYPE)
v.newarray(parcelableAsmType)
v.areturn(Type.getType("[L$parcelableAsmType;"))
}
}
private fun FunctionDescriptor.write(codegen: ImplementationBodyCodegen, code: ExpressionCodegen.() -> Unit) {
codegen.functionCodegen.generateMethod(JvmDeclarationOrigin.NO_ORIGIN, this, object : CodegenBased(codegen.state) {
override fun doGenerateBody(e: ExpressionCodegen, signature: JvmMethodSignature) {
e.code()
}
})
}
private fun ClassDescriptor.findFunction(componentKind: ParcelableSyntheticComponent.ComponentKind): SimpleFunctionDescriptor? {
return unsubstitutedMemberScope
.getContributedFunctions(Name.identifier(componentKind.methodName), WHEN_GET_ALL_DESCRIPTORS)
.firstOrNull { (it as? ParcelableSyntheticComponent)?.componentKind == componentKind }
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel
import kotlinx.android.parcel.MagicParcel
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
class ParcelableResolveExtension : SyntheticResolveExtension {
companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType {
return module.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("android.os.Parcel")))?.defaultType ?: error("Can't resolve 'android.os.Parcel' class")
}
fun createMethod(
classDescriptor: ClassDescriptor,
componentKind: ParcelableSyntheticComponent.ComponentKind,
returnType: KotlinType,
vararg parameters: Pair<String, KotlinType>
): SimpleFunctionDescriptor {
val functionDescriptor = object : ParcelableSyntheticComponent, SimpleFunctionDescriptorImpl(
classDescriptor,
null,
Annotations.EMPTY,
Name.identifier(componentKind.methodName),
CallableMemberDescriptor.Kind.SYNTHESIZED,
classDescriptor.source
) {
override val componentKind = componentKind
}
val valueParameters = parameters.mapIndexed { index, (name, type) -> functionDescriptor.makeValueParameter(name, type, index) }
functionDescriptor.initialize(
null, classDescriptor.thisAsReceiverParameter, emptyList(), valueParameters,
returnType, Modality.FINAL, Visibilities.PUBLIC)
return functionDescriptor
}
private fun FunctionDescriptor.makeValueParameter(name: String, type: KotlinType, index: Int): ValueParameterDescriptor {
return ValueParameterDescriptorImpl(
this, null, index, Annotations.EMPTY, Name.identifier(name), type, false, false, false, null, this.source)
}
}
override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor) = null
override fun generateSyntheticMethods(
clazz: ClassDescriptor,
name: Name,
fromSupertypes: List<SimpleFunctionDescriptor>,
result: MutableCollection<SimpleFunctionDescriptor>
) {
if (name.asString() == DESCRIBE_CONTENTS.methodName && clazz.isMagicParcelable) {
result += createMethod(clazz, DESCRIBE_CONTENTS, clazz.builtIns.intType)
} else if (name.asString() == WRITE_TO_PARCEL.methodName && clazz.isMagicParcelable) {
val builtIns = clazz.builtIns
val parcelClassType = resolveParcelClassType(clazz.module)
result += createMethod(clazz, WRITE_TO_PARCEL, builtIns.unitType, "parcel" to parcelClassType, "flags" to builtIns.intType)
}
}
}
interface ParcelableSyntheticComponent {
val componentKind: ComponentKind
enum class ComponentKind(val methodName: String) {
WRITE_TO_PARCEL("writeToParcel"),
DESCRIBE_CONTENTS("describeContents"),
NEW_ARRAY("newArray"),
CREATE_FROM_PARCEL("createFromParcel")
}
}
private val MAGIC_PARCEL_CLASS_FQNAME = FqName(MagicParcel::class.java.canonicalName)
internal val ClassDescriptor.isMagicParcelable: Boolean
get() = this.annotations.hasAnnotation(MAGIC_PARCEL_CLASS_FQNAME)
@@ -0,0 +1,184 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel.serializers
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.*
import java.util.concurrent.ConcurrentHashMap
interface ParcelSerializer {
val asmType: Type
fun writeValue(v: InstructionAdapter)
fun readValue(v: InstructionAdapter)
companion object {
fun get(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, forceBoxed: Boolean = false): ParcelSerializer {
val className = asmType.className
return when {
asmType.sort == Type.ARRAY -> {
val elementType = type.builtIns.getArrayElementType(type)
wrapToNullAwareIfNeeded(
type,
ArrayParcelSerializer(asmType, get(elementType, typeMapper.mapType(elementType), typeMapper)))
}
asmType.isPrimitive() -> {
if (forceBoxed || type.isMarkedNullable)
wrapToNullAwareIfNeeded(type, BoxedPrimitiveTypeParcelSerializer.forUnboxedType(asmType))
else
PrimitiveTypeParcelSerializer.getInstance(asmType)
}
asmType.isString() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeString"),
Method("readString"))
className == List::class.java.canonicalName
|| className == ArrayList::class.java.canonicalName
|| className == LinkedList::class.java.canonicalName
|| className == Set::class.java.canonicalName
|| className == HashSet::class.java.canonicalName
|| className == LinkedHashSet::class.java.canonicalName
|| className == TreeSet::class.java.canonicalName
-> {
val elementType = type.arguments.single().type
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer))
}
className == Map::class.java.canonicalName
|| className == HashMap::class.java.canonicalName
|| className == LinkedHashMap::class.java.canonicalName
|| className == TreeMap::class.java.canonicalName
|| className == ConcurrentHashMap::class.java.canonicalName
-> {
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
val keySerializer = get(keyType.type, typeMapper.mapType(keyType.type), typeMapper, forceBoxed = true)
val valueSerializer = get(valueType.type, typeMapper.mapType(valueType.type), typeMapper, forceBoxed = true)
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
}
asmType.isBoxedPrimitive() -> wrapToNullAwareIfNeeded(type, BoxedPrimitiveTypeParcelSerializer.forBoxedType(asmType))
asmType.isBlob() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeBlob"),
Method("readBlob"))
asmType.isSize() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
Method("writeSize"),
Method("readSize")))
asmType.isSizeF() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
Method("writeSizeF"),
Method("readSizeF")))
asmType.isBundle() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeBundle"),
Method("readBundle"))
asmType.isPersistableBundle() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeBundle"),
Method("readBundle"))
asmType.isSparseBooleanArray() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeSparseBooleanArray"),
Method("readSparseBooleanArray"))
asmType.isSparseIntArray() -> wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(
asmType, PrimitiveTypeParcelSerializer.getInstance(Type.INT_TYPE)))
asmType.isSparseLongArray() -> wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(
asmType, PrimitiveTypeParcelSerializer.getInstance(Type.LONG_TYPE)))
asmType.isSparseArray() -> {
val elementType = type.arguments.single().type
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
}
type.isException() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
Method("writeException"),
Method("readException")))
asmType.isFileDescriptor() -> wrapToNullAwareIfNeeded(type, NullCompliantObjectParcelSerializer(asmType,
Method("writeRawFileDescriptor"),
Method("readRawFileDescriptor")))
type.isSerializable() -> NullCompliantObjectParcelSerializer(asmType,
Method("writeSerializable"),
Method("readSerializable"))
else -> GenericParcelSerializer
}
}
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when {
type.isMarkedNullable -> NullAwareParcelSerializerWrapper(serializer)
else -> serializer
}
private fun Type.isBlob() = this.sort == Type.ARRAY && this.elementType == Type.BYTE_TYPE
private fun Type.isString() = this.descriptor == "Ljava/lang/String;"
private fun Type.isSize() = this.descriptor == "Landroid/util/Size;"
private fun Type.isSizeF() = this.descriptor == "Landroid/util/SizeF;"
private fun Type.isFileDescriptor() = this.descriptor == "Ljava/io/FileDescriptor;"
private fun Type.isBundle() = this.descriptor == "Landroid/os/Bundle;"
private fun Type.isPersistableBundle() = this.descriptor == "Landroid/os/PersistableBundle;"
private fun Type.isSparseBooleanArray() = this.descriptor == "Landroid/util/SparseBooleanArray;"
private fun Type.isSparseIntArray() = this.descriptor == "Landroid/util/SparseIntArray;"
private fun Type.isSparseLongArray() = this.descriptor == "Landroid/util/SparseLongArray;"
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 Type.isPrimitive(): Boolean = when (this.sort) {
Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT, Type.FLOAT, Type.LONG, Type.DOUBLE -> true
else -> false
}
private fun Type.isBoxedPrimitive(): Boolean = when(this.descriptor) {
"Ljava/lang/Boolean;",
"Ljava/lang/Character;",
"Ljava/lang/Byte;",
"Ljava/lang/Short;",
"Ljava/lang/Integer;",
"Ljava/lang/Float;",
"Ljava/lang/Long;",
"Ljava/lang/Double;" -> true
else -> false
}
private fun KotlinType.matchesFqNameWithSupertypes(fqName: String): Boolean {
if (this.matchesFqName(fqName)) {
return true
}
return this.constructor.supertypes.any { it.matchesFqName(fqName) }
}
private fun KotlinType.matchesFqName(fqName: String): Boolean {
return this.constructor.declarationDescriptor?.fqNameSafe?.asString() == fqName
}
}
}
@@ -0,0 +1,579 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel.serializers
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 object GenericParcelSerializer : ParcelSerializer {
override val asmType: Type = Type.getObjectType("java/lang/Object")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "writeValue", "(Ljava/lang/Object;)V", false)
}
override fun readValue(v: InstructionAdapter) {
v.aconst(asmType) // -> parcel, type
v.invokevirtual("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false) // -> parcel, classloader
v.invokevirtual(PARCEL_TYPE.internalName, "readValue", "(Ljava/lang/ClassLoader;)Ljava/lang/Object;", false)
}
}
internal class ArrayParcelSerializer(override val asmType: Type, private val elementSerializer: ParcelSerializer) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.dupX1() // -> arr, parcel, arr
v.arraylength() // -> arr, parcel, length
v.dupX2() // -> length, arr, parcel, length
// Write array size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> length, arr
v.swap() // -> arr, length
v.aconst(0) // -> arr, length, <index>
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
// Loop
v.dup2() // -> arr, length, index, length, index
v.ificmple(loopIsOver) // -> arr, length, index
v.swap() // -> arr, index, length
v.dupX2() // -> length, arr, index, length
v.pop() // -> length, arr, index
v.dup2() // -> length, arr, index, arr, index
v.aload(elementSerializer.asmType) // -> length, arr, index, obj
v.castIfNeeded(elementSerializer.asmType)
v.load(1, PARCEL_TYPE) // -> length, arr, index, obj, parcel
v.swap() // -> length, arr, index, parcel, obj
elementSerializer.writeValue(v) // -> length, arr, index
v.aconst(1) // -> length, arr, index, (1)
v.add(Type.INT_TYPE) // -> length, arr, (index + 1)
v.swap() // -> length, (index + 1), arr
v.dupX2() // -> arr, length, (index + 1), arr
v.pop() // -> arr, length, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2() // -> arr
v.pop()
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> length
v.dup() // -> length, length
v.newarray(elementSerializer.asmType) // -> length, arr
v.swap() // -> arr, length
v.aconst(0) // -> arr, length, index
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup2() // -> arr, length, index, length, index
v.ificmple(loopIsOver) // -> arr, length, index
v.swap() // -> arr, index, length
v.dupX2() // -> length, arr, index, length
v.pop() // -> length, arr, index
v.dup2() // -> length, arr, index, arr, index
v.load(1, PARCEL_TYPE) // -> length, arr, index, arr, index, parcel
elementSerializer.readValue(v) // -> length, arr, index, arr, index, obj
v.castIfNeeded(elementSerializer.asmType)
v.astore(elementSerializer.asmType) // -> length, arr, index
v.aconst(1) // -> length, arr, index, (1)
v.add(Type.INT_TYPE) // -> length, arr, (index + 1)
v.swap() // -> length, (index + 1), arr
v.dupX2() // -> arr, length, (index + 1), arr
v.pop() // -> arr, length, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2() // -> arr
}
}
internal fun InstructionAdapter.castIfNeeded(targetType: Type) {
if (targetType.sort != Type.OBJECT && targetType.sort != Type.ARRAY) return
if (targetType.descriptor == "Ljava/lang/Object;") return
checkcast(targetType)
}
internal class ListSetParcelSerializer(
asmType: Type,
elementSerializer: ParcelSerializer
) : AbstractCollectionParcelSerializer(asmType, elementSerializer) {
override fun getSize(v: InstructionAdapter) {
v.invokeinterface("java/util/Collection", "size", "()I")
}
override fun getIterator(v: InstructionAdapter) {
v.invokeinterface("java/util/Collection", "iterator", "()Ljava/util/Iterator;")
}
override fun doWriteValue(v: InstructionAdapter) {
// -> parcel, obj
v.castIfNeeded(elementSerializer.asmType)
elementSerializer.writeValue(v)
}
override fun doReadValue(v: InstructionAdapter) {
// -> collection, parcel
elementSerializer.readValue(v) // -> collection, element
v.castIfNeeded(elementSerializer.asmType)
v.invokevirtual(collectionType.internalName, "add", "(Ljava/lang/Object;)Z", false) // -> bool
v.pop()
}
}
internal class MapParcelSerializer(
asmType: Type,
private val keySerializer: ParcelSerializer,
elementSerializer: ParcelSerializer
) : AbstractCollectionParcelSerializer(asmType, elementSerializer) {
override fun getSize(v: InstructionAdapter) {
v.invokeinterface("java/util/Map", "size", "()I")
}
override fun getIterator(v: InstructionAdapter) {
v.invokeinterface("java/util/Map", "entrySet", "()Ljava/util/Set;")
v.invokeinterface("java/util/Set", "iterator", "()Ljava/util/Iterator;")
}
override fun doWriteValue(v: InstructionAdapter) {
// -> parcel, obj
v.dup2() // -> parcel, obj, parcel, obj
v.invokeinterface("java/util/Map\$Entry", "getKey", "()Ljava/lang/Object;") // -> parcel, obj, parcel, key
v.castIfNeeded(keySerializer.asmType)
keySerializer.writeValue(v) // -> parcel, obj
v.invokeinterface("java/util/Map\$Entry", "getValue", "()Ljava/lang/Object;") // -> parcel, value
v.castIfNeeded(elementSerializer.asmType)
elementSerializer.writeValue(v)
}
override fun doReadValue(v: InstructionAdapter) {
// -> map, parcel
v.dup() // -> map, parcel, parcel
keySerializer.readValue(v) // -> map, parcel, key
v.castIfNeeded(keySerializer.asmType)
v.swap() // -> map, key, parcel
elementSerializer.readValue(v) // -> map, key, value
v.castIfNeeded(elementSerializer.asmType)
v.invokeinterface("java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") // -> obj
v.pop()
}
}
abstract internal class AbstractCollectionParcelSerializer(
final override val asmType: Type,
protected val elementSerializer: ParcelSerializer
) : ParcelSerializer {
protected val collectionType: Type = Type.getObjectType(when (asmType.internalName) {
"java/util/List" -> "java/util/ArrayList"
"java/util/Set" -> "java/util/LinkedHashSet"
"java/util/Map" -> "java/util/LinkedHashMap"
else -> asmType.internalName
})
private var hasConstructorWithCapacity = when (collectionType.internalName) {
"java/util/LinkedList", "java/util/TreeSet", "java/util/TreeMap" -> false
else -> true
}
/**
* Stack before: collection
* Stack after: size
*/
protected abstract fun getSize(v: InstructionAdapter)
/**
* Stack before: collection
* Stack after: iterator
*/
protected abstract fun getIterator(v: InstructionAdapter)
/**
* Stack before: parcel, obj
* Stack after: <empty>
*/
protected abstract fun doWriteValue(v: InstructionAdapter)
/**
* Stack before: collection, parcel
* Stack after: <empty>
*/
protected abstract fun doReadValue(v: InstructionAdapter)
override fun writeValue(v: InstructionAdapter) {
val labelIteratorLoop = Label()
val labelReturn = Label()
v.dupX1() // -> collection, parcel, collection
getSize(v) // -> collection, parcel, size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // collection
getIterator(v) // -> iterator
v.visitLabel(labelIteratorLoop)
v.dup() // -> iterator, iterator
v.invokeinterface("java/util/Iterator", "hasNext", "()Z") // -> iterator, hasNext
v.ifeq(labelReturn) // -> iterator
v.dup() // -> iterator, iterator
v.invokeinterface("java/util/Iterator", "next", "()Ljava/lang/Object;") // -> iterator, obj
v.load(1, PARCEL_TYPE) // -> iterator, obj, parcel
v.swap() // -> iterator, parcel, obj
doWriteValue(v) // -> iterator
v.goTo(labelIteratorLoop)
v.visitLabel(labelReturn)
v.pop()
}
override fun readValue(v: InstructionAdapter) {
val nextLoopIteration = Label()
val loopIsOver = Label()
// Read list size
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> size
v.dup() // -> size, size
v.anew(collectionType) // -> size, size, list
v.dupX1() // -> size, list, size, list
v.swap() // -> size, list, list, size
if (hasConstructorWithCapacity) {
v.invokespecial(collectionType.internalName, "<init>", "(I)V", false) // -> size, list
}
else {
v.pop() // -> size, list, list
v.invokespecial(collectionType.internalName, "<init>", "()V", false) // -> size, list
}
v.visitLabel(nextLoopIteration)
v.swap() // -> list, size
v.dupX1() // -> size, list, size
v.ifeq(loopIsOver) // -> size, list
v.dup() // -> size, list, list
v.load(1, PARCEL_TYPE) // -> size, list, list, parcel
doReadValue(v) // -> size, list
v.swap() // -> list, size
v.aconst(-1) // -> list, size, (-1)
v.add(Type.INT_TYPE) // -> list, (size - 1)
v.swap() // -> size, list
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.swap()
v.pop()
}
}
internal class SparseArrayParcelSerializer(override val asmType: Type, private val valueSerializer: ParcelSerializer) : ParcelSerializer {
private val valueType = (valueSerializer as? PrimitiveTypeParcelSerializer)?.asmType ?: Type.getObjectType("java/lang/Object")
override fun writeValue(v: InstructionAdapter) {
v.dup() // -> parcel, arr, arr
v.invokevirtual(asmType.internalName, "size", "()I", false) // -> parcel, arr, size
v.dup2X1() // -> arr, size, parcel, arr, size
v.swap() // -> arr, size, parcel, size, arr
v.pop() // -> arr, size, parcel, size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> arr, size
v.aconst(0) // -> arr, size, <index>
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup2() // -> arr, size, index, size, index
v.ificmple(loopIsOver) // -> arr, size, index
v.swap() // -> arr, index, size
v.dupX2() // -> size, arr, index, size
v.pop() // -> size, arr, index
v.dup2() // -> size, arr, index, arr, index
v.invokevirtual(asmType.internalName, "keyAt", "(I)I", false) // -> size, arr, index, key
v.load(1, PARCEL_TYPE) // size, arr, index, key, parcel
v.dupX2() // -> size, arr, parcel, index, key, parcel
v.swap() // -> size, arr, parcel, index, parcel, key
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> size, arr, parcel, index
v.swap() // -> size, arr, index, parcel
v.dupX2() // -> size, parcel, arr, index, parcel
v.pop() // -> size, parcel, arr, index
v.dup2X1() // -> size, arr, index, parcel, arr, index
v.invokevirtual(asmType.internalName, "valueAt", "(I)${valueType.descriptor}", false) // -> size, arr, index, parcel, value
valueSerializer.writeValue(v) // -> size, arr, index
v.aconst(1) // -> size, arr, index, (1)
v.add(Type.INT_TYPE) // -> size, arr, (index + 1)
v.dup2X1() // -> arr, (index + 1), size, arr, (index + 1)
v.pop2() // -> arr, (index + 1), size
v.swap() // -> arr, size, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2()
v.pop()
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> size
v.dup() // -> size, size
v.anew(asmType) // -> size, size, arr
v.dupX2() // -> arr, size, size, arr
v.swap() // -> arr, size, arr, size
v.invokespecial(asmType.internalName, "<init>", "(I)V", false) // -> arr, size
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup() // -> arr, size, size
v.ifle(loopIsOver) // -> arr, size
v.swap() // -> size, arr
v.dupX1() // -> arr, size, arr
v.load(1, PARCEL_TYPE) // -> arr, size, arr, parcel
v.dup() // -> arr, size, arr, parcel, parcel
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> arr, size, arr, parcel, key
v.swap() // -> arr, size, arr, key, parcel
valueSerializer.readValue(v) // -> arr, size, arr, key, value
v.invokevirtual(asmType.internalName, "put", "(I${valueType.descriptor})V", false) // -> arr, size
v.aconst(-1) // -> arr, size, (-1)
v.add(Type.INT_TYPE) // -> arr, (size - 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop() // -> arr
}
}
internal class NullAwareParcelSerializerWrapper(val delegate: ParcelSerializer) : ParcelSerializer {
override val asmType: Type
get() = delegate.asmType
override fun writeValue(v: InstructionAdapter) = writeValueNullAware(v) {
delegate.writeValue(v)
}
override fun readValue(v: InstructionAdapter) = readValueNullAware(v) {
delegate.readValue(v)
}
}
/** write...() and get...() methods in Android should support passing `null` values. */
internal class NullCompliantObjectParcelSerializer(
override val asmType: Type,
writeMethod: Method<String?>,
readMethod: Method<String?>
) : ParcelSerializer {
private val writeMethod = Method(writeMethod.name, writeMethod.signature ?: "(${asmType.descriptor})V")
private val readMethod = Method(readMethod.name, readMethod.signature ?: "()${asmType.descriptor}")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, writeMethod.name, writeMethod.signature, false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, readMethod.name, readMethod.signature, false)
v.castIfNeeded(asmType)
}
}
internal class BoxedPrimitiveTypeParcelSerializer private constructor(val unboxedType: Type) : ParcelSerializer {
companion object {
private val BOXED_TO_UNBOXED_TYPE_MAPPINGS = mapOf(
"java/lang/Boolean" to Type.BOOLEAN_TYPE,
"java/lang/Character" to Type.CHAR_TYPE,
"java/lang/Byte" to Type.BYTE_TYPE,
"java/lang/Short" to Type.SHORT_TYPE,
"java/lang/Integer" to Type.INT_TYPE,
"java/lang/Float" to Type.FLOAT_TYPE,
"java/lang/Long" to Type.LONG_TYPE,
"java/lang/Double" to Type.DOUBLE_TYPE)
private val UNBOXED_TO_BOXED_TYPE_MAPPINGS = BOXED_TO_UNBOXED_TYPE_MAPPINGS.map { it.value to it.key }.toMap()
private val BOXED_VALUE_METHOD_NAMES = mapOf(
"java/lang/Boolean" to "booleanValue",
"java/lang/Character" to "charValue",
"java/lang/Byte" to "byteValue",
"java/lang/Short" to "shortValue",
"java/lang/Integer" to "intValue",
"java/lang/Float" to "floatValue",
"java/lang/Long" to "longValue",
"java/lang/Double" to "doubleValue")
private val INSTANCES = BOXED_TO_UNBOXED_TYPE_MAPPINGS.values.map { type ->
type to BoxedPrimitiveTypeParcelSerializer(type)
}.toMap()
fun forUnboxedType(type: Type) = INSTANCES[type] ?: error("Unsupported type $type")
fun forBoxedType(type: Type) = INSTANCES[BOXED_TO_UNBOXED_TYPE_MAPPINGS[type.internalName]] ?: error("Unsupported type $type")
}
override val asmType: Type = Type.getObjectType(UNBOXED_TO_BOXED_TYPE_MAPPINGS[unboxedType] ?: error("Unsupported type $unboxedType"))
val unboxedSerializer = PrimitiveTypeParcelSerializer.getInstance(unboxedType)
val typeValueMethodName = BOXED_VALUE_METHOD_NAMES[asmType.internalName]!!
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(asmType.internalName, typeValueMethodName, "()${unboxedType.descriptor}", false)
unboxedSerializer.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
unboxedSerializer.readValue(v)
v.invokestatic(asmType.internalName, "valueOf", "(${unboxedType.descriptor})${asmType.descriptor}", false)
}
}
internal open class PrimitiveTypeParcelSerializer private constructor(final override val asmType: Type) : ParcelSerializer {
companion object {
private val WRITE_METHOD_NAMES = mapOf(
Type.BOOLEAN_TYPE to Method("writeInt", "(I)V"),
Type.CHAR_TYPE to Method("writeInt", "(I)V"),
Type.BYTE_TYPE to Method("writeByte", "(B)V"),
Type.SHORT_TYPE to Method("writeInt", "(I)V"),
Type.INT_TYPE to Method("writeInt", "(I)V"),
Type.FLOAT_TYPE to Method("writeFloat", "(F)V"),
Type.LONG_TYPE to Method("writeLong", "(J)V"),
Type.DOUBLE_TYPE to Method("writeDouble", "(D)V"))
private val READ_METHOD_NAMES = mapOf(
Type.BOOLEAN_TYPE to Method("readInt", "()I"),
Type.CHAR_TYPE to Method("readInt", "()I"),
Type.BYTE_TYPE to Method("readByte", "()B"),
Type.SHORT_TYPE to Method("readInt", "()I"),
Type.INT_TYPE to Method("readInt", "()I"),
Type.FLOAT_TYPE to Method("readFloat", "()F"),
Type.LONG_TYPE to Method("readLong", "()J"),
Type.DOUBLE_TYPE to Method("readDouble", "()D"))
private val INSTANCES = READ_METHOD_NAMES.keys.map {
it to when (it) {
Type.CHAR_TYPE -> CharParcelSerializer
Type.SHORT_TYPE -> ShortParcelSerializer
else -> PrimitiveTypeParcelSerializer(it)
}
}.toMap()
fun getInstance(type: Type) = INSTANCES[type] ?: error("Unsupported type ${type.descriptor}")
}
object CharParcelSerializer : PrimitiveTypeParcelSerializer(Type.CHAR_TYPE) {
override fun writeValue(v: InstructionAdapter) {
v.cast(Type.CHAR_TYPE, Type.INT_TYPE)
super.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
super.readValue(v)
v.cast(Type.INT_TYPE, Type.CHAR_TYPE)
}
}
object ShortParcelSerializer : PrimitiveTypeParcelSerializer(Type.SHORT_TYPE) {
override fun writeValue(v: InstructionAdapter) {
v.cast(Type.SHORT_TYPE, Type.INT_TYPE)
super.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
super.readValue(v)
v.cast(Type.INT_TYPE, Type.SHORT_TYPE)
}
}
private val writeMethod = WRITE_METHOD_NAMES[asmType]!!
private val readMethod = READ_METHOD_NAMES[asmType]!!
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, writeMethod.name, writeMethod.signature, false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, readMethod.name, readMethod.signature, false)
}
}
private fun readValueNullAware(v: InstructionAdapter, block: () -> Unit) {
val labelNull = Label()
val labelReturn = Label()
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false)
v.ifeq(labelNull)
v.load(1, PARCEL_TYPE)
block()
v.goTo(labelReturn)
// Just push null on stack if the value is null
v.visitLabel(labelNull)
v.aconst(null)
v.visitLabel(labelReturn)
}
private fun writeValueNullAware(v: InstructionAdapter, block: () -> Unit) {
val labelReturn = Label()
val labelNull = Label()
v.dup()
v.ifnull(labelNull)
// Write 1 if non-null, 0 if null
v.load(1, PARCEL_TYPE)
v.aconst(1)
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false)
block()
v.goTo(labelReturn)
labelNull@ v.visitLabel(labelNull)
v.pop()
v.aconst(0)
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false)
labelReturn@ v.visitLabel(labelReturn)
}
internal class Method<out T : String?>(val name: String, val signature: T) {
companion object {
operator fun invoke(name: String) = Method(name, null)
}
}
@@ -18,6 +18,9 @@ package org.jetbrains.kotlin.android.synthetic
import com.intellij.mock.MockProject
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
@@ -41,6 +44,8 @@ import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.android.synthetic.codegen.ParcelableClinitClassBuilderInterceptorExtension
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
object AndroidConfigurationKeys {
val VARIANT: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("Android build variant")
@@ -75,7 +80,17 @@ class AndroidCommandLineProcessor : CommandLineProcessor {
}
class AndroidComponentRegistrar : ComponentRegistrar {
companion object {
fun registerParcelExtensions(project: Project) {
ExpressionCodegenExtension.registerExtension(project, ParcelableCodegenExtension())
SyntheticResolveExtension.registerExtension(project, ParcelableResolveExtension())
ClassBuilderInterceptorExtension.registerExtension(project, ParcelableClinitClassBuilderInterceptorExtension())
}
}
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
registerParcelExtensions(project)
val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE)
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: emptyList()
val isExperimental = configuration.get(AndroidConfigurationKeys.EXPERIMENTAL) == "true"
@@ -0,0 +1,14 @@
package android.os
class Parcel
interface Parcelable {
fun describeContents(): Int
fun writeToParcel(parcel: Parcel, flags: Int)
interface Creator<T> {
fun createFromParcel(parcel: Parcel): T
fun newArray(size: Int): Array<T>
}
}
@@ -0,0 +1,38 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import java.util.Arrays
@MagicParcel
data class Test(val a: Array<String>) : Parcelable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Test
if (!Arrays.equals(a, other.a)) return false
return true
}
override fun hashCode() = Arrays.hashCode(a)
}
fun box() = parcelTest { parcel ->
val first = Test(arrayOf("A", "B"))
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
}
@@ -0,0 +1,76 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import java.util.Arrays
@MagicParcel
data class Test(
val a: Array<String>,
val b: Array<String?>,
val c: IntArray,
val d: CharArray?,
val e: Array<IntArray>,
val f: Array<List<String>>,
val g: List<Array<String>>,
val h: Array<String>?
) : Parcelable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Test
if (!Arrays.equals(a, other.a)) return false
if (!Arrays.equals(b, other.b)) return false
if (!Arrays.equals(c, other.c)) return false
if (!Arrays.equals(d, other.d)) return false
if (!Arrays.deepEquals(e, other.e)) return false
if (!Arrays.equals(f, other.f)) return false
if (g.size != other.g.size) return false
if (!g.zip(other.g).all { (f, s) -> Arrays.equals(f, s) }) return false
if (!Arrays.equals(h, other.h)) return false
return true
}
override fun hashCode(): Int {
var result = Arrays.hashCode(a)
result = 31 * result + Arrays.hashCode(b)
result = 31 * result + Arrays.hashCode(c)
result = 31 * result + (d?.let { Arrays.hashCode(it) } ?: 0)
result = 31 * result + Arrays.hashCode(e)
result = 31 * result + Arrays.hashCode(f)
result = 31 * result + g.hashCode()
result = 31 * result + (h?.let { Arrays.hashCode(it) } ?: 0)
return result
}
}
fun box() = parcelTest { parcel ->
val first = Test(
a = arrayOf("A", "B", "C"),
b = arrayOf("A", null, "B"),
c = intArrayOf(1, 2, 3),
d = null,
e = arrayOf(intArrayOf(2, 4, 1), intArrayOf(10, 20)),
f = arrayOf(listOf("A"), listOf("B", "C")),
g = listOf(arrayOf("Z", "X"), arrayOf()),
h = arrayOf("")
)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
}
@@ -0,0 +1,41 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class BoxedTypes(
val boo: java.lang.Boolean,
val c: java.lang.Character,
val byt: java.lang.Byte,
val s: java.lang.Short,
val i: java.lang.Integer,
val f: java.lang.Float,
val l: java.lang.Long,
val d: java.lang.Double
) : Parcelable
fun box() = parcelTest { parcel ->
val first = BoxedTypes(
true as java.lang.Boolean,
'#' as java.lang.Character,
3.toByte() as java.lang.Byte,
10.toShort() as java.lang.Short,
-300 as java.lang.Integer,
-5.0f as java.lang.Float,
Long.MAX_VALUE as java.lang.Long,
3.14 as java.lang.Double)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<BoxedTypes>(parcel)
assert(first == first2)
}
@@ -0,0 +1,34 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import android.os.Bundle
@MagicParcel
data class User(val a: Bundle) : Parcelable
fun box() = parcelTest { parcel ->
val test = User(Bundle().apply { putChar("A", 'c'); putByte("B", 40.toByte()); putString("C", "ABC") })
test.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val test2 = readFromParcel<User>(parcel)
assert(compareBundles(test.a, test2.a))
}
private fun compareBundles(first: Bundle, second: Bundle): Boolean {
if (first.size() != second.size()) return false
for (key in first.keySet()) {
if (first.get(key) != second.get(key)) return false
}
return true
}
@@ -0,0 +1,48 @@
// WITH_RUNTIME
// FULL_JDK
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import java.util.*
@MagicParcel
data class Test(
val a: List<String>,
val b: MutableList<String>,
val c: ArrayList<String>,
val d: LinkedList<String>,
val e: Set<String>,
val f: MutableSet<String>,
val g: TreeSet<String>,
val h: HashSet<String>,
val i: LinkedHashSet<String>
) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(
a = listOf("A"),
b = mutableListOf("B"),
c = ArrayList<String>().apply { this += "C" },
d = LinkedList<String>().apply { this += "D" },
e = setOf("E"),
f = mutableSetOf("F"),
g = TreeSet<String>().apply { this += "G" },
h = HashSet<String>().apply { this += "H" },
i = LinkedHashSet<String>().apply { this += "I" }
)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
assert((first.d as LinkedList<*>).size == 1)
assert((first2.h as HashSet<*>).size == 1)
}
@@ -0,0 +1,24 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(val a: List<String>) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(listOf("A", "B"))
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
}
@@ -0,0 +1,41 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(
val a: List<String>,
val b: List<String?>,
val c: List<Int>,
val d: List<Int?>,
val e: List<List<String>?>,
val f: List<List<List<Int>>>
) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(
a = listOf("A", "B"),
b = listOf("A", null, "C"),
c = listOf(1, 2, 3),
d = listOf(1, null, 5),
e = listOf(listOf("A", "B"), listOf(), null),
f = listOf(listOf(listOf(1, 2), listOf(3)), listOf(listOf(5, 3)))
)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
assert(first2.a == listOf("A", "B"))
assert(first2.b.size == 3)
}
@@ -0,0 +1,40 @@
// WITH_RUNTIME
// FULL_JDK
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import java.util.*
@MagicParcel
data class Test(
val a: Map<String, String>,
val b: MutableMap<String, String>,
val c: HashMap<String, String>,
val d: LinkedHashMap<String, String>,
val e: TreeMap<String, String>
) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(
a = mapOf("A" to "B"),
b = mutableMapOf("A" to "B"),
c = HashMap<String, String>().apply { put("A", "B") },
d = LinkedHashMap<String, String>().apply { put("A", "B") },
e = TreeMap<String, String>().apply { put("A", "B") }
)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
assert((first.c as HashMap<*, *>).size == 1)
assert((first2.e as TreeMap<*, *>).size == 1)
}
@@ -0,0 +1,24 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(val a: Map<String, String>) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(mapOf("A" to "B", "C" to "D"))
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
}
@@ -0,0 +1,40 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(
val a: Map<String, String>,
val b: Map<String?, String>,
val c: Map<String, String?>,
val d: Map<String, Map<Int, String>>,
val e: Map<Int?, List<String>>,
val f: Map<Boolean, Boolean>,
val g: Map<String, Map<String, Map<String, String>>>
) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test(
a = mapOf("A" to "B", "C" to "D"),
b = mapOf("A" to "B", null to "D", "E" to "F"),
c = mapOf("A" to null, "C" to "D"),
d = mapOf("A" to mapOf(1 to "", 2 to "x")),
e = mapOf(1 to listOf("", ""), null to listOf()),
f = mapOf(true to false, false to true),
g = mapOf("A" to mapOf("B" to mapOf("C" to "D", "E" to "F")))
)
first.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
assert(first == first2)
}
@@ -0,0 +1,33 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(
val str1: String,
val str2: String?,
val int1: Int,
val int2: Int?
) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test("John", "Smith", 20, 30)
val second = Test("A", null, 20, null)
first.writeToParcel(parcel, 0)
second.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
val second2 = readFromParcel<Test>(parcel)
assert(first == first2)
assert(second == second2)
}
@@ -0,0 +1,28 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class Test(val a: String?) : Parcelable
fun box() = parcelTest { parcel ->
val first = Test("John")
val second = Test(null)
first.writeToParcel(parcel, 0)
second.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<Test>(parcel)
val second2 = readFromParcel<Test>(parcel)
assert(first == first2)
assert(second == second2)
}
@@ -0,0 +1,38 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@MagicParcel
data class PrimitiveTypes(
val boo: Boolean,
val c: Char,
val byt: Byte,
val s: Short,
val i: Int,
val f: Float,
val l: Long,
val d: Double
) : Parcelable
fun box() = parcelTest { parcel ->
val first = PrimitiveTypes(true, '#', 3.toByte(), 10.toShort(), -300, -5.0f, Long.MAX_VALUE, 3.14)
val second = PrimitiveTypes(false, '\n', Byte.MIN_VALUE, Short.MIN_VALUE, Int.MIN_VALUE, Float.POSITIVE_INFINITY,
Long.MAX_VALUE, Double.NEGATIVE_INFINITY)
first.writeToParcel(parcel, 0)
second.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val first2 = readFromParcel<PrimitiveTypes>(parcel)
val second2 = readFromParcel<PrimitiveTypes>(parcel)
assert(first == first2)
assert(second == second2)
}
@@ -0,0 +1,22 @@
// 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
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 == user2)
}
@@ -0,0 +1,70 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import android.util.*
@MagicParcel
data class Data(val a: String, val b: String) : Parcelable
@MagicParcel
data class User(val a: SparseIntArray, val b: SparseLongArray, val c: SparseArray<Data>) : Parcelable
fun box() = parcelTest { parcel ->
val user = User(
a = SparseIntArray().apply { put(1, 5); put(100, -1); put(1000, 0) },
b = SparseLongArray().apply { put(3, 2); put(2, 3); put(10, 10) },
c = SparseArray<Data>().apply { put(1, Data("A", "B")); put(10, Data("C", "D")); put(105, Data("E", "")) }
)
user.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val user2 = readFromParcel<User>(parcel)
assert(compareSparseIntArrays(user.a, user2.a))
assert(compareSparseLongArrays(user.b, user2.b))
assert(compareSparseArrays(user.c, user2.c))
}
private fun compareSparseIntArrays(first: SparseIntArray, second: SparseIntArray): Boolean {
if (first === second) return true
if (first.size() != second.size()) return false
for (i in 0 until first.size()) {
if (first.keyAt(i) != second.keyAt(i)) return false
if (first.valueAt(i) != second.valueAt(i)) return false
}
return true
}
private fun compareSparseLongArrays(first: SparseLongArray, second: SparseLongArray): Boolean {
if (first === second) return true
if (first.size() != second.size()) return false
for (i in 0 until first.size()) {
if (first.keyAt(i) != second.keyAt(i)) return false
if (first.valueAt(i) != second.valueAt(i)) return false
}
return true
}
private fun compareSparseArrays(first: SparseArray<*>, second: SparseArray<*>): Boolean {
if (first === second) return true
if (first.size() != second.size()) return false
for (i in 0 until first.size()) {
if (first.keyAt(i) != second.keyAt(i)) return false
if (first.valueAt(i) != second.valueAt(i)) return false
}
return true
}
@@ -0,0 +1,36 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import android.util.SparseBooleanArray
@MagicParcel
data class User(val a: SparseBooleanArray) : Parcelable
fun box() = parcelTest { parcel ->
val test = User(SparseBooleanArray().apply { put(1, false); put(5, true); put(1000, false) })
test.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
val test2 = readFromParcel<User>(parcel)
assert(compareSparseBooleanArrays(test.a, test2.a))
}
private fun compareSparseBooleanArrays(first: SparseBooleanArray, second: SparseBooleanArray): Boolean {
if (first === second) return true
if (first.size() != second.size()) return false
for (i in 0 until first.size()) {
if (first.keyAt(i) != second.keyAt(i)) return false
if (first.valueAt(i) != second.valueAt(i)) return false
}
return true
}
@@ -0,0 +1,18 @@
package test
import android.os.Parcel
fun parcelTest(block: (Parcel) -> Unit): String {
val parcel = Parcel.obtain()
try {
block(parcel)
return "OK"
} finally {
parcel.recycle()
}
}
inline fun <reified T> readFromParcel(parcel: Parcel): T {
val creator = T::class.java.getDeclaredField("CREATOR").get(null)
return creator::class.java.getDeclaredMethod("createFromParcel", Parcel::class.java).invoke(creator, parcel) as T
}
@@ -0,0 +1,6 @@
// CURIOUS_ABOUT writeToParcel
import kotlinx.android.parcel.*
@MagicParcel
class Test(val names: List<List<List<String>>>)
@@ -0,0 +1,82 @@
public static class Test$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final Test createFromParcel(android.os.Parcel p0)
public final Test[] newArray(int p0)
}
public final class Test : java/lang/Object {
public final static Test$CREATOR CREATOR
private final java.util.List names
static void <clinit>()
public void <init>(java.util.List p0)
public final int describeContents()
public final java.util.List getNames()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (names, Ljava/util/List;)
DUP_X1
INVOKEINTERFACE (java/util/Collection, size, ()I)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;)
LABEL (L1)
DUP
INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z)
IFEQ (L2)
DUP
INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;)
ALOAD (1)
SWAP
CHECKCAST
DUP_X1
INVOKEINTERFACE (java/util/Collection, size, ()I)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;)
LABEL (L3)
DUP
INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z)
IFEQ (L4)
DUP
INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;)
ALOAD (1)
SWAP
CHECKCAST
DUP_X1
INVOKEINTERFACE (java/util/Collection, size, ()I)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;)
LABEL (L5)
DUP
INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z)
IFEQ (L6)
DUP
INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;)
ALOAD (1)
SWAP
CHECKCAST
INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V)
GOTO (L5)
LABEL (L6)
POP
GOTO (L3)
LABEL (L4)
POP
GOTO (L1)
LABEL (L2)
POP
RETURN
LABEL (L7)
}
}
@@ -0,0 +1,10 @@
// CURIOUS_ABOUT writeToParcel
import android.util.Size
import kotlinx.android.parcel.*
@MagicParcel
class TestNullable(val a: Size?)
@MagicParcel
class TestNotNull(val a: Size)
@@ -0,0 +1,80 @@
public static class TestNotNull$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final TestNotNull createFromParcel(android.os.Parcel p0)
public final TestNotNull[] newArray(int p0)
}
public final class TestNotNull : java/lang/Object {
public final static TestNotNull$CREATOR CREATOR
private final android.util.Size a
static void <clinit>()
public void <init>(android.util.Size p0)
public final int describeContents()
public final android.util.Size getA()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (a, Landroid/util/Size;)
INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V)
RETURN
LABEL (L1)
}
}
public static class TestNullable$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final TestNullable createFromParcel(android.os.Parcel p0)
public final TestNullable[] newArray(int p0)
}
public final class TestNullable : java/lang/Object {
public final static TestNullable$CREATOR CREATOR
private final android.util.Size a
static void <clinit>()
public void <init>(android.util.Size p0)
public final int describeContents()
public final android.util.Size getA()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (a, Landroid/util/Size;)
DUP
IFNULL (L1)
ALOAD (1)
LDC (1)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V)
GOTO (L2)
LABEL (L1)
POP
LDC (0)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
LABEL (L2)
RETURN
LABEL (L3)
}
}
@@ -0,0 +1,10 @@
// CURIOUS_ABOUT writeToParcel, createFromParcel, <clinit>
import kotlinx.android.parcel.*
import android.os.Parcelable
import java.io.Serializable
class SerializableSimple(val a: String, val b: String) : Serializable
@MagicParcel
class User(val notNull: SerializableSimple, val nullable: SerializableSimple) : Parcelable
@@ -0,0 +1,74 @@
public final class SerializableSimple : java/lang/Object, java/io/Serializable {
private final java.lang.String a
private final java.lang.String b
public void <init>(java.lang.String p0, java.lang.String p1)
public final java.lang.String getA()
public final java.lang.String getB()
}
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)
NEW
DUP
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()LSerializableSimple;)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()LSerializableSimple;)
INVOKESPECIAL (User, <init>, (LSerializableSimple;LSerializableSimple;)V)
ARETURN
LABEL (L1)
}
public final User[] newArray(int p0)
}
public final class User : java/lang/Object, android/os/Parcelable {
public final static User$CREATOR CREATOR
private final SerializableSimple notNull
private final SerializableSimple nullable
static void <clinit>() {
NEW
DUP
INVOKESPECIAL (User$CREATOR, <init>, ()V)
PUTSTATIC (CREATOR, LUser$CREATOR;)
RETURN
}
public void <init>(SerializableSimple p0, SerializableSimple p1)
public final int describeContents()
public final SerializableSimple getNotNull()
public final SerializableSimple getNullable()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (notNull, LSerializableSimple;)
INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (LSerializableSimple;)V)
ALOAD (1)
ALOAD (0)
GETFIELD (nullable, LSerializableSimple;)
INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (LSerializableSimple;)V)
RETURN
LABEL (L1)
}
}
@@ -0,0 +1,7 @@
// CURIOUS_ABOUT writeToParcel, createFromParcel, <clinit>
import kotlinx.android.parcel.*
import android.os.Parcelable
@MagicParcel
class User(val firstName: String, val lastName: String, val age: Int, val isProUser: Boolean) : Parcelable
@@ -0,0 +1,82 @@
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)
NEW
DUP
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readInt, ()I)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readInt, ()I)
INVOKESPECIAL (User, <init>, (Ljava/lang/String;Ljava/lang/String;IZ)V)
ARETURN
LABEL (L1)
}
public final User[] newArray(int p0)
}
public final class User : java/lang/Object, android/os/Parcelable {
public final static User$CREATOR CREATOR
private final int age
private final java.lang.String firstName
private final boolean isProUser
private final java.lang.String lastName
static void <clinit>() {
NEW
DUP
INVOKESPECIAL (User$CREATOR, <init>, ()V)
PUTSTATIC (CREATOR, LUser$CREATOR;)
RETURN
}
public void <init>(java.lang.String p0, java.lang.String p1, int p2, boolean p3)
public final int describeContents()
public final int getAge()
public final java.lang.String getFirstName()
public final java.lang.String getLastName()
public final boolean isProUser()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (firstName, Ljava/lang/String;)
INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V)
ALOAD (1)
ALOAD (0)
GETFIELD (lastName, Ljava/lang/String;)
INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V)
ALOAD (1)
ALOAD (0)
GETFIELD (age, I)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
ALOAD (1)
ALOAD (0)
GETFIELD (isProUser, Z)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
RETURN
LABEL (L1)
}
}
@@ -0,0 +1,6 @@
// CURIOUS_ABOUT writeToParcel
import kotlinx.android.parcel.*
@MagicParcel
class Test(val names: List<String>)
@@ -0,0 +1,50 @@
public static class Test$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final Test createFromParcel(android.os.Parcel p0)
public final Test[] newArray(int p0)
}
public final class Test : java/lang/Object {
public final static Test$CREATOR CREATOR
private final java.util.List names
static void <clinit>()
public void <init>(java.util.List p0)
public final int describeContents()
public final java.util.List getNames()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (names, Ljava/util/List;)
DUP_X1
INVOKEINTERFACE (java/util/Collection, size, ()I)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;)
LABEL (L1)
DUP
INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z)
IFEQ (L2)
DUP
INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;)
ALOAD (1)
SWAP
CHECKCAST
INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V)
GOTO (L1)
LABEL (L2)
POP
RETURN
LABEL (L3)
}
}
@@ -0,0 +1,12 @@
// CURIOUS_ABOUT writeToParcel, createFromParcel
import kotlinx.android.parcel.*
import android.os.Parcelable
import android.util.Size
import android.util.SizeF
@MagicParcel
data class Test(val size: Size, val nullable: Size?) : Parcelable
@MagicParcel
data class TestF(val size: SizeF, val nullable: SizeF?) : Parcelable
@@ -0,0 +1,179 @@
public static class Test$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final Test createFromParcel(android.os.Parcel p0) {
LABEL (L0)
ALOAD (1)
LDC (in)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
LABEL (L1)
NEW
DUP
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readInt, ()I)
IFEQ (L2)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;)
GOTO (L3)
LABEL (L2)
ACONST_NULL
LABEL (L3)
INVOKESPECIAL (Test, <init>, (Landroid/util/Size;Landroid/util/Size;)V)
ARETURN
LABEL (L4)
}
public final Test[] newArray(int p0)
}
public final class Test : java/lang/Object, android/os/Parcelable {
public final static Test$CREATOR CREATOR
private final android.util.Size nullable
private final android.util.Size size
static void <clinit>()
public void <init>(android.util.Size p0, android.util.Size p1)
public final android.util.Size component1()
public final android.util.Size component2()
public final Test copy(android.util.Size p0, android.util.Size p1)
public static Test copy$default(Test p0, android.util.Size p1, android.util.Size p2, int p3, java.lang.Object p4)
public final int describeContents()
public boolean equals(java.lang.Object p0)
public final android.util.Size getNullable()
public final android.util.Size getSize()
public int hashCode()
public java.lang.String toString()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (size, Landroid/util/Size;)
INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V)
ALOAD (1)
ALOAD (0)
GETFIELD (nullable, Landroid/util/Size;)
DUP
IFNULL (L1)
ALOAD (1)
LDC (1)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V)
GOTO (L2)
LABEL (L1)
POP
LDC (0)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
LABEL (L2)
RETURN
LABEL (L3)
}
}
public static class TestF$CREATOR : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final TestF createFromParcel(android.os.Parcel p0) {
LABEL (L0)
ALOAD (1)
LDC (in)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
LABEL (L1)
NEW
DUP
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readInt, ()I)
IFEQ (L2)
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;)
GOTO (L3)
LABEL (L2)
ACONST_NULL
LABEL (L3)
INVOKESPECIAL (TestF, <init>, (Landroid/util/SizeF;Landroid/util/SizeF;)V)
ARETURN
LABEL (L4)
}
public final TestF[] newArray(int p0)
}
public final class TestF : java/lang/Object, android/os/Parcelable {
public final static TestF$CREATOR CREATOR
private final android.util.SizeF nullable
private final android.util.SizeF size
static void <clinit>()
public void <init>(android.util.SizeF p0, android.util.SizeF p1)
public final android.util.SizeF component1()
public final android.util.SizeF component2()
public final TestF copy(android.util.SizeF p0, android.util.SizeF p1)
public static TestF copy$default(TestF p0, android.util.SizeF p1, android.util.SizeF p2, int p3, java.lang.Object p4)
public final int describeContents()
public boolean equals(java.lang.Object p0)
public final android.util.SizeF getNullable()
public final android.util.SizeF getSize()
public int hashCode()
public java.lang.String toString()
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)
ALOAD (1)
ALOAD (0)
GETFIELD (size, Landroid/util/SizeF;)
INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V)
ALOAD (1)
ALOAD (0)
GETFIELD (nullable, Landroid/util/SizeF;)
DUP
IFNULL (L1)
ALOAD (1)
LDC (1)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V)
GOTO (L2)
LABEL (L1)
POP
LDC (0)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
LABEL (L2)
RETURN
LABEL (L3)
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.android.parcel
/**
* Instructs the Kotlin compiler to generate `writeToParcel()`, `describeContents()` [android.os.Parcelable] methods,
* as well as a `CREATOR` factory class automatically.
*
* The annotation is applicable only to classes that implements [android.os.Parcelable] (directly or indirectly).
* Note that only the primary constructor properties will be serialized.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class MagicParcel