blob: bbe6e647012f4a9de3fe7b0c47ca211f9061773f [file] [log] [blame]
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.impl.compiled.ClsClassImpl
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
import org.jetbrains.kotlin.asJava.classes.*
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.caches.lightClasses.ClsJavaStubByVirtualFileCache
import org.jetbrains.kotlin.idea.caches.lightClasses.KtLightClassForDecompiledDeclaration
import org.jetbrains.kotlin.idea.caches.lightClasses.platformMutabilityWrapper
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
import org.jetbrains.kotlin.idea.decompiler.navigation.SourceNavigationHelper
import org.jetbrains.kotlin.idea.stubindex.*
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.utils.sure
import java.util.*
class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport() {
private val psiManager: PsiManager = PsiManager.getInstance(project)
override fun getFacadeNames(packageFqName: FqName, scope: GlobalSearchScope): Collection<String> {
val facadeFilesInPackage = runReadAction {
KotlinFileFacadeClassByPackageIndex.getInstance()
.get(packageFqName.asString(), project, scope)
}
return facadeFilesInPackage.map { it.javaFileFacadeFqName.shortName().asString() }
}
override fun getFacadeClassesInPackage(packageFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
val facadeFilesInPackage = runReadAction {
KotlinFileFacadeClassByPackageIndex.getInstance()
.get(packageFqName.asString(), project, scope)
}
val groupedByFqNameAndModuleInfo = facadeFilesInPackage.groupBy {
Pair(it.javaFileFacadeFqName, it.getModuleInfo())
}
return groupedByFqNameAndModuleInfo.flatMap {
val (key, files) = it
val (fqName, moduleInfo) = key
createLightClassForFileFacade(fqName, files, moduleInfo)
}
}
override fun findClassOrObjectDeclarations(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtClassOrObject> {
return runReadAction {
KotlinFullClassNameIndex.getInstance().get(
fqName.asString(),
project,
KotlinSourceFilterScope.sourceAndClassFiles(searchScope, project)
)
}
}
override fun findFilesForPackage(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtFile> {
return runReadAction {
PackageIndexUtil.findFilesWithExactPackage(
fqName,
KotlinSourceFilterScope.sourceAndClassFiles(
searchScope,
project
),
project
)
}
}
override fun findClassOrObjectDeclarationsInPackage(
packageFqName: FqName,
searchScope: GlobalSearchScope
): Collection<KtClassOrObject> {
return KotlinTopLevelClassByPackageIndex.getInstance().get(
packageFqName.asString(), project,
KotlinSourceFilterScope.sourceAndClassFiles(searchScope, project)
)
}
override fun packageExists(fqName: FqName, scope: GlobalSearchScope): Boolean {
return PackageIndexUtil.packageExists(
fqName,
KotlinSourceFilterScope.sourceAndClassFiles(
scope,
project
),
project
)
}
override fun getSubPackages(fqn: FqName, scope: GlobalSearchScope): Collection<FqName> {
return PackageIndexUtil.getSubPackageFqNames(
fqn,
KotlinSourceFilterScope.sourceAndClassFiles(
scope,
project
),
project,
MemberScope.ALL_NAME_FILTER
)
}
override fun getLightClass(classOrObject: KtClassOrObject): KtLightClass? {
val virtualFile = classOrObject.containingFile.virtualFile
if (virtualFile != null) {
when {
ProjectRootsUtil.isProjectSourceFile(project, virtualFile) ->
return KtLightClassForSourceDeclaration.create(classOrObject)
ProjectRootsUtil.isLibraryClassFile(project, virtualFile) ->
return getLightClassForDecompiledClassOrObject(classOrObject)
ProjectRootsUtil.isLibrarySourceFile(project, virtualFile) ->
return SourceNavigationHelper.getOriginalClass(classOrObject) as? KtLightClass
}
}
if ((classOrObject.containingFile as? KtFile)?.analysisContext != null ||
classOrObject.containingFile.originalFile.virtualFile != null
) {
// explicit request to create light class from dummy.kt
return KtLightClassForSourceDeclaration.create(classOrObject)
}
return null
}
override fun getLightClassForScript(script: KtScript): KtLightClassForScript? =
KtLightClassForScript.create(script)
private fun withFakeLightClasses(
lightClassForFacade: KtLightClassForFacade?,
facadeFiles: List<KtFile>
): List<PsiClass> {
if (lightClassForFacade == null) return emptyList()
val lightClasses = ArrayList<PsiClass>()
lightClasses.add(lightClassForFacade)
if (facadeFiles.size > 1) {
lightClasses.addAll(facadeFiles.map {
FakeLightClassForFileOfPackage(lightClassForFacade, it)
})
}
return lightClasses
}
override fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
val filesByModule = findFilesForFacade(facadeFqName, scope).groupBy(KtFile::getModuleInfo)
return filesByModule.flatMap {
createLightClassForFileFacade(facadeFqName, it.value, it.key)
}
}
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
return KotlinScriptFqnIndex.instance.get(scriptFqName.asString(), project, scope).mapNotNull {
getLightClassForScript(it)
}
}
override fun getKotlinInternalClasses(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
if (fqName.isRoot) return emptyList()
return findPackageParts(fqName, scope) + findPlatformWrapper(fqName, scope)
}
private fun findPackageParts(fqName: FqName, scope: GlobalSearchScope): List<KtLightClassForDecompiledDeclaration> {
val facadeKtFiles = StaticFacadeIndexUtil.getMultifileClassForPart(fqName, scope, project)
val partShortName = fqName.shortName().asString()
val partClassFileShortName = partShortName + ".class"
return facadeKtFiles.mapNotNull { facadeKtFile ->
if (facadeKtFile is KtClsFile) {
val partClassFile = facadeKtFile.virtualFile.parent.findChild(partClassFileShortName) ?: return@mapNotNull null
val javaClsClass = createClsJavaClassFromVirtualFile(facadeKtFile, partClassFile, null) ?: return@mapNotNull null
KtLightClassForDecompiledDeclaration(javaClsClass, null, facadeKtFile)
} else {
// TODO should we build light classes for parts from source?
null
}
}
}
private fun findPlatformWrapper(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
return platformMutabilityWrapper(fqName) {
JavaPsiFacade.getInstance(
project
).findClass(it, scope)
}?.let { listOf(it) }.orEmpty()
}
fun createLightClassForFileFacade(
facadeFqName: FqName,
facadeFiles: List<KtFile>,
moduleInfo: IdeaModuleInfo
): List<PsiClass> {
val (clsFiles, sourceFiles) = facadeFiles.partition { it is KtClsFile }
val lightClassesForClsFacades = clsFiles.mapNotNull { createLightClassForDecompiledKotlinFile(it as KtClsFile) }
if (moduleInfo is ModuleSourceInfo && sourceFiles.isNotEmpty()) {
val lightClassForFacade = KtLightClassForFacade.createForFacade(
psiManager, facadeFqName, moduleInfo.contentScope(), sourceFiles
)
return withFakeLightClasses(lightClassForFacade, sourceFiles) + lightClassesForClsFacades
} else {
return lightClassesForClsFacades
}
}
override fun findFilesForFacade(facadeFqName: FqName, scope: GlobalSearchScope): Collection<KtFile> {
return runReadAction {
KotlinFileFacadeFqNameIndex.INSTANCE.get(facadeFqName.asString(), project, scope)
}
}
private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: KtClassOrObject): KtLightClassForDecompiledDeclaration? {
if (decompiledClassOrObject is KtEnumEntry) {
return null
}
val containingKtFile = decompiledClassOrObject.containingFile as? KtClsFile ?: return null
val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingKtFile) ?: return null
return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile)
}
private fun findCorrespondingLightClass(
decompiledClassOrObject: KtClassOrObject,
rootLightClassForDecompiledFile: KtLightClassForDecompiledDeclaration
): KtLightClassForDecompiledDeclaration {
val relativeFqName = getClassRelativeName(decompiledClassOrObject)
val iterator = relativeFqName.pathSegments().iterator()
val base = iterator.next()
assert(rootLightClassForDecompiledFile.name == base.asString()) { "Light class for file:\n" + decompiledClassOrObject.containingKtFile.virtualFile.canonicalPath + "\nwas expected to have name: " + base.asString() + "\n Actual: " + rootLightClassForDecompiledFile.name }
var current: KtLightClassForDecompiledDeclaration = rootLightClassForDecompiledFile
while (iterator.hasNext()) {
val name = iterator.next()
val innerClass = current.findInnerClassByName(name.asString(), false).sure {
"Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\n" + "File: " + decompiledClassOrObject.containingKtFile.virtualFile.name
}
current = innerClass as KtLightClassForDecompiledDeclaration
}
return current
}
private fun getClassRelativeName(decompiledClassOrObject: KtClassOrObject): FqName {
val name = decompiledClassOrObject.nameAsName!!
val parent = PsiTreeUtil.getParentOfType(
decompiledClassOrObject,
KtClassOrObject::class.java,
true
)
if (parent == null) {
assert(decompiledClassOrObject.isTopLevel())
return FqName.topLevel(name)
}
return getClassRelativeName(parent).child(name)
}
private fun createLightClassForDecompiledKotlinFile(file: KtClsFile): KtLightClassForDecompiledDeclaration? {
val virtualFile = file.virtualFile ?: return null
val classOrObject = file.declarations.filterIsInstance<KtClassOrObject>().singleOrNull()
val javaClsClass = createClsJavaClassFromVirtualFile(
file, virtualFile,
correspondingClassOrObject = classOrObject
) ?: return null
return KtLightClassForDecompiledDeclaration(javaClsClass, classOrObject, file)
}
private fun createClsJavaClassFromVirtualFile(
mirrorFile: KtFile,
classFile: VirtualFile,
correspondingClassOrObject: KtClassOrObject?
): ClsClassImpl? {
val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val manager = PsiManager.getInstance(mirrorFile.project)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) {
override fun getNavigationElement(): PsiElement {
if (correspondingClassOrObject != null) {
return correspondingClassOrObject.navigationElement.containingFile
}
return super.getNavigationElement()
}
override fun getStub() = javaFileStub
override fun getMirror() = mirrorFile
override fun isPhysical() = false
}
javaFileStub.psi = fakeFile
return fakeFile.classes.single() as ClsClassImpl
}
}