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"