Android Extensions: support identifiers with packages (KT-10841)
This commit is contained in:
+6
-10
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.android.synthetic
|
package org.jetbrains.kotlin.android.synthetic
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.android.synthetic.res.ResourceIdentifier
|
||||||
|
|
||||||
object AndroidConst {
|
object AndroidConst {
|
||||||
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
|
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
|
||||||
@@ -32,9 +33,7 @@ object AndroidConst {
|
|||||||
val ID_ATTRIBUTE: String = "$ANDROID_NAMESPACE:$ID_ATTRIBUTE_NO_NAMESPACE"
|
val ID_ATTRIBUTE: String = "$ANDROID_NAMESPACE:$ID_ATTRIBUTE_NO_NAMESPACE"
|
||||||
val CLASS_ATTRIBUTE_NO_NAMESPACE: String = "class"
|
val CLASS_ATTRIBUTE_NO_NAMESPACE: String = "class"
|
||||||
|
|
||||||
val ID_DECLARATION_PREFIX = "@+id/"
|
val IDENTIFIER_REGEX = "^@(\\+)?(([A-Za-z0-9_\\.]+)\\:)?id\\/([A-Za-z0-9_]+)$".toRegex()
|
||||||
val ID_USAGE_PREFIX = "@id/"
|
|
||||||
val XML_ID_PREFIXES = arrayOf(ID_DECLARATION_PREFIX, ID_USAGE_PREFIX)
|
|
||||||
|
|
||||||
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
|
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
|
||||||
|
|
||||||
@@ -55,13 +54,10 @@ object AndroidConst {
|
|||||||
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
|
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun androidIdToName(id: String): String? {
|
fun androidIdToName(id: String): ResourceIdentifier? {
|
||||||
for (prefix in AndroidConst.XML_ID_PREFIXES) {
|
val values = AndroidConst.IDENTIFIER_REGEX.matchEntire(id)?.groupValues ?: return null
|
||||||
if (id.startsWith(prefix)) {
|
val packageName = values[3]
|
||||||
return id.substring(prefix.length)
|
return ResourceIdentifier(values[4], if (packageName.isEmpty()) null else packageName)
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isWidgetTypeIgnored(xmlType: String): Boolean {
|
fun isWidgetTypeIgnored(xmlType: String): Boolean {
|
||||||
|
|||||||
+2
-1
@@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.android.synthetic
|
package org.jetbrains.kotlin.android.synthetic
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.android.synthetic.res.ResourceIdentifier
|
||||||
import org.xml.sax.Attributes
|
import org.xml.sax.Attributes
|
||||||
import org.xml.sax.helpers.DefaultHandler
|
import org.xml.sax.helpers.DefaultHandler
|
||||||
import java.util.HashMap
|
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() {
|
override fun startDocument() {
|
||||||
super.startDocument()
|
super.startDocument()
|
||||||
|
|||||||
+7
-1
@@ -178,10 +178,16 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
|||||||
return putSelectorForFragment(v)
|
return putSelectorForFragment(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syntheticProperty = propertyDescriptor as AndroidSyntheticProperty
|
||||||
|
|
||||||
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
|
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
|
||||||
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
|
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
|
||||||
receiver.put(declarationDescriptorType, v)
|
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)
|
v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
+4
-2
@@ -60,8 +60,10 @@ class AndroidSyntheticPackageFragmentDescriptor(
|
|||||||
when (resource) {
|
when (resource) {
|
||||||
is AndroidResource.Widget -> {
|
is AndroidResource.Widget -> {
|
||||||
val resolvedWidget = resource.resolve(module)
|
val resolvedWidget = resource.resolve(module)
|
||||||
for (receiver in widgetReceivers) {
|
if (resolvedWidget != null) {
|
||||||
properties += genPropertyForWidget(packageFragmentDescriptor, receiver, resolvedWidget, context)
|
for (receiver in widgetReceivers) {
|
||||||
|
properties += genPropertyForWidget(packageFragmentDescriptor, receiver, resolvedWidget, context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is AndroidResource.Fragment -> if (!packageData.forView) {
|
is AndroidResource.Fragment -> if (!packageData.forView) {
|
||||||
|
|||||||
+7
-8
@@ -27,7 +27,6 @@ import com.intellij.psi.PsiManager
|
|||||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||||
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.psi.KtProperty
|
|
||||||
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, 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 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) {
|
return when (tag) {
|
||||||
"fragment" -> AndroidResource.Fragment(id, sourceElement)
|
"fragment" -> AndroidResource.Fragment(id, sourceElement)
|
||||||
"include" -> AndroidResource.Widget(id, AndroidConst.VIEW_FQNAME, sourceElement)
|
"include" -> AndroidResource.Widget(id, AndroidConst.VIEW_FQNAME, sourceElement)
|
||||||
@@ -104,20 +103,20 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
|
|||||||
val resourcesToExclude = hashSetOf<String>()
|
val resourcesToExclude = hashSetOf<String>()
|
||||||
|
|
||||||
for (res in resources) {
|
for (res in resources) {
|
||||||
if (resourceMap.contains(res.id)) {
|
if (resourceMap.contains(res.id.name)) {
|
||||||
val existing = resourceMap[res.id]!!
|
val existing = resourceMap[res.id.name]!!
|
||||||
|
|
||||||
if (!res.sameClass(existing)) {
|
if (!res.sameClass(existing) || res.id.packageName != existing.id.packageName) {
|
||||||
resourcesToExclude.add(res.id)
|
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) {
|
||||||
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) }
|
resourcesToExclude.forEach { resourceMap.remove(it) }
|
||||||
return resourceMap.values.toList()
|
return resourceMap.values.toList()
|
||||||
|
|||||||
+30
-5
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.isValidJavaFqName
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
||||||
|
|
||||||
class AndroidVariant(val name: String, val resDirectories: List<String>) {
|
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()
|
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
|
open fun sameClass(other: AndroidResource): Boolean = false
|
||||||
|
|
||||||
class Widget(
|
class Widget(
|
||||||
id: String,
|
id: ResourceIdentifier,
|
||||||
val xmlType: String,
|
val xmlType: String,
|
||||||
sourceElement: PsiElement?
|
sourceElement: PsiElement?
|
||||||
) : AndroidResource(id, sourceElement) {
|
) : AndroidResource(id, sourceElement) {
|
||||||
override fun sameClass(other: AndroidResource) = other is Widget
|
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
|
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
|
get() = if (isErrorType) widget.xmlType else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AndroidResource.Widget.resolve(module: ModuleDescriptor): ResolvedWidget {
|
fun AndroidResource.Widget.resolve(module: ModuleDescriptor): ResolvedWidget? {
|
||||||
fun resolve(fqName: String) = module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(fqName)))
|
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) {
|
if ('.' in xmlType) {
|
||||||
return ResolvedWidget(this, resolve(xmlType))
|
return ResolvedWidget(this, resolve(xmlType))
|
||||||
|
|||||||
+4
-2
@@ -81,7 +81,7 @@ internal fun genPropertyForFragment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun genProperty(
|
private fun genProperty(
|
||||||
id: String,
|
id: ResourceIdentifier,
|
||||||
receiverType: KotlinType,
|
receiverType: KotlinType,
|
||||||
type: KotlinType,
|
type: KotlinType,
|
||||||
containingDeclaration: DeclarationDescriptor,
|
containingDeclaration: DeclarationDescriptor,
|
||||||
@@ -98,13 +98,14 @@ private fun genProperty(
|
|||||||
Modality.FINAL,
|
Modality.FINAL,
|
||||||
Visibilities.PUBLIC,
|
Visibilities.PUBLIC,
|
||||||
false,
|
false,
|
||||||
Name.identifier(id),
|
Name.identifier(id.name),
|
||||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||||
sourceElement,
|
sourceElement,
|
||||||
false,
|
false,
|
||||||
false) {
|
false) {
|
||||||
override val errorType = errorType
|
override val errorType = errorType
|
||||||
override val alwaysCastToView = alwaysCastToView
|
override val alwaysCastToView = alwaysCastToView
|
||||||
|
override val resourceId = id
|
||||||
}
|
}
|
||||||
|
|
||||||
val actualType = if (alwaysCastToView) context.viewType else type
|
val actualType = if (alwaysCastToView) context.viewType else type
|
||||||
@@ -139,6 +140,7 @@ interface AndroidSyntheticFunction
|
|||||||
interface AndroidSyntheticProperty {
|
interface AndroidSyntheticProperty {
|
||||||
val errorType: String?
|
val errorType: String?
|
||||||
val alwaysCastToView: Boolean
|
val alwaysCastToView: Boolean
|
||||||
|
val resourceId: ResourceIdentifier
|
||||||
|
|
||||||
val isErrorType: Boolean
|
val isErrorType: Boolean
|
||||||
get() = errorType != null
|
get() = errorType != null
|
||||||
|
|||||||
+1
-1
@@ -36,6 +36,6 @@ class AndroidSimpleNameReferenceExtension : SimpleNameReferenceExtension {
|
|||||||
if (resolvedElement !is XmlAttributeValue || !AndroidResourceUtil.isIdDeclaration(resolvedElement)) return null
|
if (resolvedElement !is XmlAttributeValue || !AndroidResourceUtil.isIdDeclaration(resolvedElement)) return null
|
||||||
val newSyntheticPropertyName = androidIdToName(newElementName) ?: return null
|
val newSyntheticPropertyName = androidIdToName(newElementName) ?: return null
|
||||||
|
|
||||||
return psiFactory.createNameIdentifier(newSyntheticPropertyName)
|
return psiFactory.createNameIdentifier(newSyntheticPropertyName.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -24,8 +24,9 @@ import com.intellij.psi.xml.XmlTag
|
|||||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||||
import org.jetbrains.kotlin.android.synthetic.androidIdToName
|
import org.jetbrains.kotlin.android.synthetic.androidIdToName
|
||||||
import org.jetbrains.kotlin.android.synthetic.isWidgetTypeIgnored
|
import org.jetbrains.kotlin.android.synthetic.isWidgetTypeIgnored
|
||||||
|
import org.jetbrains.kotlin.android.synthetic.res.ResourceIdentifier
|
||||||
|
|
||||||
class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> Unit) : XmlElementVisitor() {
|
class AndroidXmlVisitor(val elementCallback: (ResourceIdentifier, String, XmlAttribute) -> Unit) : XmlElementVisitor() {
|
||||||
|
|
||||||
override fun visitElement(element: PsiElement) {
|
override fun visitElement(element: PsiElement) {
|
||||||
element.acceptChildren(this)
|
element.acceptChildren(this)
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileM
|
|||||||
|
|
||||||
val attributes = arrayListOf<PsiElement>()
|
val attributes = arrayListOf<PsiElement>()
|
||||||
val visitor = AndroidXmlVisitor { retId, wClass, valueElement ->
|
val visitor = AndroidXmlVisitor { retId, wClass, valueElement ->
|
||||||
if (retId == propertyName) attributes.add(valueElement)
|
if (retId.name == propertyName) attributes.add(valueElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutFiles.forEach { it.accept(visitor) }
|
layoutFiles.forEach { it.accept(visitor) }
|
||||||
|
|||||||
Reference in New Issue
Block a user