diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt index d3282ba0fb9..a96a3990e01 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JvmAnalyzerFacade.kt @@ -58,7 +58,8 @@ object JvmAnalyzerFacade : AnalyzerFacade() { val project = moduleContext.project val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( project, moduleContext.storageManager, syntheticFiles, - if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope + if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope, + moduleInfo ) val moduleClassResolver = ModuleClassResolverImpl { javaClass -> diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt index e53dbc81444..182bcff624e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt @@ -101,7 +101,8 @@ object DefaultAnalyzerFacade : AnalyzerFacade() { val project = moduleContext.project val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( project, moduleContext.storageManager, syntheticFiles, - if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope + if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope, + moduleInfo ) val trace = CodeAnalyzerInitializer.getInstance(project).createTrace() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/CliDeclarationProviderFactoryService.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/CliDeclarationProviderFactoryService.kt index fb8778ca3bc..ea7e80803a5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/CliDeclarationProviderFactoryService.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/CliDeclarationProviderFactoryService.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.lazy.declarations import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.utils.sure @@ -29,7 +30,8 @@ class CliDeclarationProviderFactoryService(private val sourceFiles: Collection, - filesScope: GlobalSearchScope + filesScope: GlobalSearchScope, + moduleInfo: ModuleInfo ): DeclarationProviderFactory { val allFiles = ArrayList() sourceFiles.filterTo(allFiles) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/DeclarationProviderFactoryService.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/DeclarationProviderFactoryService.kt index c81982b93cb..b6d6ee4f8b1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/DeclarationProviderFactoryService.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/declarations/DeclarationProviderFactoryService.kt @@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.storage.StorageManager import java.util.* @@ -31,7 +32,8 @@ abstract class DeclarationProviderFactoryService { project: Project, storageManager: StorageManager, syntheticFiles: Collection, - filesScope: GlobalSearchScope + filesScope: GlobalSearchScope, + moduleInfo: ModuleInfo ): DeclarationProviderFactory companion object { @@ -39,10 +41,11 @@ abstract class DeclarationProviderFactoryService { project: Project, storageManager: StorageManager, syntheticFiles: Collection, - filesScope: GlobalSearchScope + filesScope: GlobalSearchScope, + moduleInfo: ModuleInfo ): DeclarationProviderFactory { return ServiceManager.getService(project, DeclarationProviderFactoryService::class.java)!! - .create(project, storageManager, syntheticFiles, filteringScope(syntheticFiles, filesScope)) + .create(project, storageManager, syntheticFiles, filteringScope(syntheticFiles, filesScope), moduleInfo) } private fun filteringScope(syntheticFiles: Collection, baseScope: GlobalSearchScope): GlobalSearchScope { diff --git a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt index 24d1a1f72f3..837c7b366c7 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt @@ -51,7 +51,11 @@ object JsAnalyzerFacade : AnalyzerFacade() { val (syntheticFiles, moduleContentScope) = moduleContent val project = moduleContext.project val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( - project, moduleContext.storageManager, syntheticFiles, if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope + project, + moduleContext.storageManager, + syntheticFiles, + if (moduleInfo.isLibrary) GlobalSearchScope.EMPTY_SCOPE else moduleContentScope, + moduleInfo ) val languageVersionSettings = LanguageSettingsProvider.getInstance(project).getLanguageVersionSettings(moduleInfo, project) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt new file mode 100644 index 00000000000..6b6fea02e63 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.caches + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.Ref +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.BulkFileListener +import com.intellij.openapi.vfs.newvfs.events.* +import com.intellij.psi.impl.PsiTreeChangeEventImpl +import com.intellij.psi.impl.PsiTreeChangePreprocessor +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo +import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfoByVirtualFile +import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo +import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPackageDirective +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import java.util.concurrent.ConcurrentMap + +class KotlinPackageContentModificationListener( + private val project: Project +) { + init { + val connection = project.messageBus.connect() + + val scope = GlobalSearchScope.projectScope(project) + + connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { + override fun before(events: MutableList) = onEvents(events) { it is VFileDeleteEvent || it is VFileMoveEvent } + override fun after(events: List) = onEvents(events) { it is VFileMoveEvent || it is VFileCreateEvent || it is VFileCopyEvent } + + fun onEvents(events: List, filter: (VFileEvent) -> Boolean) = events.asSequence() + .filter(filter) + .map { it.file } + .filterNotNull() + .filter { it in scope } + .forEach { file -> + PerModulePackageCacheService.notifyPackageChange(file, project) + } + }) + } +} + +class KotlinPackageStatementPsiTreeChangePreprocessor : PsiTreeChangePreprocessor { + override fun treeChanged(event: PsiTreeChangeEventImpl) { + val file = event.file as? KtFile ?: return + + when (event.code) { + PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED, PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED, PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED, PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED -> { + val child = event.child ?: return + if (child.getParentOfType(false) != null) + PerModulePackageCacheService.notifyPackageChange(file) + } + else -> { + } + } + } +} + +class PerModulePackageCacheService { + + val cache = ContainerUtil.createConcurrentWeakMap>>() + + companion object { + + val PER_MODULE_PACKAGE_CACHE = Key.create>>>("per_module_package_cache") + + internal fun notifyPackageChange(file: KtFile): Unit { + (file.getNullableModuleInfo() as? ModuleSourceInfo)?.let { onChangeInModuleSource(it, file.project) } + } + + internal fun onChangeInModuleSource(moduleSourceInfo: ModuleSourceInfo, project: Project) = with(getInstance(project)) { + cache[moduleSourceInfo]?.set(null) + } + + internal fun notifyPackageChange(file: VirtualFile, project: Project): Unit { + (getModuleInfoByVirtualFile(project, file) as? ModuleSourceInfo)?.let { onChangeInModuleSource(it, project) } + } + + fun getInstance(project: Project): PerModulePackageCacheService = ServiceManager.getService(project, PerModulePackageCacheService::class.java) + + fun packageExists(packageFqName: FqName, moduleInfo: ModuleSourceInfo, project: Project): Boolean = with(getInstance(project)) { + val module = moduleInfo.module + + // Module own cache is a view on global cache. Since global cache based on WeakReferences when module + // gets disposed this soft map will be disposed too, leading to drop soft refs on ModuleInfo's, and then to + // disposing global cache entry + val moduleOwnCache = module.getUserData(PerModulePackageCacheService.PER_MODULE_PACKAGE_CACHE) ?: run { + ContainerUtil.createConcurrentSoftMap>>() + .apply { module.putUserData(PerModulePackageCacheService.PER_MODULE_PACKAGE_CACHE, this) } + } + val cached = moduleOwnCache.getOrPut(moduleInfo) { cache.getOrPut(moduleInfo) { Ref() } } + .apply { if (isNull) set(ContainerUtil.createConcurrentSoftMap()) } + .get() + + + return cached.getOrPut(packageFqName) { + PackageIndexUtil.packageExists(packageFqName, moduleInfo.contentScope(), project) + } + } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinSourceFilterScope.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinSourceFilterScope.kt index 4ed7bb72226..fbb00f3d577 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinSourceFilterScope.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinSourceFilterScope.kt @@ -53,6 +53,32 @@ class KotlinSourceFilterScope private constructor( "cls=$includeClassFiles script=$includeScriptDependencies)" } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + if (!super.equals(other)) return false + + other as KotlinSourceFilterScope + + if (includeProjectSourceFiles != other.includeProjectSourceFiles) return false + if (includeLibrarySourceFiles != other.includeLibrarySourceFiles) return false + if (includeClassFiles != other.includeClassFiles) return false + if (includeScriptDependencies != other.includeScriptDependencies) return false + if (project != other.project) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + includeProjectSourceFiles.hashCode() + result = 31 * result + includeLibrarySourceFiles.hashCode() + result = 31 * result + includeClassFiles.hashCode() + result = 31 * result + includeScriptDependencies.hashCode() + result = 31 * result + project.hashCode() + return result + } + companion object { @JvmStatic fun sourcesAndLibraries(delegate: GlobalSearchScope, project: Project) = create(delegate, true, true, true, true, project) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/PackageIndexUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/PackageIndexUtil.kt index e47b8bf0bef..ea6a02f0261 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/PackageIndexUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/PackageIndexUtil.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtFile + object PackageIndexUtil { @JvmStatic fun getSubPackageFqNames( packageFqName: FqName, @@ -48,6 +49,7 @@ object PackageIndexUtil { searchScope: GlobalSearchScope, project: Project ): Boolean { + val subpackagesIndex = SubpackagesIndexService.getInstance(project) if (!subpackagesIndex.packageExists(packageFqName)) { return false diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt index c0895d68d94..e64c5e68eb9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt @@ -18,6 +18,9 @@ package org.jetbrains.kotlin.idea.stubindex.resolve import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService +import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil import org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService import org.jetbrains.kotlin.name.FqName @@ -30,7 +33,8 @@ class PluginDeclarationProviderFactory( private val project: Project, private val indexedFilesScope: GlobalSearchScope, private val storageManager: StorageManager, - private val nonIndexedFiles: Collection + private val nonIndexedFiles: Collection, + private val moduleInfo: ModuleInfo ) : AbstractDeclarationProviderFactory(storageManager) { private val fileBasedDeclarationProviderFactory = FileBasedDeclarationProviderFactory(storageManager, nonIndexedFiles) @@ -49,8 +53,15 @@ class PluginDeclarationProviderFactory( } } + private fun packageExists(name: FqName): Boolean { + return if (moduleInfo is ModuleSourceInfo) + PerModulePackageCacheService.packageExists(name, moduleInfo, project) + else + PackageIndexUtil.packageExists(name, indexedFilesScope, project) + } + private fun getStubBasedPackageMemberDeclarationProvider(name: FqName): PackageMemberDeclarationProvider? { - if (!PackageIndexUtil.packageExists(name, indexedFilesScope, project)) return null + if (!packageExists(name)) return null return StubBasedPackageMemberDeclarationProvider(name, project, indexedFilesScope) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactoryService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactoryService.kt index 1a1885f695d..61061617e4d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactoryService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactoryService.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.stubindex.resolve import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory @@ -30,7 +31,8 @@ class PluginDeclarationProviderFactoryService : DeclarationProviderFactoryServic project: Project, storageManager: StorageManager, syntheticFiles: Collection, - filesScope: GlobalSearchScope + filesScope: GlobalSearchScope, + moduleInfo: ModuleInfo ): DeclarationProviderFactory = - PluginDeclarationProviderFactory(project, KotlinSourceFilterScope.sources(filesScope, project), storageManager, syntheticFiles) + PluginDeclarationProviderFactory(project, KotlinSourceFilterScope.sources(filesScope, project), storageManager, syntheticFiles, moduleInfo) } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/CachedValueHelpers.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/CachedValueHelpers.kt new file mode 100644 index 00000000000..57baaaadcad --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/util/CachedValueHelpers.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.core.util + +import com.intellij.openapi.project.Project +import com.intellij.psi.util.CachedValue +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import kotlin.reflect.KProperty + +operator fun CachedValue.getValue(o: Any, property: KProperty<*>): T = value + +fun CachedValue(project: Project, trackValue: Boolean = false, provider: () -> CachedValueProvider.Result) = + CachedValuesManager.getManager(project).createCachedValue(provider, trackValue) diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 750227a4d44..26e26508ba8 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -40,6 +40,9 @@ org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener + + org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener + @@ -311,6 +314,8 @@ + + @@ -454,6 +459,8 @@ language="kotlin" implementation="org.jetbrains.kotlin.idea.references.KotlinDefaultAnnotationMethodImplicitReferenceContributor"/> + +