Clear cache on fragment onDestroy()

This commit is contained in:
Yan Zhulanow
2015-09-08 19:27:59 +03:00
parent 5dc9557650
commit fbc769b0e9
10 changed files with 272 additions and 67 deletions
@@ -21,11 +21,13 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
@@ -93,6 +95,7 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension())
StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor())
ClassBuilderInterceptorExtension.registerExtension(project, AndroidOnDestroyClassBuilderInterceptorExtension())
}
}
}
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
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.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
@@ -39,25 +40,57 @@ import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
private enum class AndroidClassType(val internalClassName: String, val supportsCache: Boolean = false) {
ACTIVITY(AndroidConst.ACTIVITY_FQNAME.innerName, true),
FRAGMENT(AndroidConst.FRAGMENT_FQNAME.innerName, true),
SUPPORT_FRAGMENT_ACTIVITY(AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME.innerName, true),
SUPPORT_FRAGMENT(AndroidConst.SUPPORT_FRAGMENT_FQNAME.innerName, true),
VIEW(AndroidConst.VIEW_FQNAME.innerName),
UNKNOWN("")
}
public enum class AndroidClassType(className: String, val supportsCache: Boolean = false, val fragment: Boolean = false) {
ACTIVITY(AndroidConst.ACTIVITY_FQNAME, supportsCache = true),
FRAGMENT(AndroidConst.FRAGMENT_FQNAME, supportsCache = true, fragment = true),
SUPPORT_FRAGMENT_ACTIVITY(AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME, supportsCache = true),
SUPPORT_FRAGMENT(AndroidConst.SUPPORT_FRAGMENT_FQNAME, supportsCache = true, fragment = true),
VIEW(AndroidConst.VIEW_FQNAME),
UNKNOWN("");
private val String.innerName: String
get() = replace('.', '/')
public val internalClassName: String = className.replace('.', '/')
public companion object {
public fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY
AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT
AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW
else -> null
}
if (descriptor is LazyJavaClassDescriptor) {
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).asString())
if (androidClassType != null) return androidClassType
}
else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity)
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString())
if (androidClassType != null) return androidClassType
}
for (supertype in descriptor.typeConstructor.supertypes) {
val declarationDescriptor = supertype.constructor.declarationDescriptor
if (declarationDescriptor != null) {
val androidClassType = getClassType(declarationDescriptor)
if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType
}
}
return AndroidClassType.UNKNOWN
}
}
}
public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
companion object {
private val PROPERTY_NAME = "_\$_findViewCache"
private val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById"
private val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val ON_DESTROY_METHOD_NAME = "onDestroy"
fun isCacheSupported(descriptor: ClassifierDescriptor) = descriptor.getSource() is KotlinSourceElement
fun isCacheSupported(descriptor: ClassifierDescriptor) = descriptor.source is KotlinSourceElement
}
private class SyntheticPartsGenerateContext(
@@ -65,12 +98,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val state: GenerationState,
val descriptor: ClassDescriptor,
val classOrObject: JetClassOrObject,
val androidClassType: AndroidClassType) {
}
val androidClassType: AndroidClassType)
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.getResultingDescriptor()
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is PropertyDescriptor) {
return generateSyntheticPropertyCall(receiver, resolvedCall, c, resultingDescriptor)
}
@@ -78,7 +109,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
}
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): Boolean {
val resultingDescriptor = resolvedCall.getResultingDescriptor()
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is FunctionDescriptor) {
return generateSyntheticFunctionCall(receiver, resolvedCall, c, resultingDescriptor)
}
@@ -92,15 +123,15 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
descriptor: FunctionDescriptor
): Boolean {
if (descriptor.getAndroidPackage() == null) return false
if (descriptor.getName().asString() != AndroidConst.CLEAR_FUNCTION_NAME) return false
if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return false
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return false
if (!isCacheSupported(declarationDescriptor)) return true
val androidClassType = getClassType(declarationDescriptor)
val androidClassType = AndroidClassType.getClassType(declarationDescriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return false
val bytecodeClassName = DescriptorUtils.getFqName(declarationDescriptor).asString().replace('.', '/')
val bytecodeClassName = c.typeMapper.mapType(declarationDescriptor).internalName
receiver.put(c.typeMapper.mapType(declarationDescriptor), c.v)
c.v.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
@@ -116,8 +147,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
): StackValue? {
val androidPackage = descriptor.getAndroidPackage() ?: return null
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null
val androidClassType = getClassType(declarationDescriptor)
val androidClassType = AndroidClassType.getClassType(declarationDescriptor)
return SyntheticProperty(receiver, c.typeMapper, descriptor, declarationDescriptor, androidClassType, androidPackage)
}
@@ -129,10 +159,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val declarationDescriptor: ClassifierDescriptor,
val androidClassType: AndroidClassType,
val androidPackage: String
) : StackValue(typeMapper.mapType(descriptor.getReturnType()!!)) {
) : StackValue(typeMapper.mapType(descriptor.returnType!!)) {
override fun putSelector(type: Type, v: InstructionAdapter) {
val returnTypeString = typeMapper.mapType(descriptor.getType().lowerIfFlexible()).getClassName()
val returnTypeString = typeMapper.mapType(descriptor.type.lowerIfFlexible()).className
if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) {
return putSelectorForFragment(v)
}
@@ -140,8 +170,8 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(declarationDescriptor)
receiver.put(declarationDescriptorType, v)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I")
v.invokevirtual(declarationDescriptorType.getInternalName(), CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I")
v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
}
else {
when (androidClassType) {
@@ -189,7 +219,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
}
fun getResourceId(v: InstructionAdapter) {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I")
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I")
}
}
@@ -198,37 +228,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
}
private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? {
return getExtensionReceiver().getType().getConstructor().getDeclarationDescriptor()
}
private fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY
AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT
AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW
else -> null
}
if (descriptor is LazyJavaClassDescriptor) {
val androidClassType = getClassTypeInternal(descriptor.fqName.asString())
if (androidClassType != null) return androidClassType
}
else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity)
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString())
if (androidClassType != null) return androidClassType
}
for (supertype in descriptor.getTypeConstructor().getSupertypes()) {
val declarationDescriptor = supertype.getConstructor().getDeclarationDescriptor()
if (declarationDescriptor != null) {
val androidClassType = getClassType(declarationDescriptor)
if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType
}
}
return AndroidClassType.UNKNOWN
return extensionReceiver.type.constructor.declarationDescriptor
}
override fun generateClassSyntheticParts(
@@ -237,27 +237,58 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
classOrObject: JetClassOrObject,
descriptor: ClassDescriptor
) {
if (descriptor.getKind() != ClassKind.CLASS || descriptor.isInner() || DescriptorUtils.isLocal(descriptor)) return
if (descriptor.kind != ClassKind.CLASS || descriptor.isInner || DescriptorUtils.isLocal(descriptor)) return
// Do not generate anything if class is not supported
val androidClassType = getClassType(descriptor)
val androidClassType = AndroidClassType.getClassType(descriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return
val context = SyntheticPartsGenerateContext(classBuilder, state, descriptor, classOrObject, androidClassType)
context.generateCachedFindViewByIdFunction()
context.generateClearCacheFunction()
if (androidClassType.fragment) {
val classMembers = descriptor.unsubstitutedMemberScope.getAllDescriptors()
val onDestroy = classMembers.firstOrNull { it is FunctionDescriptor && it.isOnDestroyFunction() }
if (onDestroy == null) {
context.generateOnDestroyFunctionForFragment()
}
}
classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null)
}
private fun SyntheticPartsGenerateContext.generateClearCacheFunction() {
val methodVisitor = classBuilder.newMethod(
JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CLEAR_CACHE_METHOD_NAME, "()V", null, null)
private fun FunctionDescriptor.isOnDestroyFunction(): Boolean {
return kind == CallableMemberDescriptor.Kind.DECLARATION
&& name.asString() == ON_DESTROY_METHOD_NAME
&& (visibility == Visibilities.INHERITED || visibility == Visibilities.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, ON_DESTROY_METHOD_NAME, "()V", null, null)
methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor)
val classType = state.typeMapper.mapClass(descriptor)
val className = classType.getInternalName()
iv.load(0, classType)
iv.invokespecial(state.typeMapper.mapClass(descriptor.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() {
val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CLEAR_CACHE_METHOD_NAME, "()V", null, null)
methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor)
val classType = state.typeMapper.mapClass(descriptor)
val className = classType.internalName
fun loadCache() {
iv.load(0, classType)
@@ -278,7 +309,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() {
val classType = state.typeMapper.mapClass(descriptor)
val className = classType.getInternalName()
val className = classType.internalName
val viewType = Type.getObjectType("android/view/View")
@@ -0,0 +1,118 @@
/*
* 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.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.descriptors.ClassDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.JetClass
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.commons.InstructionAdapter
public class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension {
override fun interceptClassBuilderFactory(
interceptedFactory: ClassBuilderFactory,
bindingContext: BindingContext,
diagnostics: DiagnosticSink
): ClassBuilderFactory {
return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext)
}
private inner class AndroidOnDestroyClassBuilderFactory(private val delegateFactory: ClassBuilderFactory, val bindingContext: BindingContext) : ClassBuilderFactory {
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
return AndroidOnDestroyCollectorClassBuilder(delegateFactory.newClassBuilder(origin), bindingContext)
}
override fun getClassBuilderMode() = delegateFactory.getClassBuilderMode()
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: JetClass? = null
private var currentClassName: String? = null
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 JetClass) {
currentClass = origin
currentClassName = name
}
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
return object : MethodVisitor(Opcodes.ASM5, super.newMethod(origin, access, name, desc, signature, exceptions)) {
override fun visitCode() {
super.visitCode()
if (name != AndroidExpressionCodegenExtension.ON_DESTROY_METHOD_NAME || currentClass == null) return
if (Type.getArgumentTypes(desc).size() != 0) return
if (Type.getReturnType(desc) != Type.VOID_TYPE) return
val classType = currentClassName?.let { Type.getObjectType(it) } ?: return
val descriptor = bindingContext.get(BindingContext.CLASS, currentClass) as? ClassDescriptor ?: return
val androidClassType = AndroidClassType.getClassType(descriptor)
if (!androidClassType.fragment) return
val iv = InstructionAdapter(this)
iv.load(0, classType)
iv.invokevirtual(currentClassName, AndroidExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME, "()V", false)
}
}
}
}
}