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
|
||||
|
||||
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 {
|
||||
|
||||
+2
-1
@@ -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()
|
||||
|
||||
+7
-1
@@ -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 {
|
||||
|
||||
+4
-2
@@ -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) {
|
||||
|
||||
+7
-8
@@ -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()
|
||||
|
||||
+30
-5
@@ -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))
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user