blob: 272fd883c942834e4332de6aa9529e37c7738600 [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.project
import com.intellij.facet.FacetTypeRegistry
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.impl.scopes.LibraryScopeBase
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.ModuleOrderEntryImpl
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.PathUtil
import com.intellij.util.SmartList
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
import org.jetbrains.kotlin.caches.project.LibraryModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
import org.jetbrains.kotlin.idea.configuration.getBuildSystemType
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.facet.KotlinFacetType.Companion.ID
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
import org.jetbrains.kotlin.idea.util.rootManager
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.GlobalSearchScopeWithModuleSources
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
internal val LOG = Logger.getInstance(IdeaModuleInfo::class.java)
@Suppress("DEPRECATION_ERROR")
interface IdeaModuleInfo : org.jetbrains.kotlin.idea.caches.resolve.IdeaModuleInfo {
fun contentScope(): GlobalSearchScope
val moduleOrigin: ModuleOrigin
override val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>
get() = super.capabilities + mapOf(OriginCapability to moduleOrigin)
override fun dependencies(): List<IdeaModuleInfo>
}
private fun orderEntryToModuleInfo(project: Project, orderEntry: OrderEntry, forProduction: Boolean): List<IdeaModuleInfo> {
fun Module.toInfos() = correspondingModuleInfos().filter { !forProduction || it is ModuleProductionSourceInfo }
if (!orderEntry.isValid) return emptyList()
return when (orderEntry) {
is ModuleSourceOrderEntry -> {
orderEntry.getOwnerModule().toInfos()
}
is ModuleOrderEntry -> {
val module = orderEntry.module ?: return emptyList()
if (forProduction && orderEntry is ModuleOrderEntryImpl && orderEntry.isProductionOnTestDependency) {
listOfNotNull(module.testSourceInfo())
} else {
module.toInfos()
}
}
is LibraryOrderEntry -> {
val library = orderEntry.library ?: return listOf()
listOfNotNull(LibraryInfo(project, library))
}
is JdkOrderEntry -> {
val sdk = orderEntry.jdk ?: return listOf()
listOfNotNull(SdkInfo(project, sdk))
}
else -> {
throw IllegalStateException("Unexpected order entry $orderEntry")
}
}
}
fun <T> Module.cached(provider: CachedValueProvider<T>): T {
return CachedValuesManager.getManager(project).getCachedValue(this, provider)
}
private fun OrderEntry.acceptAsDependency(forProduction: Boolean): Boolean {
return this !is ExportableOrderEntry
|| !forProduction
// this is needed for Maven/Gradle projects with "production-on-test" dependency
|| this is ModuleOrderEntryImpl && isProductionOnTestDependency
|| scope.isForProductionCompile
}
private fun ideaModelDependencies(module: Module, forProduction: Boolean): List<IdeaModuleInfo> {
//NOTE: lib dependencies can be processed several times during recursive traversal
val result = LinkedHashSet<IdeaModuleInfo>()
val dependencyEnumerator = ModuleRootManager.getInstance(module).orderEntries().compileOnly().recursively().exportedOnly()
if (forProduction && module.getBuildSystemType() == BuildSystemType.JPS) {
dependencyEnumerator.productionOnly()
}
dependencyEnumerator.forEach { orderEntry ->
if (orderEntry.acceptAsDependency(forProduction)) {
result.addAll(orderEntryToModuleInfo(module.project, orderEntry!!, forProduction))
}
true
}
return result.toList()
}
fun Module.findImplementedModuleNames(modelsProvider: IdeModifiableModelsProvider): List<String> {
val facetModel = modelsProvider.getModifiableFacetModel(this)
val facet = facetModel.findFacet(
KotlinFacetType.TYPE_ID,
FacetTypeRegistry.getInstance().findFacetType(ID)!!.defaultFacetName
)
return facet?.configuration?.settings?.implementedModuleNames ?: emptyList()
}
fun Module.findImplementedModules(modelsProvider: IdeModifiableModelsProvider) =
findImplementedModuleNames(modelsProvider).mapNotNull { modelsProvider.findIdeModule(it) }
interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
val module: Module
override val expectedBy: List<ModuleSourceInfo>
override val displayedName get() = module.name
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.MODULE
override val platform: TargetPlatform
get() = TargetPlatformDetector.getPlatform(module)
override fun createModificationTracker(): ModificationTracker =
KotlinModuleModificationTracker(module)
}
sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo {
override val expectedBy: List<ModuleSourceInfo>
get() {
val modelsProvider = IdeModifiableModelsProviderImpl(module.project)
val expectedByModules = module.findImplementedModules(modelsProvider)
return expectedByModules.mapNotNull { if (forProduction) it.productionSourceInfo() else it.testSourceInfo() }
}
override fun dependencies(): List<IdeaModuleInfo> = module.cached(createCachedValueProvider {
CachedValueProvider.Result(
ideaModelDependencies(module, forProduction),
ProjectRootModificationTracker.getInstance(module.project)
)
})
// NB: CachedValueProvider must exist separately in Production / Test source info,
// otherwise caching does not work properly
protected abstract fun <T> createCachedValueProvider(f: () -> CachedValueProvider.Result<T>): CachedValueProvider<T>
}
data class ModuleProductionSourceInfo internal constructor(
override val module: Module
) : ModuleSourceInfoWithExpectedBy(forProduction = true) {
override val name = Name.special("<production sources for module ${module.name}>")
override fun contentScope(): GlobalSearchScope = ModuleProductionSourceScope(module)
override fun <T> createCachedValueProvider(f: () -> CachedValueProvider.Result<T>) = CachedValueProvider { f() }
}
//TODO: (module refactoring) do not create ModuleTestSourceInfo when there are no test roots for module
@Suppress("DEPRECATION_ERROR")
data class ModuleTestSourceInfo internal constructor(override val module: Module) :
ModuleSourceInfoWithExpectedBy(forProduction = false), org.jetbrains.kotlin.idea.caches.resolve.ModuleTestSourceInfo {
override val name = Name.special("<test sources for module ${module.name}>")
override val displayedName get() = module.name + " (test)"
override fun contentScope(): GlobalSearchScope = ModuleTestSourceScope(module)
override fun modulesWhoseInternalsAreVisible() = module.cached(CachedValueProvider {
val list = SmartList<ModuleInfo>()
list.addIfNotNull(module.productionSourceInfo())
TestModuleProperties.getInstance(module).productionModule?.let {
list.addIfNotNull(it.productionSourceInfo())
}
CachedValueProvider.Result(list, ProjectRootModificationTracker.getInstance(module.project))
})
override fun <T> createCachedValueProvider(f: () -> CachedValueProvider.Result<T>) = CachedValueProvider { f() }
}
internal fun ModuleSourceInfo.isTests() = this is ModuleTestSourceInfo
fun Module.productionSourceInfo(): ModuleProductionSourceInfo? = if (hasProductionRoots()) ModuleProductionSourceInfo(this) else null
fun Module.testSourceInfo(): ModuleTestSourceInfo? = if (hasTestRoots()) ModuleTestSourceInfo(this) else null
internal fun Module.correspondingModuleInfos(): List<ModuleSourceInfo> = listOf(testSourceInfo(), productionSourceInfo()).filterNotNull()
private fun Module.hasProductionRoots() = hasRootsOfType(JavaSourceRootType.SOURCE)
private fun Module.hasTestRoots() = hasRootsOfType(JavaSourceRootType.TEST_SOURCE)
private fun Module.hasRootsOfType(sourceRootType: JavaSourceRootType): Boolean =
rootManager.contentEntries.any { it.getSourceFolders(sourceRootType).isNotEmpty() }
private abstract class ModuleSourceScope(val module: Module) : GlobalSearchScope(module.project), GlobalSearchScopeWithModuleSources {
override fun compare(file1: VirtualFile, file2: VirtualFile) = 0
override fun isSearchInModuleContent(aModule: Module) = aModule == module
override fun isSearchInLibraries() = false
}
private class ModuleProductionSourceScope(module: Module) : ModuleSourceScope(module) {
val moduleFileIndex = ModuleRootManager.getInstance(module).fileIndex
override fun equals(other: Any?): Boolean {
if (this === other) return true
return (other is ModuleProductionSourceScope && module == other.module)
}
// KT-6206
override fun hashCode(): Int = 31 * module.hashCode()
override fun contains(file: VirtualFile) =
moduleFileIndex.isInSourceContentWithoutInjected(file) && !moduleFileIndex.isInTestSourceContent(file)
override fun toString() = "ModuleProductionSourceScope($module)"
}
private class ModuleTestSourceScope(module: Module) : ModuleSourceScope(module) {
val moduleFileIndex = ModuleRootManager.getInstance(module).fileIndex
override fun equals(other: Any?): Boolean {
if (this === other) return true
return (other is ModuleTestSourceScope && module == other.module)
}
// KT-6206
override fun hashCode(): Int = 37 * module.hashCode()
override fun contains(file: VirtualFile) = moduleFileIndex.isInTestSourceContent(file)
override fun toString() = "ModuleTestSourceScope($module)"
}
class LibraryInfo(val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.LIBRARY
override val name: Name = Name.special("<library ${library.name}>")
override fun contentScope(): GlobalSearchScope = LibraryWithoutSourceScope(project, library)
override fun dependencies(): List<IdeaModuleInfo> {
val result = LinkedHashSet<IdeaModuleInfo>()
result.add(this)
val (libraries, sdks) = LibraryDependenciesCache.getInstance(project).getLibrariesAndSdksUsedWith(library)
sdks.mapTo(result) { SdkInfo(project, it) }
libraries.filter { it is LibraryEx && !it.isDisposed }.mapTo(result) {
LibraryInfo(
project,
it
)
}
return result.toList()
}
override val platform: TargetPlatform
get() = getLibraryPlatform(library)
override val sourcesModuleInfo: SourceForBinaryModuleInfo
get() = LibrarySourceInfo(project, library)
override fun getLibraryRoots(): Collection<String> =
library.getFiles(OrderRootType.CLASSES).mapNotNull(PathUtil::getLocalPath)
override fun toString() = "LibraryInfo(libraryName=${library.name})"
override fun equals(other: Any?): Boolean {
if (this === other) return true
return (other is LibraryInfo && library == other.library)
}
override fun hashCode(): Int = 43 * library.hashCode()
}
data class LibrarySourceInfo(val project: Project, val library: Library) : IdeaModuleInfo, SourceForBinaryModuleInfo {
override val name: Name = Name.special("<sources for library ${library.name}>")
override fun sourceScope(): GlobalSearchScope = KotlinSourceFilterScope.librarySources(
LibrarySourceScope(
project,
library
), project)
override fun modulesWhoseInternalsAreVisible(): Collection<ModuleInfo> {
return listOf(LibraryInfo(project, library))
}
override val binariesModuleInfo: BinaryModuleInfo
get() = LibraryInfo(project, library)
override fun toString() = "LibrarySourceInfo(libraryName=${library.name})"
}
//TODO: (module refactoring) there should be separate SdkSourceInfo but there are no kotlin source in existing sdks for now :)
data class SdkInfo(val project: Project, val sdk: Sdk) : IdeaModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.LIBRARY
override val name: Name = Name.special("<sdk ${sdk.name}>")
override fun contentScope(): GlobalSearchScope = SdkScope(project, sdk)
override fun dependencies(): List<IdeaModuleInfo> = listOf(this)
}
object NotUnderContentRootModuleInfo : IdeaModuleInfo {
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.OTHER
override val name: Name = Name.special("<special module for files not under source root>")
override fun contentScope() = GlobalSearchScope.EMPTY_SCOPE
//TODO: (module refactoring) dependency on runtime can be of use here
override fun dependencies(): List<IdeaModuleInfo> = listOf(this)
}
private class LibraryWithoutSourceScope(project: Project, private val library: Library) :
LibraryScopeBase(project, library.getFiles(OrderRootType.CLASSES), arrayOf<VirtualFile>()) {
override fun getFileRoot(file: VirtualFile): VirtualFile? = myIndex.getClassRootForFile(file)
override fun equals(other: Any?) = other is LibraryWithoutSourceScope && library == other.library
override fun hashCode() = library.hashCode()
override fun toString() = "LibraryWithoutSourceScope($library)"
}
private class LibrarySourceScope(project: Project, private val library: Library) :
LibraryScopeBase(project, arrayOf<VirtualFile>(), library.getFiles(OrderRootType.SOURCES)) {
override fun getFileRoot(file: VirtualFile): VirtualFile? = myIndex.getSourceRootForFile(file)
override fun equals(other: Any?) = other is LibrarySourceScope && library == other.library
override fun hashCode() = library.hashCode()
override fun toString() = "LibrarySourceScope($library)"
}
//TODO: (module refactoring) android sdk has modified scope
private class SdkScope(project: Project, private val sdk: Sdk) :
LibraryScopeBase(project, sdk.rootProvider.getFiles(OrderRootType.CLASSES), arrayOf<VirtualFile>()) {
override fun equals(other: Any?) = other is SdkScope && sdk == other.sdk
override fun hashCode() = sdk.hashCode()
override fun toString() = "SdkScope($sdk)"
}
internal fun IdeaModuleInfo.isLibraryClasses() = this is SdkInfo || this is LibraryInfo
val OriginCapability = ModuleDescriptor.Capability<ModuleOrigin>("MODULE_ORIGIN")
enum class ModuleOrigin {
MODULE,
LIBRARY,
OTHER
}
interface BinaryModuleInfo : IdeaModuleInfo {
val sourcesModuleInfo: SourceForBinaryModuleInfo?
fun binariesScope(): GlobalSearchScope {
val contentScope = contentScope()
return KotlinSourceFilterScope.libraryClassFiles(contentScope, contentScope.project!!)
}
}
interface SourceForBinaryModuleInfo : IdeaModuleInfo {
val binariesModuleInfo: BinaryModuleInfo
fun sourceScope(): GlobalSearchScope
// module infos for library source do not have contents in the following sense:
// we can not provide a collection of files that is supposed to be analyzed in IDE independently
//
// as of now each source file is analyzed separately and depends on corresponding binaries
// see KotlinCacheServiceImpl#createFacadeForSyntheticFiles
override fun contentScope(): GlobalSearchScope = GlobalSearchScope.EMPTY_SCOPE
override fun dependencies() = listOf(this) + binariesModuleInfo.dependencies()
override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.OTHER
}