Minor: Android Extensions compiler plugin refactoring

This commit is contained in:
Yan Zhulanow
2017-06-01 21:32:01 +03:00
parent 0494862ce5
commit c9cd1a4d75
14 changed files with 325 additions and 281 deletions
@@ -22,7 +22,6 @@ import org.xml.sax.helpers.DefaultHandler
import java.util.HashMap
class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String) -> Unit) : DefaultHandler() {
override fun startDocument() {
super.startDocument()
}
@@ -40,13 +39,10 @@ class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String
if (name != null) elementCallback(name, widgetType)
}
override fun endElement(uri: String?, localName: String, qName: String) {
}
override fun endElement(uri: String?, localName: String, qName: String) {}
}
fun Attributes.toMap(): HashMap<String, String> {
private fun Attributes.toMap(): HashMap<String, String> {
val res = HashMap<String, String>()
for (index in 0..length - 1) {
val attrName = getLocalName(index)!!
@@ -0,0 +1,68 @@
/*
* 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.synthetic.codegen
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
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),
DIALOG(AndroidConst.DIALOG_FQNAME, supportsCache = false),
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("");
val internalClassName: String = className.replace('.', '/')
companion object {
fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.DIALOG_FQNAME -> AndroidClassType.DIALOG
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
}
}
}
@@ -26,18 +26,14 @@ import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
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.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.lowerIfFlexible
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC
@@ -45,64 +41,23 @@ import org.jetbrains.org.objectweb.asm.Opcodes.ACC_SYNTHETIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
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),
DIALOG(AndroidConst.DIALOG_FQNAME, supportsCache = false),
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("");
val internalClassName: String = className.replace('.', '/')
companion object {
fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.DIALOG_FQNAME -> AndroidClassType.DIALOG
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
}
}
}
class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
companion object {
private val PROPERTY_NAME = "_\$_findViewCache"
private val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById"
val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById"
val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val ON_DESTROY_METHOD_NAME = "onDestroyView"
fun isCacheSupported(receiverDescriptor: ClassDescriptor, descriptor: PropertyDescriptor? = null): Boolean {
val receiverIsKotlinClass = receiverDescriptor.source is KotlinSourceElement
return receiverIsKotlinClass && when (descriptor) {
is AndroidSyntheticProperty -> descriptor.cacheView
else -> true
fun doesContainerSupportCache(container: ClassDescriptor): Boolean {
return container.source is KotlinSourceElement
}
fun shouldCacheResource(container: ClassDescriptor, resource: PropertyDescriptor): Boolean {
if (!doesContainerSupportCache(container)) {
return false
}
return (resource as? AndroidSyntheticProperty)?.shouldBeCached ?: false
}
}
@@ -116,43 +71,45 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is PropertyDescriptor) {
return generateSyntheticPropertyCall(receiver, resolvedCall, c, resultingDescriptor)
return generateResourcePropertyCall(receiver, resolvedCall, c, resultingDescriptor)
}
else null
}
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is FunctionDescriptor) {
return generateSyntheticFunctionCall(receiver, resolvedCall, c, resultingDescriptor)
val targetCallable = resolvedCall.resultingDescriptor
resolvedCall.resultingDescriptor as? AndroidSyntheticFunction ?: return null
return if (targetCallable.name.asString() == AndroidConst.CLEAR_FUNCTION_NAME) {
val container = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
generateClearFindViewByIdCacheFunctionCall(receiver, container, c)
}
else {
null
}
else null
}
private fun generateSyntheticFunctionCall(
private fun generateClearFindViewByIdCacheFunctionCall(
receiver: StackValue,
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
functionDescriptor: FunctionDescriptor
container: ClassDescriptor,
c: ExpressionCodegenExtension.Context
): StackValue? {
if (functionDescriptor !is AndroidSyntheticFunction) return null
if (functionDescriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
if (!doesContainerSupportCache(container)) {
return StackValue.functionCall(Type.VOID_TYPE) {}
}
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
if (!isCacheSupported(receiverDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
val androidClassType = AndroidClassType.getClassType(container)
if (androidClassType == AndroidClassType.UNKNOWN) return null
return StackValue.functionCall(Type.VOID_TYPE) {
val bytecodeClassName = c.typeMapper.mapType(receiverDescriptor).internalName
val bytecodeClassName = c.typeMapper.mapType(container).internalName
receiver.put(c.typeMapper.mapType(receiverDescriptor), it)
receiver.put(c.typeMapper.mapType(container), it)
it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
}
}
private fun generateSyntheticPropertyCall(
private fun generateResourcePropertyCall(
receiver: StackValue,
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
@@ -164,84 +121,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
return SyntheticProperty(receiver, c.typeMapper, descriptor, receiverDescriptor, androidClassType, androidPackage)
}
private class SyntheticProperty(
val receiver: StackValue,
val typeMapper: KotlinTypeMapper,
val propertyDescriptor: PropertyDescriptor,
val receiverDescriptor: ClassDescriptor,
val androidClassType: AndroidClassType,
val androidPackage: String
) : StackValue(typeMapper.mapType(propertyDescriptor.returnType!!)) {
override fun putSelector(type: Type, v: InstructionAdapter) {
val returnTypeString = typeMapper.mapType(propertyDescriptor.type.lowerIfFlexible()).className
if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) {
return putSelectorForFragment(v)
}
val syntheticProperty = propertyDescriptor as AndroidSyntheticProperty
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
receiver.put(declarationDescriptorType, v)
val resourceId = syntheticProperty.resource.id
val packageName = resourceId.packageName ?: androidPackage
v.getstatic(packageName.replace(".", "/") + "/R\$id", resourceId.name, "I")
v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
}
else {
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW, AndroidClassType.DIALOG -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
getResourceId(v)
v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false)
getResourceId(v)
v.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
}
v.checkcast(this.type)
}
private fun putSelectorForFragment(v: InstructionAdapter) {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/app/FragmentManager", "findFragmentById", "(I)Landroid/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY -> {
v.invokevirtual(androidClassType.internalClassName, "getSupportFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
v.checkcast(this.type)
}
fun getResourceId(v: InstructionAdapter) {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
}
return ResourcePropertyStackValue(receiver, c.typeMapper, descriptor, receiverDescriptor, androidClassType, androidPackage)
}
private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? {
@@ -250,18 +130,16 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
val classBuilder = codegen.v
val state = codegen.state
val classOrObject = codegen.myClass
val descriptor = codegen.descriptor
val targetClass = codegen.myClass as? KtClassOrObject ?: return
val descriptor = codegen.descriptor
if (descriptor.kind != ClassKind.CLASS || descriptor.isInner || DescriptorUtils.isLocal(descriptor)) return
if (classOrObject !is KtClassOrObject) return // works only only on physical classes, not synthetic ones
// Do not generate anything if class is not supported
val androidClassType = AndroidClassType.getClassType(descriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return
val context = SyntheticPartsGenerateContext(classBuilder, state, descriptor, classOrObject, androidClassType)
val context = SyntheticPartsGenerateContext(classBuilder, codegen.state, descriptor, targetClass, androidClassType)
context.generateCachedFindViewByIdFunction()
context.generateClearCacheFunction()
@@ -21,7 +21,6 @@ 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.KtClass
import org.jetbrains.kotlin.resolve.BindingContext
@@ -107,12 +106,12 @@ class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptor
private fun generateClearCacheMethodCall() {
if (name != AndroidExpressionCodegenExtension.ON_DESTROY_METHOD_NAME || currentClass == null) return
if (Type.getArgumentTypes(desc).size != 0) return
if (Type.getArgumentTypes(desc).isNotEmpty()) 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 descriptor = bindingContext.get(BindingContext.CLASS, currentClass) ?: return
val androidClassType = AndroidClassType.getClassType(descriptor)
if (!androidClassType.fragment) return
@@ -0,0 +1,104 @@
/*
* 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.synthetic.codegen
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.types.lowerIfFlexible
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class ResourcePropertyStackValue(
val receiver: StackValue,
val typeMapper: KotlinTypeMapper,
val propertyDescriptor: PropertyDescriptor,
val receiverDescriptor: ClassDescriptor,
val androidClassType: AndroidClassType,
val androidPackage: String
) : StackValue(typeMapper.mapType(propertyDescriptor.returnType!!)) {
override fun putSelector(type: Type, v: InstructionAdapter) {
val returnTypeString = typeMapper.mapType(propertyDescriptor.type.lowerIfFlexible()).className
if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) {
return putSelectorForFragment(v)
}
val syntheticProperty = propertyDescriptor as AndroidSyntheticProperty
if (androidClassType.supportsCache && AndroidExpressionCodegenExtension.shouldCacheResource(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
receiver.put(declarationDescriptorType, v)
val resourceId = syntheticProperty.resource.id
val packageName = resourceId.packageName ?: androidPackage
v.getstatic(packageName.replace(".", "/") + "/R\$id", resourceId.name, "I")
v.invokevirtual(declarationDescriptorType.internalName, AndroidExpressionCodegenExtension.CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
}
else {
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW, AndroidClassType.DIALOG -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
getResourceId(v)
v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false)
getResourceId(v)
v.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
}
v.checkcast(this.type)
}
private fun putSelectorForFragment(v: InstructionAdapter) {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/app/FragmentManager", "findFragmentById", "(I)Landroid/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY -> {
v.invokevirtual(androidClassType.internalClassName, "getSupportFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
v.checkcast(this.type)
}
fun getResourceId(v: InstructionAdapter) {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
}
}
@@ -32,7 +32,6 @@ import org.jetbrains.kotlin.utils.Printer
import java.util.*
class AndroidSyntheticPackageData(
val layoutName: String,
val moduleData: AndroidModuleData,
val forView: Boolean,
val isDeprecated: Boolean,
@@ -41,18 +41,20 @@ class PredefinedPackageFragmentDescriptor(
}
private val scope = PredefinedScope()
override fun getMemberScope() = scope
inner class PredefinedScope : MemberScopeImpl() {
@Suppress("UNCHECKED_CAST")
override fun getContributedVariables(name: Name, location: LookupLocation) = emptyList<PropertyDescriptor>()
@Suppress("UNCHECKED_CAST")
override fun getContributedFunctions(name: Name, location: LookupLocation) =
calculatedFunctions().filter { it.name == name }
override fun getContributedFunctions(name: Name, location: LookupLocation) = calculatedFunctions().filter { it.name == name }
override fun getContributedDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) =
calculatedFunctions().filter { nameFilter(it.name) && kindFilter.accepts(it) }
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): List<SimpleFunctionDescriptor> {
return calculatedFunctions().filter { nameFilter(it.name) && kindFilter.accepts(it) }
}
override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName)
@@ -20,6 +20,7 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid.*
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.android.synthetic.res.isErrorType
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
@@ -29,15 +29,17 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import java.util.*
class AndroidVariantData(val variant: AndroidVariant, private val layouts: Map<String, List<PsiFile>>): Map<String, List<PsiFile>> by layouts
class AndroidModuleData(val module: AndroidModule, private val variants: List<AndroidVariantData>): Iterable<AndroidVariantData> by variants {
class AndroidVariantData(val variant: AndroidVariant, val layouts: Map<String, List<PsiFile>>)
class AndroidModuleData(val module: AndroidModule, val variants: List<AndroidVariantData>) {
companion object {
val EMPTY = AndroidModuleData(AndroidModule("android", listOf()), listOf())
}
}
abstract class AndroidLayoutXmlFileManager(val project: Project) {
data class AndroidLayoutGroupData(val name: String, val layouts: List<PsiFile>)
abstract class AndroidLayoutXmlFileManager(val project: Project) {
abstract val androidModule: AndroidModule?
open fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> = listOf()
@@ -47,7 +49,7 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return AndroidModuleData(androidModule, androidModule.variants.map { getVariantData(it) })
}
fun getVariantData(variant: AndroidVariant): AndroidVariantData {
private fun getVariantData(variant: AndroidVariant): AndroidVariantData {
val psiManager = PsiManager.getInstance(project)
val fileManager = VirtualFileManager.getInstance()
@@ -84,11 +86,11 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return AndroidVariantData(variant, layoutNameToXmlFiles)
}
fun extractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource> {
return filterDuplicates(doExtractResources(files, module))
fun extractResources(layoutGroupFiles: AndroidLayoutGroupData, module: ModuleDescriptor): List<AndroidResource> {
return filterDuplicates(doExtractResources(layoutGroupFiles, module))
}
protected abstract fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidLayoutGroup>
protected abstract fun doExtractResources(layoutGroup: AndroidLayoutGroupData, module: ModuleDescriptor): AndroidLayoutGroup
protected fun parseAndroidResource(id: ResourceIdentifier, tag: String, sourceElement: PsiElement?): AndroidResource {
return when (tag) {
@@ -98,41 +100,39 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
}
}
private fun filterDuplicates(layoutGroups: List<AndroidLayoutGroup>): List<AndroidResource> {
private fun filterDuplicates(layoutGroup: AndroidLayoutGroup): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>()
for (layoutGroup in layoutGroups) {
val resources = layoutGroup.layouts.flatMap { it.resources }.groupBy {
val id = it.id
if (id.packageName == null) id.name else id.packageName + "/" + id.name
}
val resourcesByName = layoutGroup.layouts.flatMap { it.resources }.groupBy {
val id = it.id
if (id.packageName == null) id.name else id.packageName + "/" + id.name
}
for (resources in resources.values) {
val isPartiallyDefined = resources.size < layoutGroup.layouts.size
for (resources in resourcesByName.values) {
val isPartiallyDefined = resources.size < layoutGroup.layouts.size
for (res in resources) {
if (resourceMap.contains(res.id.name)) {
val existing = resourceMap[res.id.name]!!
for (res in resources) {
if (res.id.name in resourceMap) {
val existing = resourceMap[res.id.name]!!
if (!res.sameClass(existing) || res.id.packageName != existing.id.packageName) {
resourcesToExclude.add(res.id.name)
}
else if (res is AndroidResource.Widget && existing is AndroidResource.Widget) {
// Widgets with the same id but different types exist.
if (res.xmlType != existing.xmlType && existing.xmlType != AndroidConst.VIEW_FQNAME) {
val mergedWidget = AndroidResource.Widget(
res.id, AndroidConst.VIEW_FQNAME, res.sourceElement, isPartiallyDefined)
resourceMap.put(res.id.name, mergedWidget)
}
if (!res.sameClass(existing) || res.id.packageName != existing.id.packageName) {
resourcesToExclude.add(res.id.name)
}
else if (res is AndroidResource.Widget && existing is AndroidResource.Widget) {
// Widgets with the same id but different types exist.
if (res.xmlType != existing.xmlType && existing.xmlType != AndroidConst.VIEW_FQNAME) {
val mergedWidget = AndroidResource.Widget(
res.id, AndroidConst.VIEW_FQNAME, res.sourceElement, isPartiallyDefined)
resourceMap.put(res.id.name, mergedWidget)
}
}
else if (isPartiallyDefined) {
resourceMap.put(res.id.name, res.partiallyDefined())
}
else {
resourceMap.put(res.id.name, res)
}
}
else if (isPartiallyDefined) {
resourceMap.put(res.id.name, res.partiallyDefined())
}
else {
resourceMap.put(res.id.name, res)
}
}
}
@@ -141,12 +141,10 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return resourceMap.values.toList()
}
companion object {
fun getInstance(module: Module): AndroidLayoutXmlFileManager? {
val service = ModuleServiceManager.getService(module, AndroidLayoutXmlFileManager::class.java)
return service ?: module.getComponent(AndroidLayoutXmlFileManager::class.java)
}
}
}
@@ -48,11 +48,11 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
val packagesToLookupInCompletion = arrayListOf<PackageFragmentDescriptor>()
// Packages with synthetic properties
for (variantData in moduleData) {
for ((layoutName, layouts) in variantData) {
for (variantData in moduleData.variants) {
for ((layoutName, layouts) in variantData.layouts) {
fun createPackageFragment(fqName: String, forView: Boolean, isDeprecated: Boolean = false) {
val resources = layoutXmlFileManager.extractResources(layouts, module)
val packageData = AndroidSyntheticPackageData(layoutName, moduleData, forView, isDeprecated, resources)
val resources = layoutXmlFileManager.extractResources(AndroidLayoutGroupData(layoutName, layouts), module)
val packageData = AndroidSyntheticPackageData(moduleData, forView, isDeprecated, resources)
val packageDescriptor = AndroidSyntheticPackageFragmentDescriptor(
module, FqName(fqName), packageData, lazyContext, storageManager)
packagesToLookupInCompletion += packageDescriptor
@@ -71,7 +71,7 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
allPackageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager)
}
for (variantData in moduleData) {
for (variantData in moduleData.variants) {
val fqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name
allPackageDescriptors += PredefinedPackageFragmentDescriptor(fqName, module, storageManager)
}
@@ -79,7 +79,9 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
// Package with clearFindViewByIdCache()
AndroidConst.SYNTHETIC_SUBPACKAGES.last().let { s ->
val packageDescriptor = PredefinedPackageFragmentDescriptor(s, module, storageManager, packagesToLookupInCompletion) { descriptor ->
lazyContext().getWidgetReceivers(false).filter { it.mayHaveCache }.map { genClearCacheFunction(descriptor, it.type) }
lazyContext().getWidgetReceivers(false)
.filter { it.mayHaveCache }
.map { genClearCacheFunction(descriptor, it.type) }
}
packagesToLookupInCompletion += packageDescriptor
allPackageDescriptors += packageDescriptor
@@ -89,14 +91,13 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
}
}
class AndroidSyntheticPackageFragmentProvider(
val packageFragments: Collection<PackageFragmentDescriptor>
) : PackageFragmentProvider {
class AndroidSyntheticPackageFragmentProvider(val packageFragments: Collection<PackageFragmentDescriptor>) : PackageFragmentProvider {
override fun getPackageFragments(fqName: FqName) = packageFragments.filter { it.fqName == fqName }
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean) =
packageFragments.asSequence()
.map { it.fqName }
.filter { !it.isRoot && it.parent() == fqName }
.toList()
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): List<FqName> {
return packageFragments.asSequence()
.map { it.fqName }
.filter { !it.isRoot && it.parent() == fqName }
.toList()
}
}
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import java.io.ByteArrayInputStream
@@ -33,24 +32,19 @@ class CliAndroidLayoutXmlFileManager(
private val saxParser: SAXParser = initSAX()
override fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidLayoutGroup> {
val layoutGroupFiles = files.groupBy { it.name }
val layoutGroups = mutableListOf<AndroidLayoutGroup>()
override fun doExtractResources(layoutGroup: AndroidLayoutGroupData, module: ModuleDescriptor): AndroidLayoutGroup {
val layouts = layoutGroup.layouts.map { layout ->
val resources = arrayListOf<AndroidResource>()
for ((name, layouts) in layoutGroupFiles) {
layoutGroups += AndroidLayoutGroup(name, layouts.map { layout ->
val resources = arrayListOf<AndroidResource>()
val inputStream = ByteArrayInputStream(layout.virtualFile.contentsToByteArray())
saxParser.parse(inputStream, AndroidXmlHandler { id, tag ->
resources += parseAndroidResource(id, tag, null)
})
AndroidLayout(resources)
val inputStream = ByteArrayInputStream(layout.virtualFile.contentsToByteArray())
saxParser.parse(inputStream, AndroidXmlHandler { id, tag ->
resources += parseAndroidResource(id, tag, null)
})
AndroidLayout(resources)
}
return layoutGroups
return AndroidLayoutGroup(layoutGroup.name, layouts)
}
private fun initSAX(): SAXParser {
@@ -61,11 +61,14 @@ internal fun genPropertyForWidget(
val classDescriptor = resolvedWidget.viewClassDescriptor
val type = classDescriptor?.let {
val defaultType = classDescriptor.defaultType
if (defaultType.constructor.parameters.isEmpty())
if (defaultType.constructor.parameters.isEmpty()) {
defaultType
else
KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, classDescriptor,
defaultType.constructor.parameters.map(::StarProjectionImpl))
}
else {
KotlinTypeFactory.simpleNotNullType(
Annotations.EMPTY, classDescriptor, defaultType.constructor.parameters.map(::StarProjectionImpl))
}
} ?: context.viewType
return genProperty(resolvedWidget.widget, receiverType, type, packageFragmentDescriptor, sourceEl, resolvedWidget.errorType)
@@ -89,8 +92,6 @@ private fun genProperty(
sourceElement: SourceElement,
errorType: String?
): PropertyDescriptor {
val cacheView = type.constructor.declarationDescriptor?.fqNameUnsafe?.asString() != AndroidConst.VIEWSTUB_FQNAME
val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl(
containingDeclaration,
null,
@@ -101,7 +102,7 @@ private fun genProperty(
Name.identifier(resource.id.name),
CallableMemberDescriptor.Kind.SYNTHESIZED,
sourceElement,
/* lateinit = */ false,
/* lateInit = */ false,
/* isConst = */ false,
/* isHeader = */ false,
/* isImpl = */ false,
@@ -109,7 +110,7 @@ private fun genProperty(
/* isDelegated = */ false
) {
override val errorType = errorType
override val cacheView = cacheView
override val shouldBeCached = type.shouldBeCached
override val resource = resource
}
@@ -126,11 +127,11 @@ private fun genProperty(
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
false,
false,
false,
/* isDefault = */ false,
/* isExternal = */ false,
/* isInline = */ false,
CallableMemberDescriptor.Kind.SYNTHESIZED,
null,
/* original = */ null,
SourceElement.NO_SOURCE
)
@@ -141,13 +142,26 @@ private fun genProperty(
return property
}
private val SimpleType.shouldBeCached: Boolean
get() {
val viewClassFqName = constructor.declarationDescriptor?.fqNameUnsafe?.asString() ?: return false
return when (viewClassFqName) {
AndroidConst.VIEWSTUB_FQNAME -> false
else -> true
}
}
interface AndroidSyntheticFunction
interface AndroidSyntheticProperty {
val errorType: String?
val cacheView: Boolean
val resource: AndroidResource
val isErrorType: Boolean
get() = errorType != null
val errorType: String?
// True if the View should be cached.
// Some views (such as ViewStub) should not be cached.
val shouldBeCached: Boolean
}
val AndroidSyntheticProperty.isErrorType: Boolean
get() = errorType != null
@@ -33,7 +33,6 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
if (sourceElement is LeafPsiElement && sourceElement.parent is KtSimpleNameExpression) {
val simpleNameExpression = sourceElement.parent as? KtSimpleNameExpression ?: return null
@@ -21,7 +21,6 @@ import com.android.tools.idea.gradle.AndroidGradleModel
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
@@ -33,10 +32,11 @@ import org.jetbrains.kotlin.android.synthetic.res.*
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.project) {
override val androidModule: AndroidModule?
get() = module.androidFacet?.toAndroidModuleInfo()
get() = AndroidFacet.getInstance(module)?.let { getAndroidModuleInfo(it) }
@Volatile
private var _moduleData: CachedValue<AndroidModuleData>? = null
@@ -58,27 +58,21 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
}
private fun getPsiTreeChangePreprocessor(): PsiTreeChangePreprocessor {
return project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
return project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).firstIsInstance<AndroidPsiTreeChangePreprocessor>()
}
override fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidLayoutGroup> {
val layoutGroupFiles = files.groupBy { it.name }
val layoutGroups = mutableListOf<AndroidLayoutGroup>()
for ((name, layouts) in layoutGroupFiles) {
layoutGroups += AndroidLayoutGroup(name, layouts.map { layout ->
val resources = arrayListOf<AndroidResource>()
layout.accept(AndroidXmlVisitor { id, widgetType, attribute ->
resources += parseAndroidResource(id, widgetType, attribute.valueElement)
})
AndroidLayout(resources)
override fun doExtractResources(layoutGroup: AndroidLayoutGroupData, module: ModuleDescriptor): AndroidLayoutGroup {
val layouts = layoutGroup.layouts.map { layout ->
val resources = arrayListOf<AndroidResource>()
layout.accept(AndroidXmlVisitor { id, widgetType, attribute ->
resources += parseAndroidResource(id, widgetType, attribute.valueElement)
})
AndroidLayout(resources)
}
return layoutGroups
return AndroidLayoutGroup(layoutGroup.name, layouts)
}
override fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> {
val fqPath = propertyDescriptor.fqNameUnsafe.pathSegments()
if (fqPath.size <= SYNTHETIC_PACKAGE_PATH_LENGTH) return listOf()
@@ -87,7 +81,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
val layoutNamePosition = SYNTHETIC_PACKAGE_PATH_LENGTH + (if (defaultVariant) 0 else 1)
val layoutName = fqPath[layoutNamePosition].asString()
val layoutFiles = variantData[layoutName] ?: return null
val layoutFiles = variantData.layouts[layoutName] ?: return null
if (layoutFiles.isEmpty()) return null
val propertyName = propertyDescriptor.name.asString()
@@ -101,7 +95,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
return attributes
}
for (variantData in getModuleData()) {
for (variantData in getModuleData().variants) {
if (variantData.variant.isMainVariant && fqPath.size == SYNTHETIC_PACKAGE_PATH_LENGTH + 2) {
handle(variantData, true)?.let { return it }
}
@@ -115,16 +109,13 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
private fun SourceProvider.toVariant() = AndroidVariant(name, resDirectories.map { it.canonicalPath })
private val Module.androidFacet: AndroidFacet?
get() = AndroidFacet.getInstance(this)
private fun getAndroidModuleInfo(androidFacet: AndroidFacet): AndroidModule? {
val applicationPackage = androidFacet.manifest?.`package`?.toString() ?: return null
private fun AndroidFacet.toAndroidModuleInfo(): AndroidModule? {
val applicationPackage = manifest?.`package`?.toString() ?: return null
val allResDirectories = androidFacet.getAppResources(true)?.resourceDirs.orEmpty().mapNotNull { it.canonicalPath }
val allResDirectories = getAppResources(true)?.resourceDirs.orEmpty().mapNotNull { it.canonicalPath }
val resDirectoriesForMainVariant = run {
val resDirsFromSourceProviders = AndroidGradleModel.get(module)?.allSourceProviders.orEmpty()
val resDirectoriesForMainVariant = androidFacet.run {
val resDirsFromSourceProviders = AndroidGradleModel.get(this.module)?.allSourceProviders.orEmpty()
.filter { it.name != "main" }
.flatMap { it.resDirectories }
.map { it.canonicalPath }
@@ -134,7 +125,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
val variants = mutableListOf(AndroidVariant("main", resDirectoriesForMainVariant))
AndroidGradleModel.get(module)?.let { androidGradleModel ->
AndroidGradleModel.get(androidFacet.module)?.let { androidGradleModel ->
androidGradleModel.activeSourceProviders.filter { it.name != "main" }.forEach { sourceProvider ->
variants += sourceProvider.toVariant()
}