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 import java.util.HashMap
class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String) -> Unit) : DefaultHandler() { class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String) -> Unit) : DefaultHandler() {
override fun startDocument() { override fun startDocument() {
super.startDocument() super.startDocument()
} }
@@ -40,13 +39,10 @@ class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String
if (name != null) elementCallback(name, widgetType) 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>() val res = HashMap<String, String>()
for (index in 0..length - 1) { for (index in 0..length - 1) {
val attrName = getLocalName(index)!! 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.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin 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.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement 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.Label
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC 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.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter 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 { class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
companion object { companion object {
private val PROPERTY_NAME = "_\$_findViewCache" 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 CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val ON_DESTROY_METHOD_NAME = "onDestroyView" val ON_DESTROY_METHOD_NAME = "onDestroyView"
fun isCacheSupported(receiverDescriptor: ClassDescriptor, descriptor: PropertyDescriptor? = null): Boolean { fun doesContainerSupportCache(container: ClassDescriptor): Boolean {
val receiverIsKotlinClass = receiverDescriptor.source is KotlinSourceElement return container.source is KotlinSourceElement
return receiverIsKotlinClass && when (descriptor) { }
is AndroidSyntheticProperty -> descriptor.cacheView
else -> true 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? { override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is PropertyDescriptor) { return if (resultingDescriptor is PropertyDescriptor) {
return generateSyntheticPropertyCall(receiver, resolvedCall, c, resultingDescriptor) return generateResourcePropertyCall(receiver, resolvedCall, c, resultingDescriptor)
} }
else null else null
} }
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? { override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor val targetCallable = resolvedCall.resultingDescriptor
return if (resultingDescriptor is FunctionDescriptor) { resolvedCall.resultingDescriptor as? AndroidSyntheticFunction ?: return null
return generateSyntheticFunctionCall(receiver, resolvedCall, c, resultingDescriptor)
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, receiver: StackValue,
resolvedCall: ResolvedCall<*>, container: ClassDescriptor,
c: ExpressionCodegenExtension.Context, c: ExpressionCodegenExtension.Context
functionDescriptor: FunctionDescriptor
): StackValue? { ): StackValue? {
if (functionDescriptor !is AndroidSyntheticFunction) return null if (!doesContainerSupportCache(container)) {
if (functionDescriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null return StackValue.functionCall(Type.VOID_TYPE) {}
}
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null val androidClassType = AndroidClassType.getClassType(container)
if (!isCacheSupported(receiverDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return null if (androidClassType == AndroidClassType.UNKNOWN) return null
return StackValue.functionCall(Type.VOID_TYPE) { 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) it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
} }
} }
private fun generateSyntheticPropertyCall( private fun generateResourcePropertyCall(
receiver: StackValue, receiver: StackValue,
resolvedCall: ResolvedCall<*>, resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context, c: ExpressionCodegenExtension.Context,
@@ -164,84 +121,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
val androidClassType = AndroidClassType.getClassType(receiverDescriptor) val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
return SyntheticProperty(receiver, c.typeMapper, descriptor, receiverDescriptor, androidClassType, androidPackage) return ResourcePropertyStackValue(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")
}
} }
private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? { private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? {
@@ -250,18 +130,16 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) { override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
val classBuilder = codegen.v val classBuilder = codegen.v
val state = codegen.state val targetClass = codegen.myClass as? KtClassOrObject ?: return
val classOrObject = codegen.myClass
val descriptor = codegen.descriptor
val descriptor = codegen.descriptor
if (descriptor.kind != ClassKind.CLASS || descriptor.isInner || DescriptorUtils.isLocal(descriptor)) return 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 // Do not generate anything if class is not supported
val androidClassType = AndroidClassType.getClassType(descriptor) val androidClassType = AndroidClassType.getClassType(descriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return 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.generateCachedFindViewByIdFunction()
context.generateClearCacheFunction() context.generateClearCacheFunction()
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
@@ -107,12 +106,12 @@ class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptor
private fun generateClearCacheMethodCall() { private fun generateClearCacheMethodCall() {
if (name != AndroidExpressionCodegenExtension.ON_DESTROY_METHOD_NAME || currentClass == null) return 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 if (Type.getReturnType(desc) != Type.VOID_TYPE) return
val classType = currentClassName?.let { Type.getObjectType(it) } ?: 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) val androidClassType = AndroidClassType.getClassType(descriptor)
if (!androidClassType.fragment) return 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.* import java.util.*
class AndroidSyntheticPackageData( class AndroidSyntheticPackageData(
val layoutName: String,
val moduleData: AndroidModuleData, val moduleData: AndroidModuleData,
val forView: Boolean, val forView: Boolean,
val isDeprecated: Boolean, val isDeprecated: Boolean,
@@ -41,18 +41,20 @@ class PredefinedPackageFragmentDescriptor(
} }
private val scope = PredefinedScope() private val scope = PredefinedScope()
override fun getMemberScope() = scope override fun getMemberScope() = scope
inner class PredefinedScope : MemberScopeImpl() { inner class PredefinedScope : MemberScopeImpl() {
@Suppress("UNCHECKED_CAST")
override fun getContributedVariables(name: Name, location: LookupLocation) = emptyList<PropertyDescriptor>() 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) = override fun getContributedDescriptors(
calculatedFunctions().filter { nameFilter(it.name) && kindFilter.accepts(it) } kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): List<SimpleFunctionDescriptor> {
return calculatedFunctions().filter { nameFilter(it.name) && kindFilter.accepts(it) }
}
override fun printScopeStructure(p: Printer) { override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName) 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.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid.* import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid.*
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty 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.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
@@ -29,15 +29,17 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import java.util.* import java.util.*
class AndroidVariantData(val variant: AndroidVariant, private val layouts: Map<String, List<PsiFile>>): Map<String, List<PsiFile>> by layouts class AndroidVariantData(val variant: AndroidVariant, val layouts: Map<String, List<PsiFile>>)
class AndroidModuleData(val module: AndroidModule, private val variants: List<AndroidVariantData>): Iterable<AndroidVariantData> by variants {
class AndroidModuleData(val module: AndroidModule, val variants: List<AndroidVariantData>) {
companion object { companion object {
val EMPTY = AndroidModuleData(AndroidModule("android", listOf()), listOf()) 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? abstract val androidModule: AndroidModule?
open fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> = listOf() 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) }) 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 psiManager = PsiManager.getInstance(project)
val fileManager = VirtualFileManager.getInstance() val fileManager = VirtualFileManager.getInstance()
@@ -84,11 +86,11 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return AndroidVariantData(variant, layoutNameToXmlFiles) return AndroidVariantData(variant, layoutNameToXmlFiles)
} }
fun extractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource> { fun extractResources(layoutGroupFiles: AndroidLayoutGroupData, module: ModuleDescriptor): List<AndroidResource> {
return filterDuplicates(doExtractResources(files, module)) 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 { protected fun parseAndroidResource(id: ResourceIdentifier, tag: String, sourceElement: PsiElement?): AndroidResource {
return when (tag) { 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 resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>() val resourcesToExclude = hashSetOf<String>()
for (layoutGroup in layoutGroups) { val resourcesByName = layoutGroup.layouts.flatMap { it.resources }.groupBy {
val resources = layoutGroup.layouts.flatMap { it.resources }.groupBy { val id = it.id
val id = it.id if (id.packageName == null) id.name else id.packageName + "/" + id.name
if (id.packageName == null) id.name else id.packageName + "/" + id.name }
}
for (resources in resources.values) { for (resources in resourcesByName.values) {
val isPartiallyDefined = resources.size < layoutGroup.layouts.size val isPartiallyDefined = resources.size < layoutGroup.layouts.size
for (res in resources) { for (res in resources) {
if (resourceMap.contains(res.id.name)) { if (res.id.name in resourceMap) {
val existing = resourceMap[res.id.name]!! val existing = resourceMap[res.id.name]!!
if (!res.sameClass(existing) || res.id.packageName != existing.id.packageName) { if (!res.sameClass(existing) || res.id.packageName != existing.id.packageName) {
resourcesToExclude.add(res.id.name) resourcesToExclude.add(res.id.name)
} }
else if (res is AndroidResource.Widget && existing is AndroidResource.Widget) { else if (res is AndroidResource.Widget && existing is AndroidResource.Widget) {
// Widgets with the same id but different types exist. // Widgets with the same id but different types exist.
if (res.xmlType != existing.xmlType && existing.xmlType != AndroidConst.VIEW_FQNAME) { if (res.xmlType != existing.xmlType && existing.xmlType != AndroidConst.VIEW_FQNAME) {
val mergedWidget = AndroidResource.Widget( val mergedWidget = AndroidResource.Widget(
res.id, AndroidConst.VIEW_FQNAME, res.sourceElement, isPartiallyDefined) res.id, AndroidConst.VIEW_FQNAME, res.sourceElement, isPartiallyDefined)
resourceMap.put(res.id.name, mergedWidget) resourceMap.put(res.id.name, mergedWidget)
}
} }
} }
else if (isPartiallyDefined) { }
resourceMap.put(res.id.name, res.partiallyDefined()) else if (isPartiallyDefined) {
} resourceMap.put(res.id.name, res.partiallyDefined())
else { }
resourceMap.put(res.id.name, res) else {
} resourceMap.put(res.id.name, res)
} }
} }
} }
@@ -141,12 +141,10 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return resourceMap.values.toList() return resourceMap.values.toList()
} }
companion object { companion object {
fun getInstance(module: Module): AndroidLayoutXmlFileManager? { fun getInstance(module: Module): AndroidLayoutXmlFileManager? {
val service = ModuleServiceManager.getService(module, AndroidLayoutXmlFileManager::class.java) val service = ModuleServiceManager.getService(module, AndroidLayoutXmlFileManager::class.java)
return service ?: module.getComponent(AndroidLayoutXmlFileManager::class.java) return service ?: module.getComponent(AndroidLayoutXmlFileManager::class.java)
} }
} }
} }
@@ -48,11 +48,11 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
val packagesToLookupInCompletion = arrayListOf<PackageFragmentDescriptor>() val packagesToLookupInCompletion = arrayListOf<PackageFragmentDescriptor>()
// Packages with synthetic properties // Packages with synthetic properties
for (variantData in moduleData) { for (variantData in moduleData.variants) {
for ((layoutName, layouts) in variantData) { for ((layoutName, layouts) in variantData.layouts) {
fun createPackageFragment(fqName: String, forView: Boolean, isDeprecated: Boolean = false) { fun createPackageFragment(fqName: String, forView: Boolean, isDeprecated: Boolean = false) {
val resources = layoutXmlFileManager.extractResources(layouts, module) val resources = layoutXmlFileManager.extractResources(AndroidLayoutGroupData(layoutName, layouts), module)
val packageData = AndroidSyntheticPackageData(layoutName, moduleData, forView, isDeprecated, resources) val packageData = AndroidSyntheticPackageData(moduleData, forView, isDeprecated, resources)
val packageDescriptor = AndroidSyntheticPackageFragmentDescriptor( val packageDescriptor = AndroidSyntheticPackageFragmentDescriptor(
module, FqName(fqName), packageData, lazyContext, storageManager) module, FqName(fqName), packageData, lazyContext, storageManager)
packagesToLookupInCompletion += packageDescriptor packagesToLookupInCompletion += packageDescriptor
@@ -71,7 +71,7 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
allPackageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager) allPackageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager)
} }
for (variantData in moduleData) { for (variantData in moduleData.variants) {
val fqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name val fqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name
allPackageDescriptors += PredefinedPackageFragmentDescriptor(fqName, module, storageManager) allPackageDescriptors += PredefinedPackageFragmentDescriptor(fqName, module, storageManager)
} }
@@ -79,7 +79,9 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
// Package with clearFindViewByIdCache() // Package with clearFindViewByIdCache()
AndroidConst.SYNTHETIC_SUBPACKAGES.last().let { s -> AndroidConst.SYNTHETIC_SUBPACKAGES.last().let { s ->
val packageDescriptor = PredefinedPackageFragmentDescriptor(s, module, storageManager, packagesToLookupInCompletion) { descriptor -> 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 packagesToLookupInCompletion += packageDescriptor
allPackageDescriptors += packageDescriptor allPackageDescriptors += packageDescriptor
@@ -89,14 +91,13 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider
} }
} }
class AndroidSyntheticPackageFragmentProvider( class AndroidSyntheticPackageFragmentProvider(val packageFragments: Collection<PackageFragmentDescriptor>) : PackageFragmentProvider {
val packageFragments: Collection<PackageFragmentDescriptor>
) : PackageFragmentProvider {
override fun getPackageFragments(fqName: FqName) = packageFragments.filter { it.fqName == fqName } override fun getPackageFragments(fqName: FqName) = packageFragments.filter { it.fqName == fqName }
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean) = override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): List<FqName> {
packageFragments.asSequence() return packageFragments.asSequence()
.map { it.fqName } .map { it.fqName }
.filter { !it.isRoot && it.parent() == fqName } .filter { !it.isRoot && it.parent() == fqName }
.toList() .toList()
}
} }
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.android.synthetic.res package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@@ -33,24 +32,19 @@ class CliAndroidLayoutXmlFileManager(
private val saxParser: SAXParser = initSAX() private val saxParser: SAXParser = initSAX()
override fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidLayoutGroup> { override fun doExtractResources(layoutGroup: AndroidLayoutGroupData, module: ModuleDescriptor): AndroidLayoutGroup {
val layoutGroupFiles = files.groupBy { it.name } val layouts = layoutGroup.layouts.map { layout ->
val layoutGroups = mutableListOf<AndroidLayoutGroup>() val resources = arrayListOf<AndroidResource>()
for ((name, layouts) in layoutGroupFiles) { val inputStream = ByteArrayInputStream(layout.virtualFile.contentsToByteArray())
layoutGroups += AndroidLayoutGroup(name, layouts.map { layout -> saxParser.parse(inputStream, AndroidXmlHandler { id, tag ->
val resources = arrayListOf<AndroidResource>() resources += parseAndroidResource(id, tag, null)
val inputStream = ByteArrayInputStream(layout.virtualFile.contentsToByteArray())
saxParser.parse(inputStream, AndroidXmlHandler { id, tag ->
resources += parseAndroidResource(id, tag, null)
})
AndroidLayout(resources)
}) })
AndroidLayout(resources)
} }
return layoutGroups return AndroidLayoutGroup(layoutGroup.name, layouts)
} }
private fun initSAX(): SAXParser { private fun initSAX(): SAXParser {
@@ -61,11 +61,14 @@ internal fun genPropertyForWidget(
val classDescriptor = resolvedWidget.viewClassDescriptor val classDescriptor = resolvedWidget.viewClassDescriptor
val type = classDescriptor?.let { val type = classDescriptor?.let {
val defaultType = classDescriptor.defaultType val defaultType = classDescriptor.defaultType
if (defaultType.constructor.parameters.isEmpty())
if (defaultType.constructor.parameters.isEmpty()) {
defaultType defaultType
else }
KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, classDescriptor, else {
defaultType.constructor.parameters.map(::StarProjectionImpl)) KotlinTypeFactory.simpleNotNullType(
Annotations.EMPTY, classDescriptor, defaultType.constructor.parameters.map(::StarProjectionImpl))
}
} ?: context.viewType } ?: context.viewType
return genProperty(resolvedWidget.widget, receiverType, type, packageFragmentDescriptor, sourceEl, resolvedWidget.errorType) return genProperty(resolvedWidget.widget, receiverType, type, packageFragmentDescriptor, sourceEl, resolvedWidget.errorType)
@@ -89,8 +92,6 @@ private fun genProperty(
sourceElement: SourceElement, sourceElement: SourceElement,
errorType: String? errorType: String?
): PropertyDescriptor { ): PropertyDescriptor {
val cacheView = type.constructor.declarationDescriptor?.fqNameUnsafe?.asString() != AndroidConst.VIEWSTUB_FQNAME
val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl( val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl(
containingDeclaration, containingDeclaration,
null, null,
@@ -101,7 +102,7 @@ private fun genProperty(
Name.identifier(resource.id.name), Name.identifier(resource.id.name),
CallableMemberDescriptor.Kind.SYNTHESIZED, CallableMemberDescriptor.Kind.SYNTHESIZED,
sourceElement, sourceElement,
/* lateinit = */ false, /* lateInit = */ false,
/* isConst = */ false, /* isConst = */ false,
/* isHeader = */ false, /* isHeader = */ false,
/* isImpl = */ false, /* isImpl = */ false,
@@ -109,7 +110,7 @@ private fun genProperty(
/* isDelegated = */ false /* isDelegated = */ false
) { ) {
override val errorType = errorType override val errorType = errorType
override val cacheView = cacheView override val shouldBeCached = type.shouldBeCached
override val resource = resource override val resource = resource
} }
@@ -126,11 +127,11 @@ private fun genProperty(
Annotations.EMPTY, Annotations.EMPTY,
Modality.FINAL, Modality.FINAL,
Visibilities.PUBLIC, Visibilities.PUBLIC,
false, /* isDefault = */ false,
false, /* isExternal = */ false,
false, /* isInline = */ false,
CallableMemberDescriptor.Kind.SYNTHESIZED, CallableMemberDescriptor.Kind.SYNTHESIZED,
null, /* original = */ null,
SourceElement.NO_SOURCE SourceElement.NO_SOURCE
) )
@@ -141,13 +142,26 @@ private fun genProperty(
return property 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 AndroidSyntheticFunction
interface AndroidSyntheticProperty { interface AndroidSyntheticProperty {
val errorType: String?
val cacheView: Boolean
val resource: AndroidResource val resource: AndroidResource
val isErrorType: Boolean val errorType: String?
get() = errorType != null
// 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 import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class AndroidGotoDeclarationHandler : GotoDeclarationHandler { class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? { override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
if (sourceElement is LeafPsiElement && sourceElement.parent is KtSimpleNameExpression) { if (sourceElement is LeafPsiElement && sourceElement.parent is KtSimpleNameExpression) {
val simpleNameExpression = sourceElement.parent as? KtSimpleNameExpression ?: return null 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.module.Module
import com.intellij.openapi.roots.ProjectRootModificationTracker import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider 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.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.project) { class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.project) {
override val androidModule: AndroidModule? override val androidModule: AndroidModule?
get() = module.androidFacet?.toAndroidModuleInfo() get() = AndroidFacet.getInstance(module)?.let { getAndroidModuleInfo(it) }
@Volatile @Volatile
private var _moduleData: CachedValue<AndroidModuleData>? = null private var _moduleData: CachedValue<AndroidModuleData>? = null
@@ -58,27 +58,21 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
} }
private fun getPsiTreeChangePreprocessor(): PsiTreeChangePreprocessor { 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> { override fun doExtractResources(layoutGroup: AndroidLayoutGroupData, module: ModuleDescriptor): AndroidLayoutGroup {
val layoutGroupFiles = files.groupBy { it.name } val layouts = layoutGroup.layouts.map { layout ->
val layoutGroups = mutableListOf<AndroidLayoutGroup>() val resources = arrayListOf<AndroidResource>()
layout.accept(AndroidXmlVisitor { id, widgetType, attribute ->
for ((name, layouts) in layoutGroupFiles) { resources += parseAndroidResource(id, widgetType, attribute.valueElement)
layoutGroups += AndroidLayoutGroup(name, layouts.map { layout ->
val resources = arrayListOf<AndroidResource>()
layout.accept(AndroidXmlVisitor { id, widgetType, attribute ->
resources += parseAndroidResource(id, widgetType, attribute.valueElement)
})
AndroidLayout(resources)
}) })
AndroidLayout(resources)
} }
return layoutGroups return AndroidLayoutGroup(layoutGroup.name, layouts)
} }
override fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> { override fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> {
val fqPath = propertyDescriptor.fqNameUnsafe.pathSegments() val fqPath = propertyDescriptor.fqNameUnsafe.pathSegments()
if (fqPath.size <= SYNTHETIC_PACKAGE_PATH_LENGTH) return listOf() 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 layoutNamePosition = SYNTHETIC_PACKAGE_PATH_LENGTH + (if (defaultVariant) 0 else 1)
val layoutName = fqPath[layoutNamePosition].asString() val layoutName = fqPath[layoutNamePosition].asString()
val layoutFiles = variantData[layoutName] ?: return null val layoutFiles = variantData.layouts[layoutName] ?: return null
if (layoutFiles.isEmpty()) return null if (layoutFiles.isEmpty()) return null
val propertyName = propertyDescriptor.name.asString() val propertyName = propertyDescriptor.name.asString()
@@ -101,7 +95,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
return attributes return attributes
} }
for (variantData in getModuleData()) { for (variantData in getModuleData().variants) {
if (variantData.variant.isMainVariant && fqPath.size == SYNTHETIC_PACKAGE_PATH_LENGTH + 2) { if (variantData.variant.isMainVariant && fqPath.size == SYNTHETIC_PACKAGE_PATH_LENGTH + 2) {
handle(variantData, true)?.let { return it } 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 fun SourceProvider.toVariant() = AndroidVariant(name, resDirectories.map { it.canonicalPath })
private val Module.androidFacet: AndroidFacet? private fun getAndroidModuleInfo(androidFacet: AndroidFacet): AndroidModule? {
get() = AndroidFacet.getInstance(this) val applicationPackage = androidFacet.manifest?.`package`?.toString() ?: return null
private fun AndroidFacet.toAndroidModuleInfo(): AndroidModule? { val allResDirectories = androidFacet.getAppResources(true)?.resourceDirs.orEmpty().mapNotNull { it.canonicalPath }
val applicationPackage = manifest?.`package`?.toString() ?: return null
val allResDirectories = getAppResources(true)?.resourceDirs.orEmpty().mapNotNull { it.canonicalPath } val resDirectoriesForMainVariant = androidFacet.run {
val resDirsFromSourceProviders = AndroidGradleModel.get(this.module)?.allSourceProviders.orEmpty()
val resDirectoriesForMainVariant = run {
val resDirsFromSourceProviders = AndroidGradleModel.get(module)?.allSourceProviders.orEmpty()
.filter { it.name != "main" } .filter { it.name != "main" }
.flatMap { it.resDirectories } .flatMap { it.resDirectories }
.map { it.canonicalPath } .map { it.canonicalPath }
@@ -134,7 +125,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
val variants = mutableListOf(AndroidVariant("main", resDirectoriesForMainVariant)) 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 -> androidGradleModel.activeSourceProviders.filter { it.name != "main" }.forEach { sourceProvider ->
variants += sourceProvider.toVariant() variants += sourceProvider.toVariant()
} }