mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
JVM_IR: implement view caching in kotlin-android-extensions
#KT-47733 Fixed
This commit is contained in:
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
@@ -153,38 +152,6 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC
|
||||
context.generateCachedFindViewByIdFunction()
|
||||
context.generateClearCacheFunction()
|
||||
context.generateCacheField()
|
||||
|
||||
if (containerOptions.containerType.isFragment) {
|
||||
val classMembers = container.unsubstitutedMemberScope.getContributedDescriptors()
|
||||
val onDestroy = classMembers.firstOrNull { it is FunctionDescriptor && it.isOnDestroyFunction() }
|
||||
if (onDestroy == null) {
|
||||
context.generateOnDestroyFunctionForFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FunctionDescriptor.isOnDestroyFunction(): Boolean {
|
||||
return kind == CallableMemberDescriptor.Kind.DECLARATION
|
||||
&& name.asString() == ON_DESTROY_METHOD_NAME
|
||||
&& (visibility == DescriptorVisibilities.INHERITED || visibility == DescriptorVisibilities.PUBLIC)
|
||||
&& (valueParameters.isEmpty())
|
||||
&& (typeParameters.isEmpty())
|
||||
}
|
||||
|
||||
// This generates a simple onDestroy(): Unit = super.onDestroy() function.
|
||||
// CLEAR_CACHE_METHOD_NAME() method call will be inserted in ClassBuilder interceptor.
|
||||
private fun SyntheticPartsGenerateContext.generateOnDestroyFunctionForFragment() {
|
||||
val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC or ACC_SYNTHETIC, ON_DESTROY_METHOD_NAME, "()V", null, null)
|
||||
methodVisitor.visitCode()
|
||||
val iv = InstructionAdapter(methodVisitor)
|
||||
|
||||
val containerType = state.typeMapper.mapClass(container)
|
||||
|
||||
iv.load(0, containerType)
|
||||
iv.invokespecial(state.typeMapper.mapClass(container.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false)
|
||||
iv.areturn(Type.VOID_TYPE)
|
||||
|
||||
FunctionCodegen.endVisit(methodVisitor, ON_DESTROY_METHOD_NAME, classOrObject)
|
||||
}
|
||||
|
||||
private fun SyntheticPartsGenerateContext.generateClearCacheFunction() {
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
@@ -28,6 +27,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
|
||||
@@ -73,8 +73,15 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
|
||||
private val cachedMethods = mutableMapOf<FqName, IrSimpleFunction>()
|
||||
private val cachedFields = mutableMapOf<FqName, IrField>()
|
||||
|
||||
private val cachedCacheFields = mutableMapOf<IrClass, IrField>()
|
||||
private val cachedCacheClearFuns = mutableMapOf<IrClass, IrSimpleFunction>()
|
||||
private val cachedCacheLookupFuns = mutableMapOf<IrClass, IrSimpleFunction>()
|
||||
|
||||
private val irFactory: IrFactory = IrFactoryImpl
|
||||
|
||||
private fun irBuilder(scope: IrSymbol, replacing: IrStatement): IrBuilderWithScope =
|
||||
DeclarationIrBuilder(IrGeneratorContextBase(pluginContext.irBuiltIns), scope, replacing.startOffset, replacing.endOffset)
|
||||
|
||||
private fun createPackage(fqName: FqName) =
|
||||
cachedPackages.getOrPut(fqName) {
|
||||
IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment(pluginContext.moduleDescriptor, fqName)
|
||||
@@ -111,6 +118,79 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
|
||||
createClass(fqName.parent()).addField(fqName.shortName(), type, DescriptorVisibilities.PUBLIC)
|
||||
}
|
||||
|
||||
// NOTE: sparse array version intentionally not implemented; this plugin is deprecated
|
||||
private val mapFactory = pluginContext.irBuiltIns.findFunctions(Name.identifier("mutableMapOf"), "kotlin", "collections")
|
||||
.single { it.descriptor.valueParameters.isEmpty() } // unbound - can't use `owner`
|
||||
private val mapGet = pluginContext.irBuiltIns.mapClass.owner.functions
|
||||
.single { it.name.asString() == "get" && it.valueParameters.size == 1 }
|
||||
private val mapSet = pluginContext.irBuiltIns.mutableMapClass.owner.functions
|
||||
.single { it.name.asString() == "put" && it.valueParameters.size == 2 }
|
||||
private val mapClear = pluginContext.irBuiltIns.mutableMapClass.owner.functions
|
||||
.single { it.name.asString() == "clear" && it.valueParameters.isEmpty() }
|
||||
|
||||
private fun IrClass.getCacheField(): IrField =
|
||||
cachedCacheFields.getOrPut(this) {
|
||||
val viewType = createClass(FqName(AndroidConst.VIEW_FQNAME)).defaultType.makeNullable()
|
||||
irFactory.buildField {
|
||||
name = Name.identifier(AbstractAndroidExtensionsExpressionCodegenExtension.PROPERTY_NAME)
|
||||
type = pluginContext.irBuiltIns.mutableMapClass.typeWith(pluginContext.irBuiltIns.intType, viewType)
|
||||
}.apply {
|
||||
parent = this@getCacheField
|
||||
initializer = irBuilder(symbol, this).run {
|
||||
irExprBody(irCall(mapFactory, type, valueArgumentsCount = 0, typeArgumentsCount = 2).apply {
|
||||
putTypeArgument(0, context.irBuiltIns.intType)
|
||||
putTypeArgument(1, viewType)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrClass.getClearCacheFun(): IrSimpleFunction =
|
||||
cachedCacheClearFuns.getOrPut(this) {
|
||||
irFactory.buildFun {
|
||||
name = Name.identifier(AbstractAndroidExtensionsExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME)
|
||||
modality = Modality.OPEN
|
||||
returnType = pluginContext.irBuiltIns.unitType
|
||||
}.apply {
|
||||
val self = addDispatchReceiver { type = defaultType }
|
||||
parent = this@getClearCacheFun
|
||||
body = irBuilder(symbol, this).irBlockBody {
|
||||
+irCall(mapClear).apply { dispatchReceiver = irGetField(irGet(self), getCacheField()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrClass.getCachedFindViewByIdFun(): IrSimpleFunction =
|
||||
cachedCacheLookupFuns.getOrPut(this) {
|
||||
val containerType = ContainerOptionsProxy.create(descriptor).containerType
|
||||
val viewType = createClass(FqName(AndroidConst.VIEW_FQNAME)).defaultType.makeNullable()
|
||||
irFactory.buildFun {
|
||||
name = Name.identifier(AbstractAndroidExtensionsExpressionCodegenExtension.CACHED_FIND_VIEW_BY_ID_METHOD_NAME)
|
||||
modality = Modality.OPEN
|
||||
returnType = viewType
|
||||
}.apply {
|
||||
val self = addDispatchReceiver { type = defaultType }
|
||||
val resourceId = addValueParameter("id", pluginContext.irBuiltIns.intType)
|
||||
parent = this@getCachedFindViewByIdFun
|
||||
body = irBuilder(symbol, this).irBlockBody {
|
||||
val cache = irTemporary(irGetField(irGet(self), getCacheField()))
|
||||
// cache[resourceId] ?: findViewById(resourceId)?.also { cache[resourceId] = it }
|
||||
+irReturn(irElvis(viewType, irCallOp(mapGet.symbol, viewType, irGet(cache), irGet(resourceId))) {
|
||||
irSafeLet(viewType, irFindViewById(irGet(self), irGet(resourceId), containerType)) { foundView ->
|
||||
irBlock {
|
||||
+irCall(mapSet.symbol).apply {
|
||||
dispatchReceiver = irGet(cache)
|
||||
putValueArgument(0, irGet(resourceId))
|
||||
putValueArgument(1, irGet(foundView))
|
||||
}
|
||||
+irGet(foundView)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClassNew(declaration: IrClass): IrStatement {
|
||||
if (!declaration.isClass && !declaration.isObject)
|
||||
return super.visitClassNew(declaration)
|
||||
@@ -120,39 +200,42 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
|
||||
if (containerOptions.containerType == AndroidContainerType.LAYOUT_CONTAINER && !extension.isExperimental(declaration))
|
||||
return super.visitClassNew(declaration)
|
||||
|
||||
// TODO 1. actually generate the cache;
|
||||
// 2. clear it in the function added below;
|
||||
// 3. if containerOptions.containerType.isFragment and there's no onDestroy, add one that clears the cache
|
||||
declaration.addFunction {
|
||||
name = Name.identifier(AbstractAndroidExtensionsExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME)
|
||||
modality = Modality.OPEN
|
||||
returnType = pluginContext.irBuiltIns.unitType
|
||||
}.apply {
|
||||
addDispatchReceiver { type = declaration.defaultType }
|
||||
body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET)
|
||||
if ((containerOptions.cache ?: extension.getGlobalCacheImpl(declaration)).hasCache) {
|
||||
declaration.declarations += declaration.getCacheField()
|
||||
declaration.declarations += declaration.getClearCacheFun()
|
||||
declaration.declarations += declaration.getCachedFindViewByIdFun()
|
||||
}
|
||||
return super.visitClassNew(declaration)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val receiverClass = expression.extensionReceiver?.type?.classOrNull
|
||||
?: return super.visitFunctionAccess(expression)
|
||||
val receiver = expression.extensionReceiver!!.transform(this, null)
|
||||
|
||||
val containerOptions = ContainerOptionsProxy.create(receiverClass.descriptor)
|
||||
val containerHasCache = (containerOptions.cache ?: extension.getGlobalCacheImpl(receiverClass.owner)).hasCache
|
||||
|
||||
if (expression.symbol.descriptor is AndroidSyntheticFunction) {
|
||||
// TODO actually call the appropriate CLEAR_CACHE_METHOD_NAME-named function
|
||||
return IrBlockImpl(expression.startOffset, expression.endOffset, pluginContext.irBuiltIns.unitType)
|
||||
if (expression.symbol.owner.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME)
|
||||
return super.visitFunctionAccess(expression)
|
||||
if (!containerHasCache)
|
||||
return IrBlockImpl(expression.startOffset, expression.endOffset, expression.type)
|
||||
return receiverClass.owner.getClearCacheFun().callWithRanges(expression).apply {
|
||||
dispatchReceiver = receiver
|
||||
}
|
||||
}
|
||||
|
||||
val resource = (expression.symbol.descriptor as? PropertyGetterDescriptor)?.correspondingProperty as? AndroidSyntheticProperty
|
||||
?: return super.visitFunctionAccess(expression)
|
||||
val packageFragment = (resource as PropertyDescriptor).containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor
|
||||
?: return super.visitFunctionAccess(expression)
|
||||
val receiverClass = expression.extensionReceiver?.type?.classOrNull
|
||||
?: return super.visitFunctionAccess(expression)
|
||||
val receiver = expression.extensionReceiver!!.transform(this, null)
|
||||
|
||||
val packageFqName = FqName(packageFragment.packageData.moduleData.module.applicationPackage)
|
||||
val field = createField(packageFqName.child("R\$id").child(resource.name), pluginContext.irBuiltIns.intType)
|
||||
val resourceId = IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type)
|
||||
|
||||
val containerType = ContainerOptionsProxy.create(receiverClass.descriptor).containerType
|
||||
val containerType = containerOptions.containerType
|
||||
val result = if (expression.type.classifierOrNull?.isFragment == true) {
|
||||
// this.get[Support]FragmentManager().findFragmentById(R$id.<name>)
|
||||
val appPackageFqName = when (containerType) {
|
||||
@@ -180,47 +263,65 @@ private class AndroidIrTransformer(val extension: AndroidIrExtension, val plugin
|
||||
}
|
||||
putValueArgument(0, resourceId)
|
||||
}
|
||||
} else {
|
||||
// this[.getView()?|.getContainerView()?].findViewById(R$id.<name>)
|
||||
val viewClass = createClass(FqName(AndroidConst.VIEW_FQNAME))
|
||||
val getView = when (containerType) {
|
||||
AndroidContainerType.ACTIVITY, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG ->
|
||||
null
|
||||
AndroidContainerType.FRAGMENT, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, AndroidContainerType.SUPPORT_FRAGMENT ->
|
||||
createMethod(containerType.fqName.child("getView"), viewClass.defaultType.makeNullable())
|
||||
AndroidContainerType.LAYOUT_CONTAINER ->
|
||||
createMethod(containerType.fqName.child("getContainerView"), viewClass.defaultType.makeNullable(), true)
|
||||
else -> throw IllegalStateException("Invalid Android class type: $containerType") // Should never occur
|
||||
}
|
||||
val findViewByIdParent = if (getView == null) containerType.fqName else FqName(AndroidConst.VIEW_FQNAME)
|
||||
val findViewById = createMethod(findViewByIdParent.child("findViewById"), viewClass.defaultType.makeNullable()) {
|
||||
addValueParameter("id", pluginContext.irBuiltIns.intType)
|
||||
}.callWithRanges(expression).apply {
|
||||
} else if (containerHasCache) {
|
||||
// this._$_findCachedViewById(R$id.<name>)
|
||||
receiverClass.owner.getCachedFindViewByIdFun().callWithRanges(expression).apply {
|
||||
dispatchReceiver = receiver
|
||||
putValueArgument(0, resourceId)
|
||||
}
|
||||
if (getView == null) {
|
||||
findViewById.apply { dispatchReceiver = receiver }
|
||||
} else {
|
||||
val scope = currentScope!!.scope.scopeOwnerSymbol
|
||||
DeclarationIrBuilder(IrGeneratorContextBase(pluginContext.irBuiltIns), scope).irBlock(expression) {
|
||||
val variable = irTemporary(getView.callWithRanges(expression).apply {
|
||||
dispatchReceiver = receiver
|
||||
})
|
||||
+irIfNull(findViewById.type, irGet(variable), irNull(), findViewById.apply {
|
||||
dispatchReceiver = irGet(variable)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
irBuilder(currentScope!!.scope.scopeOwnerSymbol, expression).irFindViewById(receiver, resourceId, containerType)
|
||||
}
|
||||
return with(expression) { IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.CAST, type, result) }
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.irFindViewById(
|
||||
receiver: IrExpression, id: IrExpression, container: AndroidContainerType
|
||||
): IrExpression {
|
||||
// this[.getView()?|.getContainerView()?].findViewById(R$id.<name>)
|
||||
val viewClass = createClass(FqName(AndroidConst.VIEW_FQNAME))
|
||||
val getView = when (container) {
|
||||
AndroidContainerType.FRAGMENT,
|
||||
AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT,
|
||||
AndroidContainerType.SUPPORT_FRAGMENT ->
|
||||
createMethod(container.fqName.child("getView"), viewClass.defaultType.makeNullable())
|
||||
AndroidContainerType.LAYOUT_CONTAINER ->
|
||||
createMethod(container.fqName.child("getContainerView"), viewClass.defaultType.makeNullable(), true)
|
||||
else -> null
|
||||
}
|
||||
val findViewByIdParent = if (getView == null) container.fqName else FqName(AndroidConst.VIEW_FQNAME)
|
||||
val findViewById = createMethod(findViewByIdParent.child("findViewById"), viewClass.defaultType.makeNullable()) {
|
||||
addValueParameter("id", pluginContext.irBuiltIns.intType)
|
||||
}
|
||||
val findViewCall = irCall(findViewById).apply { putValueArgument(0, id) }
|
||||
return if (getView == null) {
|
||||
findViewCall.apply { dispatchReceiver = receiver }
|
||||
} else {
|
||||
irSafeLet(findViewCall.type, irCall(getView).apply { dispatchReceiver = receiver }) { parent ->
|
||||
findViewCall.apply { dispatchReceiver = irGet(parent) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FqName.child(name: String) = child(Name.identifier(name))
|
||||
|
||||
@ObsoleteDescriptorBasedAPI
|
||||
private fun IrSimpleFunction.callWithRanges(source: IrExpression) =
|
||||
IrCallImpl.fromSymbolDescriptor(source.startOffset, source.endOffset, returnType, symbol)
|
||||
IrCallImpl.fromSymbolOwner(source.startOffset, source.endOffset, returnType, symbol)
|
||||
|
||||
private inline fun IrBuilderWithScope.irSafeCall(
|
||||
type: IrType, lhs: IrExpression,
|
||||
ifNull: IrBuilderWithScope.() -> IrExpression,
|
||||
ifNotNull: IrBuilderWithScope.(IrVariable) -> IrExpression
|
||||
) = irBlock(origin = IrStatementOrigin.SAFE_CALL) {
|
||||
+irTemporary(lhs).let { irIfNull(type, irGet(it), ifNull(), ifNotNull(it)) }
|
||||
}
|
||||
|
||||
private inline fun IrBuilderWithScope.irSafeLet(type: IrType, lhs: IrExpression, rhs: IrBuilderWithScope.(IrVariable) -> IrExpression) =
|
||||
irSafeCall(type, lhs, { irNull() }, rhs)
|
||||
|
||||
private inline fun IrBuilderWithScope.irElvis(type: IrType, lhs: IrExpression, rhs: IrBuilderWithScope.() -> IrExpression) =
|
||||
irSafeCall(type, lhs, rhs) { irGet(it) }
|
||||
|
||||
private val AndroidContainerType.fqName: FqName
|
||||
get() = FqName(internalClassName.replace("/", "."))
|
||||
|
||||
@@ -21,65 +21,48 @@ import kotlinx.android.extensions.CacheImplementation
|
||||
import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.CLEAR_CACHE_METHOD_NAME
|
||||
import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.ON_DESTROY_METHOD_NAME
|
||||
import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy
|
||||
import org.jetbrains.kotlin.codegen.AbstractClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
|
||||
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.RecordComponentVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
|
||||
abstract class AbstractAndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension {
|
||||
override fun interceptClassBuilderFactory(
|
||||
interceptedFactory: ClassBuilderFactory,
|
||||
bindingContext: BindingContext,
|
||||
diagnostics: DiagnosticSink
|
||||
): ClassBuilderFactory {
|
||||
return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext)
|
||||
}
|
||||
): ClassBuilderFactory = AndroidOnDestroyClassBuilderFactory(interceptedFactory)
|
||||
|
||||
abstract fun getGlobalCacheImpl(element: KtElement): CacheImplementation
|
||||
|
||||
private inner class AndroidOnDestroyClassBuilderFactory(
|
||||
private val delegateFactory: ClassBuilderFactory,
|
||||
val bindingContext: BindingContext
|
||||
) : ClassBuilderFactory {
|
||||
private inner class AndroidOnDestroyClassBuilderFactory(delegate: ClassBuilderFactory) : DelegatingClassBuilderFactory(delegate) {
|
||||
override fun newClassBuilder(origin: JvmDeclarationOrigin): DelegatingClassBuilder =
|
||||
AndroidOnDestroyCollectorClassBuilder(delegate.newClassBuilder(origin), origin.hasCache)
|
||||
|
||||
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 val JvmDeclarationOrigin.hasCache: Boolean
|
||||
get() = descriptor is ClassDescriptor && element is KtElement &&
|
||||
ContainerOptionsProxy.create(descriptor as ClassDescriptor).let {
|
||||
it.containerType.isFragment && (it.cache ?: getGlobalCacheImpl(element as KtElement)).hasCache
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AndroidOnDestroyCollectorClassBuilder(
|
||||
internal val delegateClassBuilder: ClassBuilder,
|
||||
val bindingContext: BindingContext
|
||||
private class AndroidOnDestroyCollectorClassBuilder(
|
||||
private val delegate: ClassBuilder,
|
||||
private val hasCache: Boolean
|
||||
) : DelegatingClassBuilder() {
|
||||
private var currentClass: KtClass? = null
|
||||
private var currentClassName: String? = null
|
||||
private lateinit var currentClassName: String
|
||||
private lateinit var superClassName: String
|
||||
private var hasOnDestroy = false
|
||||
|
||||
override fun getDelegate() = delegateClassBuilder
|
||||
override fun getDelegate() = delegate
|
||||
|
||||
override fun defineClass(
|
||||
origin: PsiElement?,
|
||||
@@ -90,13 +73,27 @@ abstract class AbstractAndroidOnDestroyClassBuilderInterceptorExtension : ClassB
|
||||
superName: String,
|
||||
interfaces: Array<out String>
|
||||
) {
|
||||
if (origin is KtClass) {
|
||||
currentClass = origin
|
||||
currentClassName = name
|
||||
}
|
||||
currentClassName = name
|
||||
superClassName = superName
|
||||
super.defineClass(origin, version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun done() {
|
||||
if (hasCache && !hasOnDestroy) {
|
||||
val mv = newMethod(
|
||||
JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_PUBLIC or Opcodes.ACC_SYNTHETIC, ON_DESTROY_METHOD_NAME, "()V",
|
||||
null, null
|
||||
)
|
||||
mv.visitCode()
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0)
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName, ON_DESTROY_METHOD_NAME, "()V", false)
|
||||
mv.visitInsn(Opcodes.RETURN)
|
||||
mv.visitMaxs(1, 1)
|
||||
mv.visitEnd()
|
||||
}
|
||||
super.done()
|
||||
}
|
||||
|
||||
override fun newMethod(
|
||||
origin: JvmDeclarationOrigin,
|
||||
access: Int,
|
||||
@@ -105,37 +102,18 @@ abstract class AbstractAndroidOnDestroyClassBuilderInterceptorExtension : ClassB
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(Opcodes.API_VERSION, super.newMethod(origin, access, name, desc, signature, exceptions)) {
|
||||
val mv = super.newMethod(origin, access, name, desc, signature, exceptions)
|
||||
if (!hasCache || name != ON_DESTROY_METHOD_NAME || desc != "()V") return mv
|
||||
hasOnDestroy = true
|
||||
return object : MethodVisitor(Opcodes.API_VERSION, mv) {
|
||||
override fun visitInsn(opcode: Int) {
|
||||
if (opcode == Opcodes.RETURN) {
|
||||
generateClearCacheMethodCall()
|
||||
visitVarInsn(Opcodes.ALOAD, 0)
|
||||
visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
|
||||
}
|
||||
|
||||
super.visitInsn(opcode)
|
||||
}
|
||||
|
||||
private fun generateClearCacheMethodCall() {
|
||||
val currentClass = currentClass
|
||||
if (name != ON_DESTROY_METHOD_NAME || currentClass == null) return
|
||||
if (Type.getArgumentTypes(desc).isNotEmpty()) return
|
||||
if (Type.getReturnType(desc) != Type.VOID_TYPE) return
|
||||
|
||||
val containerType = currentClassName?.let { Type.getObjectType(it) } ?: return
|
||||
|
||||
val container = bindingContext.get(BindingContext.CLASS, currentClass) ?: return
|
||||
val entityOptions = ContainerOptionsProxy.create(container)
|
||||
if (!entityOptions.containerType.isFragment || !(entityOptions.cache ?: getGlobalCacheImpl(currentClass)).hasCache) return
|
||||
|
||||
val iv = InstructionAdapter(this)
|
||||
iv.load(0, containerType)
|
||||
iv.invokevirtual(currentClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newRecordComponent(name: String, desc: String, signature: String?): RecordComponentVisitor {
|
||||
return AbstractClassBuilder.EMPTY_RECORD_VISITOR
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user