Android Extensions: support identifiers with packages (KT-10841)

This commit is contained in:
Yan Zhulanow
2016-03-25 16:25:09 +03:00
parent 0494e1d258
commit 9e33b7d3bc
10 changed files with 64 additions and 32 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.android.synthetic
import org.jetbrains.kotlin.android.synthetic.res.ResourceIdentifier
object AndroidConst {
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
@@ -32,9 +33,7 @@ object AndroidConst {
val ID_ATTRIBUTE: String = "$ANDROID_NAMESPACE:$ID_ATTRIBUTE_NO_NAMESPACE"
val CLASS_ATTRIBUTE_NO_NAMESPACE: String = "class"
val ID_DECLARATION_PREFIX = "@+id/"
val ID_USAGE_PREFIX = "@id/"
val XML_ID_PREFIXES = arrayOf(ID_DECLARATION_PREFIX, ID_USAGE_PREFIX)
val IDENTIFIER_REGEX = "^@(\\+)?(([A-Za-z0-9_\\.]+)\\:)?id\\/([A-Za-z0-9_]+)$".toRegex()
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
@@ -55,13 +54,10 @@ object AndroidConst {
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
}
fun androidIdToName(id: String): String? {
for (prefix in AndroidConst.XML_ID_PREFIXES) {
if (id.startsWith(prefix)) {
return id.substring(prefix.length)
}
}
return null
fun androidIdToName(id: String): ResourceIdentifier? {
val values = AndroidConst.IDENTIFIER_REGEX.matchEntire(id)?.groupValues ?: return null
val packageName = values[3]
return ResourceIdentifier(values[4], if (packageName.isEmpty()) null else packageName)
}
fun isWidgetTypeIgnored(xmlType: String): Boolean {
@@ -16,11 +16,12 @@
package org.jetbrains.kotlin.android.synthetic
import org.jetbrains.kotlin.android.synthetic.res.ResourceIdentifier
import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import java.util.HashMap
class AndroidXmlHandler(private val elementCallback: (String, String) -> Unit) : DefaultHandler() {
class AndroidXmlHandler(private val elementCallback: (ResourceIdentifier, String) -> Unit) : DefaultHandler() {
override fun startDocument() {
super.startDocument()
@@ -178,10 +178,16 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
return putSelectorForFragment(v)
}
val syntheticProperty = propertyDescriptor as AndroidSyntheticProperty
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
receiver.put(declarationDescriptorType, v)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
val resourceId = syntheticProperty.resourceId
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 {
@@ -60,8 +60,10 @@ class AndroidSyntheticPackageFragmentDescriptor(
when (resource) {
is AndroidResource.Widget -> {
val resolvedWidget = resource.resolve(module)
for (receiver in widgetReceivers) {
properties += genPropertyForWidget(packageFragmentDescriptor, receiver, resolvedWidget, context)
if (resolvedWidget != null) {
for (receiver in widgetReceivers) {
properties += genPropertyForWidget(packageFragmentDescriptor, receiver, resolvedWidget, context)
}
}
}
is AndroidResource.Fragment -> if (!packageData.forView) {
@@ -27,7 +27,6 @@ import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.psi.KtProperty
import java.util.*
class AndroidVariantData(val variant: AndroidVariant, private val layouts: Map<String, List<PsiFile>>): Map<String, List<PsiFile>> by layouts
@@ -91,7 +90,7 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
protected abstract fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource>
protected fun parseAndroidResource(id: String, tag: String, sourceElement: PsiElement?): AndroidResource {
protected fun parseAndroidResource(id: ResourceIdentifier, tag: String, sourceElement: PsiElement?): AndroidResource {
return when (tag) {
"fragment" -> AndroidResource.Fragment(id, sourceElement)
"include" -> AndroidResource.Widget(id, AndroidConst.VIEW_FQNAME, sourceElement)
@@ -104,20 +103,20 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
val resourcesToExclude = hashSetOf<String>()
for (res in resources) {
if (resourceMap.contains(res.id)) {
val existing = resourceMap[res.id]!!
if (resourceMap.contains(res.id.name)) {
val existing = resourceMap[res.id.name]!!
if (!res.sameClass(existing)) {
resourcesToExclude.add(res.id)
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) {
resourceMap.put(res.id, AndroidResource.Widget(res.id, AndroidConst.VIEW_FQNAME, res.sourceElement))
resourceMap.put(res.id.name, AndroidResource.Widget(res.id, AndroidConst.VIEW_FQNAME, res.sourceElement))
}
}
}
else resourceMap.put(res.id, res)
else resourceMap.put(res.id.name, res)
}
resourcesToExclude.forEach { resourceMap.remove(it) }
return resourceMap.values.toList()
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.isValidJavaFqName
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
class AndroidVariant(val name: String, val resDirectories: List<String>) {
@@ -43,18 +44,35 @@ class AndroidModule(val applicationPackage: String, val variants: List<AndroidVa
override fun hashCode() = applicationPackage.hashCode()
}
sealed class AndroidResource(val id: String, val sourceElement: PsiElement?) {
class ResourceIdentifier(val name: String, val packageName: String?) {
// Without packageName
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as ResourceIdentifier
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
}
sealed class AndroidResource(val id: ResourceIdentifier, val sourceElement: PsiElement?) {
open fun sameClass(other: AndroidResource): Boolean = false
class Widget(
id: String,
id: ResourceIdentifier,
val xmlType: String,
sourceElement: PsiElement?
) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Widget
}
class Fragment(id: String, sourceElement: PsiElement?) : AndroidResource(id, sourceElement) {
class Fragment(id: ResourceIdentifier, sourceElement: PsiElement?) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Fragment
}
}
@@ -71,8 +89,15 @@ class ResolvedWidget(val widget: AndroidResource.Widget, val viewClassDescriptor
get() = if (isErrorType) widget.xmlType else null
}
fun AndroidResource.Widget.resolve(module: ModuleDescriptor): ResolvedWidget {
fun resolve(fqName: String) = module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(fqName)))
fun AndroidResource.Widget.resolve(module: ModuleDescriptor): ResolvedWidget? {
fun resolve(fqName: String): ClassDescriptor? {
if (!isValidJavaFqName(fqName)) return null
return module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(fqName)))
}
if (id.packageName != null && resolve(id.packageName + ".R") == null) {
return null
}
if ('.' in xmlType) {
return ResolvedWidget(this, resolve(xmlType))
@@ -81,7 +81,7 @@ internal fun genPropertyForFragment(
}
private fun genProperty(
id: String,
id: ResourceIdentifier,
receiverType: KotlinType,
type: KotlinType,
containingDeclaration: DeclarationDescriptor,
@@ -98,13 +98,14 @@ private fun genProperty(
Modality.FINAL,
Visibilities.PUBLIC,
false,
Name.identifier(id),
Name.identifier(id.name),
CallableMemberDescriptor.Kind.SYNTHESIZED,
sourceElement,
false,
false) {
override val errorType = errorType
override val alwaysCastToView = alwaysCastToView
override val resourceId = id
}
val actualType = if (alwaysCastToView) context.viewType else type
@@ -139,6 +140,7 @@ interface AndroidSyntheticFunction
interface AndroidSyntheticProperty {
val errorType: String?
val alwaysCastToView: Boolean
val resourceId: ResourceIdentifier
val isErrorType: Boolean
get() = errorType != null