Resolve Android widget class fq names properly (KT-8790)

This commit is contained in:
Yan Zhulanow
2015-08-20 15:11:01 +03:00
parent 8577f3e91b
commit e0bd532b7a
11 changed files with 171 additions and 123 deletions
@@ -21,6 +21,10 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension 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.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.CliOption import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException 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.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider 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 import org.jetbrains.kotlin.psi.JetFile
public object AndroidConfigurationKeys { public object AndroidConfigurationKeys {
@@ -70,8 +70,8 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider { public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> { override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
val parser = ServiceManager.getService(project, javaClass<SyntheticFileGenerator>()) val parser = ServiceManager.getService(project, javaClass<SyntheticFileGenerator>()) as? CliSyntheticFileGenerator
return parser.getSyntheticFiles() ?: listOf() return parser?.getSyntheticFiles() ?: listOf()
} }
} }
@@ -55,6 +55,8 @@ public object AndroidConst {
val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes()) val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes())
.map { it as? JetKeywordToken }.filterNotNull().map { it.getValue() }.toSet() .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 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 return if (id in AndroidConst.ESCAPED_IDENTIFIERS) "`$id`" else id
} }
public fun parseAndroidResource(id: String, type: String): AndroidResource { public fun parseAndroidResource(id: String, tag: String, fqNameResolver: (String) -> String?): AndroidResource {
return when (type) { return when (tag) {
"fragment" -> AndroidFragment(id) "fragment" -> AndroidFragment(id)
"include" -> AndroidWidget(id, "View") "include" -> AndroidWidget(id, AndroidConst.VIEW_FQNAME)
else -> AndroidWidget(id, type) else -> {
val fqName = fqNameResolver(tag) ?: AndroidConst.VIEW_FQNAME
AndroidWidget(id, fqName)
}
} }
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.android.synthetic.codegen 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.ClassBuilder
import org.jetbrains.kotlin.codegen.FunctionCodegen import org.jetbrains.kotlin.codegen.FunctionCodegen
import org.jetbrains.kotlin.codegen.StackValue 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.GenerationState
import org.jetbrains.kotlin.codegen.state.JetTypeMapper import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.* 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.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.psi.JetClassOrObject import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -16,23 +16,31 @@
package org.jetbrains.kotlin.android.synthetic.res package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project 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.PsiFile
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.psi.JetFile
import kotlin.properties.Delegates import kotlin.properties.Delegates
public class CliSyntheticFileGenerator( public open class CliSyntheticFileGenerator(
project: Project, project: Project,
private val manifestPath: String, private val manifestPath: String,
private val resDirectories: List<String>, private val resDirectories: List<String>,
private val supportV4: Boolean private val supportV4: Boolean
) : SyntheticFileGenerator(project) { ) : 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 { override fun supportV4(): Boolean {
return supportV4 return supportV4
} }
@@ -41,27 +49,31 @@ public class CliSyntheticFileGenerator(
CliAndroidLayoutXmlFileManager(project, manifestPath, resDirectories) CliAndroidLayoutXmlFileManager(project, manifestPath, resDirectories)
} }
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy { public override fun getSyntheticFiles(): List<JetFile> = cachedJetFiles
cachedValue {
Result.create(generateSyntheticFiles(), ModificationTracker.NEVER_CHANGED)
}
}
override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> { override fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource> {
val resources = arrayListOf<AndroidResource>() val resources = arrayListOf<AndroidResource>()
val handler = AndroidXmlHandler { id, widgetType -> resources.add(parseAndroidResource(id, widgetType)) }
try { val handler = AndroidXmlHandler { id, tag ->
for (file in files) { resources += parseAndroidResource(id, tag) { tag ->
resolveFqClassNameForView(javaPsiFacade, scope, tag)
}
}
for (file in files) {
try {
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray()) val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
layoutXmlFileManager.saxParser.parse(inputStream, handler) 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)
} }
} }
@@ -16,10 +16,11 @@
package org.jetbrains.kotlin.android.synthetic.res package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import com.intellij.psi.util.CachedValuesManager 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") public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
protected val LOG: Logger = Logger.getInstance(javaClass)
public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager 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 protected abstract fun supportV4(): Boolean
public fun generateSyntheticFiles(generateCommonFiles: Boolean = true): List<AndroidSyntheticFile> { public abstract fun getSyntheticFiles(): List<JetFile>
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
listOf(clearCacheFile, protected fun generateSyntheticFiles(generateCommonFiles: Boolean = true, scope: GlobalSearchScope): List<AndroidSyntheticFile> {
FLEXIBLE_TYPE_FILE, val commonFiles = if (generateCommonFiles) generateCommonFiles() else listOf()
FAKE_SUPPORT_V4_APP_FILE,
FAKE_SUPPORT_V4_VIEW_FILE,
FAKE_SUPPORT_V4_WIDGET_FILE)
} else listOf()
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry -> return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry ->
val files = entry.getValue() 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 mainLayoutFile = renderMainLayoutFile(layoutName, resources)
val viewLayoutFile = renderViewLayoutFile(layoutName, resources) val viewLayoutFile = renderViewLayoutFile(layoutName, resources)
@@ -94,9 +59,25 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
}.filterNotNull() + commonFiles }.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 { private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX,
@@ -140,13 +121,13 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
writeEmptyLine() 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 // extract startsWith() to fun
val className = if (isFromSupportV4Package(receiver)) widget.supportClassName else widget.className val className = if (isFromSupportV4Package(receiver)) resource.supportClassName else resource.className
val cast = if (widget.className != "View") " as? $className" else "" val cast = if (resource.className != "View") " as? $className" else ""
val body = arrayListOf("return $stubCall$cast") val body = arrayListOf("return $stubCall$cast")
writeImmutableExtensionProperty(receiver, writeImmutableExtensionProperty(receiver,
name = widget.id, name = resource.id,
retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>", retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>",
getterBody = body) getterBody = body)
} }
@@ -163,6 +144,22 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE) 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> { protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>() val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>() val resourcesToExclude = hashSetOf<String>()
@@ -184,6 +181,21 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
return resourceMap.values().toList() 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 { companion object {
private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString() private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString() private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
@@ -16,17 +16,18 @@
package org.jetbrains.kotlin.lang.resolve.android.test package org.jetbrains.kotlin.lang.resolve.android.test
import com.intellij.openapi.module.ModuleManager
import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import java.io.File
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import kotlin.test.* import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.TestJdkKind
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.fail
public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() { public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
@@ -35,10 +36,14 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
val jetCoreEnvironment = getEnvironment() val jetCoreEnvironment = getEnvironment()
val layoutPaths = getResPaths(path) val layoutPaths = getResPaths(path)
val parser = CliSyntheticFileGenerator(jetCoreEnvironment.project, path + "AndroidManifest.xml", layoutPaths) val supportV4 = testDirectory.name.startsWith("support")
parser.supportV4 = testDirectory.name.startsWith("support") val parser = CliSyntheticFileGeneratorForConversionTest(
jetCoreEnvironment.project, path + "AndroidManifest.xml", layoutPaths, supportV4)
val actual = parser.generateSyntheticFiles(false).toMap { it.name } val module = ModuleManager.getInstance(jetCoreEnvironment.project).modules.first()
val moduleScope = module.getModuleWithDependenciesAndLibrariesScope(false)
val actual = parser.gen(moduleScope).toMap { it.name }
val expectedLayoutFiles = testDirectory.listFiles { val expectedLayoutFiles = testDirectory.listFiles {
it.isFile() && it.name.endsWith(".kt") it.isFile() && it.name.endsWith(".kt")
@@ -16,20 +16,19 @@
package org.jetbrains.kotlin.lang.resolve.android.test package org.jetbrains.kotlin.lang.resolve.android.test
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.android.synthetic.AndroidConfigurationKeys
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.android.synthetic.AndroidConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.psi.JetFile
import java.io.File import java.io.File
private class AndroidTestExternalDeclarationsProvider( private class AndroidTestExternalDeclarationsProvider(
@@ -39,12 +38,20 @@ private class AndroidTestExternalDeclarationsProvider(
val supportV4: Boolean val supportV4: Boolean
) : ExternalDeclarationsProvider { ) : ExternalDeclarationsProvider {
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> { override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
val parser = CliSyntheticFileGenerator(project, manifestPath, resPaths) val parser = CliSyntheticFileGeneratorForConversionTest(project, manifestPath, resPaths, supportV4)
parser.supportV4 = supportV4 return parser.getSyntheticFiles()
return parser.getSyntheticFiles() ?: listOf()
} }
} }
class CliSyntheticFileGeneratorForConversionTest(
project: Project,
manifestPath: String,
resDirectories: List<String>,
supportV4: Boolean
) : CliSyntheticFileGenerator(project, manifestPath, resDirectories, supportV4) {
fun gen(scope: GlobalSearchScope) = generateSyntheticFiles(false, scope)
}
fun UsefulTestCase.createAndroidTestEnvironment( fun UsefulTestCase.createAndroidTestEnvironment(
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
resPaths: List<String>, resPaths: List<String>,
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.android.synthetic.idea
import com.intellij.openapi.module.ModuleServiceManager import com.intellij.openapi.module.ModuleServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.idea.res.IDESyntheticFileGenerator
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
@@ -30,8 +31,8 @@ public class IDEAndroidExternalDeclarationsProvider(private val project: Project
if (moduleInfo !is ModuleSourceInfo) return listOf() if (moduleInfo !is ModuleSourceInfo) return listOf()
val module = moduleInfo.module val module = moduleInfo.module
val parser = ModuleServiceManager.getService(module, javaClass<SyntheticFileGenerator>())!! val parser = ModuleServiceManager.getService(module, javaClass<SyntheticFileGenerator>()) as? IDESyntheticFileGenerator
val syntheticFiles = parser.getSyntheticFiles() val syntheticFiles = parser?.getSyntheticFiles()
syntheticFiles?.forEach { it.moduleInfo = moduleInfo } syntheticFiles?.forEach { it.moduleInfo = moduleInfo }
return syntheticFiles ?: listOf() return syntheticFiles ?: listOf()
@@ -21,11 +21,11 @@ import com.intellij.psi.PsiElement
import org.jetbrains.android.facet.AndroidFacet import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.android.synthetic.AndroidConst import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor
import org.jetbrains.kotlin.android.synthetic.res.AndroidModuleInfo
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.AndroidModuleInfo
import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.JetProperty
public class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.getProject()) { public class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.project) {
override val androidModuleInfo: AndroidModuleInfo? by lazy { module.androidFacet?.toAndroidModuleInfo() } override val androidModuleInfo: AndroidModuleInfo? by lazy { module.androidFacet?.toAndroidModuleInfo() }
@@ -37,7 +37,7 @@ public class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutX
val layoutFiles = getLayoutXmlFiles()[layoutPackageName] val layoutFiles = getLayoutXmlFiles()[layoutPackageName]
if (layoutFiles == null || layoutFiles.isEmpty()) return listOf() if (layoutFiles == null || layoutFiles.isEmpty()) return listOf()
val propertyName = property.getName() val propertyName = property.name
val attributes = arrayListOf<PsiElement>() val attributes = arrayListOf<PsiElement>()
val visitor = AndroidXmlVisitor { retId, wClass, valueElement -> val visitor = AndroidXmlVisitor { retId, wClass, valueElement ->
@@ -53,7 +53,7 @@ public class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutX
private fun AndroidFacet.toAndroidModuleInfo(): AndroidModuleInfo? { private fun AndroidFacet.toAndroidModuleInfo(): AndroidModuleInfo? {
val applicationPackage = manifest?.getPackage()?.toString() val applicationPackage = manifest?.getPackage()?.toString()
val mainResDirectories = getAllResourceDirectories().map { it.getPath() } val mainResDirectories = allResourceDirectories.map { it.path }
return if (applicationPackage != null) { return if (applicationPackage != null) {
AndroidModuleInfo(applicationPackage, mainResDirectories) AndroidModuleInfo(applicationPackage, mainResDirectories)
@@ -20,6 +20,7 @@ import com.intellij.openapi.module.Module
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import org.jetbrains.kotlin.android.synthetic.AndroidConst import org.jetbrains.kotlin.android.synthetic.AndroidConst
@@ -27,17 +28,24 @@ import org.jetbrains.kotlin.android.synthetic.idea.AndroidPsiTreeChangePreproces
import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
import org.jetbrains.kotlin.android.synthetic.res.AndroidResource import org.jetbrains.kotlin.android.synthetic.res.AndroidResource
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFile
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.psi.JetFile
class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(module.project) { class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(module.project) {
private val supportV4: Boolean private val supportV4: Boolean
private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(module.project) }
private val moduleScope: GlobalSearchScope by lazy { module.getModuleWithDependenciesAndLibrariesScope(false) }
private val cachedJetFiles: CachedValue<List<JetFile>> by lazy {
cachedValue {
Result.create(generateSyntheticJetFiles(generateSyntheticFiles(true, moduleScope)), psiTreeChangePreprocessor)
}
}
init { init {
val scope = module.getModuleWithDependenciesAndLibrariesScope(false) supportV4 = javaPsiFacade.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, moduleScope).isNotEmpty()
supportV4 = JavaPsiFacade.getInstance(module.getProject())
.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty()
} }
override fun supportV4() = supportV4 override fun supportV4() = supportV4
@@ -48,16 +56,14 @@ class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(mod
module.project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor } module.project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
} }
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy { public override fun getSyntheticFiles(): List<JetFile> = cachedJetFiles.value
cachedValue {
Result.create(generateSyntheticFiles(), psiTreeChangePreprocessor)
}
}
override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> { override fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource> {
val widgets = arrayListOf<AndroidResource>() val widgets = arrayListOf<AndroidResource>()
val visitor = AndroidXmlVisitor { id, widgetType, attribute -> val visitor = AndroidXmlVisitor { id, widgetType, attribute ->
widgets.add(parseAndroidResource(id, widgetType)) widgets += parseAndroidResource(id, widgetType) {
resolveFqClassNameForView(javaPsiFacade, scope, it)
}
} }
files.forEach { it.accept(visitor) } files.forEach { it.accept(visitor) }
@@ -16,10 +16,10 @@
package org.jetbrains.kotlin.android package org.jetbrains.kotlin.android
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleManager
import org.jetbrains.kotlin.android.synthetic.idea.TestConst import org.jetbrains.kotlin.android.synthetic.idea.TestConst
import org.jetbrains.kotlin.android.synthetic.idea.res.IDESyntheticFileGenerator import org.jetbrains.kotlin.android.synthetic.idea.res.IDESyntheticFileGenerator
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import kotlin.test.assertEquals import kotlin.test.assertEquals
public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() { public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() {
@@ -31,11 +31,11 @@ public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase()
"$path${it.name}/" "$path${it.name}/"
} }
val cliParser = CliSyntheticFileGenerator(project, path + "../AndroidManifest.xml", resDirs, false) val cliParser = CliSyntheticFileGenerator(project, "$path../AndroidManifest.xml", resDirs, false)
val ideParser = IDESyntheticFileGenerator(ModuleManager.getInstance(project).getModules()[0]) val ideParser = IDESyntheticFileGenerator(ModuleManager.getInstance(project).getModules()[0])
val cliResult = cliParser.getSyntheticFiles()!!.joinToString("\n\n") val cliResult = cliParser.getSyntheticFiles().joinToString("\n\n")
val ideResult = ideParser.getSyntheticFiles()!!.joinToString("\n\n") val ideResult = ideParser.getSyntheticFiles().joinToString("\n\n")
assertEquals(cliResult, ideResult) assertEquals(cliResult, ideResult)
} }