Resolve Android widget class fq names properly (KT-8790)
This commit is contained in:
+6
-6
@@ -21,6 +21,10 @@ import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOption
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
|
||||
@@ -29,10 +33,6 @@ import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
|
||||
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
|
||||
public object AndroidConfigurationKeys {
|
||||
@@ -70,8 +70,8 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
|
||||
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
|
||||
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
|
||||
val parser = ServiceManager.getService(project, javaClass<SyntheticFileGenerator>())
|
||||
return parser.getSyntheticFiles() ?: listOf()
|
||||
val parser = ServiceManager.getService(project, javaClass<SyntheticFileGenerator>()) as? CliSyntheticFileGenerator
|
||||
return parser?.getSyntheticFiles() ?: listOf()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -55,6 +55,8 @@ public object AndroidConst {
|
||||
|
||||
val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes())
|
||||
.map { it as? JetKeywordToken }.filterNotNull().map { it.getValue() }.toSet()
|
||||
|
||||
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
|
||||
}
|
||||
|
||||
public fun nameToIdDeclaration(name: String): String = AndroidConst.ID_DECLARATION_PREFIX + name
|
||||
@@ -74,10 +76,13 @@ fun escapeAndroidIdentifier(id: String): String {
|
||||
return if (id in AndroidConst.ESCAPED_IDENTIFIERS) "`$id`" else id
|
||||
}
|
||||
|
||||
public fun parseAndroidResource(id: String, type: String): AndroidResource {
|
||||
return when (type) {
|
||||
public fun parseAndroidResource(id: String, tag: String, fqNameResolver: (String) -> String?): AndroidResource {
|
||||
return when (tag) {
|
||||
"fragment" -> AndroidFragment(id)
|
||||
"include" -> AndroidWidget(id, "View")
|
||||
else -> AndroidWidget(id, type)
|
||||
"include" -> AndroidWidget(id, AndroidConst.VIEW_FQNAME)
|
||||
else -> {
|
||||
val fqName = fqNameResolver(tag) ?: AndroidConst.VIEW_FQNAME
|
||||
AndroidWidget(id, fqName)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.codegen
|
||||
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.FunctionCodegen
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
@@ -23,7 +24,6 @@ import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
|
||||
+30
-18
@@ -16,23 +16,31 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.res
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.CachedValue
|
||||
import com.intellij.psi.util.CachedValueProvider.Result
|
||||
import java.io.ByteArrayInputStream
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
|
||||
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
public class CliSyntheticFileGenerator(
|
||||
public open class CliSyntheticFileGenerator(
|
||||
project: Project,
|
||||
private val manifestPath: String,
|
||||
private val resDirectories: List<String>,
|
||||
private val supportV4: Boolean
|
||||
) : SyntheticFileGenerator(project) {
|
||||
|
||||
private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(project) }
|
||||
private val projectScope: GlobalSearchScope by lazy { GlobalSearchScope.allScope(project) }
|
||||
|
||||
private val cachedJetFiles by lazy {
|
||||
generateSyntheticJetFiles(generateSyntheticFiles(true, projectScope))
|
||||
}
|
||||
|
||||
override fun supportV4(): Boolean {
|
||||
return supportV4
|
||||
}
|
||||
@@ -41,27 +49,31 @@ public class CliSyntheticFileGenerator(
|
||||
CliAndroidLayoutXmlFileManager(project, manifestPath, resDirectories)
|
||||
}
|
||||
|
||||
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy {
|
||||
cachedValue {
|
||||
Result.create(generateSyntheticFiles(), ModificationTracker.NEVER_CHANGED)
|
||||
}
|
||||
}
|
||||
public override fun getSyntheticFiles(): List<JetFile> = cachedJetFiles
|
||||
|
||||
override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> {
|
||||
override fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource> {
|
||||
val resources = arrayListOf<AndroidResource>()
|
||||
val handler = AndroidXmlHandler { id, widgetType -> resources.add(parseAndroidResource(id, widgetType)) }
|
||||
|
||||
try {
|
||||
for (file in files) {
|
||||
val handler = AndroidXmlHandler { id, tag ->
|
||||
resources += parseAndroidResource(id, tag) { tag ->
|
||||
resolveFqClassNameForView(javaPsiFacade, scope, tag)
|
||||
}
|
||||
}
|
||||
|
||||
for (file in files) {
|
||||
try {
|
||||
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
|
||||
layoutXmlFileManager.saxParser.parse(inputStream, handler)
|
||||
} catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
}
|
||||
return filterDuplicates(resources)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
return listOf()
|
||||
}
|
||||
|
||||
return filterDuplicates(resources)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val LOG: Logger = Logger.getInstance(javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+60
-48
@@ -16,10 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.res
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.CachedValue
|
||||
import com.intellij.psi.util.CachedValueProvider.Result
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
@@ -36,56 +37,20 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
|
||||
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
|
||||
|
||||
protected val LOG: Logger = Logger.getInstance(javaClass)
|
||||
|
||||
public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager
|
||||
|
||||
protected abstract val cachedSources: CachedValue<List<AndroidSyntheticFile>>
|
||||
|
||||
private val cachedJetFiles: CachedValue<List<JetFile>> by lazy {
|
||||
cachedValue {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val applicationPackage = layoutXmlFileManager.androidModuleInfo?.applicationPackage
|
||||
|
||||
val jetFiles = cachedSources.value.mapIndexed { index, syntheticFile ->
|
||||
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
|
||||
val virtualFile = LightVirtualFile(fileName, syntheticFile.contents)
|
||||
val jetFile = psiManager.findFile(virtualFile) as JetFile
|
||||
if (applicationPackage != null) {
|
||||
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
|
||||
}
|
||||
jetFile
|
||||
}
|
||||
|
||||
Result.create(jetFiles, cachedSources)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun supportV4(): Boolean
|
||||
|
||||
public fun generateSyntheticFiles(generateCommonFiles: Boolean = true): List<AndroidSyntheticFile> {
|
||||
val commonFiles = if (generateCommonFiles) {
|
||||
val renderSyntheticFile = renderSyntheticFile("clearCache") {
|
||||
writePackage(AndroidConst.SYNTHETIC_PACKAGE)
|
||||
writeAndroidImports()
|
||||
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
|
||||
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
|
||||
if (supportV4()) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
|
||||
}
|
||||
val clearCacheFile = renderSyntheticFile
|
||||
public abstract fun getSyntheticFiles(): List<JetFile>
|
||||
|
||||
listOf(clearCacheFile,
|
||||
FLEXIBLE_TYPE_FILE,
|
||||
FAKE_SUPPORT_V4_APP_FILE,
|
||||
FAKE_SUPPORT_V4_VIEW_FILE,
|
||||
FAKE_SUPPORT_V4_WIDGET_FILE)
|
||||
} else listOf()
|
||||
protected fun generateSyntheticFiles(generateCommonFiles: Boolean = true, scope: GlobalSearchScope): List<AndroidSyntheticFile> {
|
||||
val commonFiles = if (generateCommonFiles) generateCommonFiles() else listOf()
|
||||
|
||||
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry ->
|
||||
val files = entry.getValue()
|
||||
val resources = extractLayoutResources(files)
|
||||
val resources = extractLayoutResources(files, scope)
|
||||
|
||||
val layoutName = files[0].name.substringBefore('.')
|
||||
val layoutName = entry.getKey()
|
||||
|
||||
val mainLayoutFile = renderMainLayoutFile(layoutName, resources)
|
||||
val viewLayoutFile = renderViewLayoutFile(layoutName, resources)
|
||||
@@ -94,9 +59,25 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
}.filterNotNull() + commonFiles
|
||||
}
|
||||
|
||||
public fun getSyntheticFiles(): List<JetFile>? = cachedJetFiles.value
|
||||
private fun generateCommonFiles(): List<AndroidSyntheticFile> {
|
||||
val renderSyntheticFile = renderSyntheticFile("clearCache") {
|
||||
writePackage(AndroidConst.SYNTHETIC_PACKAGE)
|
||||
writeAndroidImports()
|
||||
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
|
||||
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
|
||||
if (supportV4()) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
|
||||
}
|
||||
val clearCacheFile = renderSyntheticFile
|
||||
|
||||
protected abstract fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource>
|
||||
return listOf(
|
||||
clearCacheFile,
|
||||
FLEXIBLE_TYPE_FILE,
|
||||
FAKE_SUPPORT_V4_APP_FILE,
|
||||
FAKE_SUPPORT_V4_VIEW_FILE,
|
||||
FAKE_SUPPORT_V4_WIDGET_FILE)
|
||||
}
|
||||
|
||||
protected abstract fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource>
|
||||
|
||||
private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
|
||||
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX,
|
||||
@@ -140,13 +121,13 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
writeEmptyLine()
|
||||
}
|
||||
|
||||
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidResource, stubCall: String) {
|
||||
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, resource: AndroidResource, stubCall: String) {
|
||||
// extract startsWith() to fun
|
||||
val className = if (isFromSupportV4Package(receiver)) widget.supportClassName else widget.className
|
||||
val cast = if (widget.className != "View") " as? $className" else ""
|
||||
val className = if (isFromSupportV4Package(receiver)) resource.supportClassName else resource.className
|
||||
val cast = if (resource.className != "View") " as? $className" else ""
|
||||
val body = arrayListOf("return $stubCall$cast")
|
||||
writeImmutableExtensionProperty(receiver,
|
||||
name = widget.id,
|
||||
name = resource.id,
|
||||
retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>",
|
||||
getterBody = body)
|
||||
}
|
||||
@@ -163,6 +144,22 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)
|
||||
}
|
||||
|
||||
protected fun resolveFqClassNameForView(javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope, tag: String): String? {
|
||||
if (tag.contains('.')) {
|
||||
if (javaPsiFacade.findClass(tag, scope) == null) {
|
||||
return null
|
||||
}
|
||||
return tag
|
||||
}
|
||||
for (pkg in AndroidConst.FQNAME_RESOLVE_PACKAGES) {
|
||||
val fqName = "$pkg.$tag"
|
||||
if (javaPsiFacade.findClass(fqName, scope) != null) {
|
||||
return fqName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
|
||||
val resourceMap = linkedMapOf<String, AndroidResource>()
|
||||
val resourcesToExclude = hashSetOf<String>()
|
||||
@@ -184,6 +181,21 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
return resourceMap.values().toList()
|
||||
}
|
||||
|
||||
protected fun generateSyntheticJetFiles(files: List<AndroidSyntheticFile>): List<JetFile> {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val applicationPackage = layoutXmlFileManager.androidModuleInfo?.applicationPackage
|
||||
|
||||
return files.mapIndexed { index, syntheticFile ->
|
||||
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
|
||||
val virtualFile = LightVirtualFile(fileName, syntheticFile.contents)
|
||||
val jetFile = psiManager.findFile(virtualFile) as JetFile
|
||||
if (applicationPackage != null) {
|
||||
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
|
||||
}
|
||||
jetFile
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
|
||||
private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
|
||||
|
||||
Reference in New Issue
Block a user