From ce3ad8f4da88b7ab64799a78296454cdabfc0c80 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Wed, 31 Jul 2019 14:14:24 +0300 Subject: [PATCH] Use kotlin specific OOCB tracker for light classes inner cache ClassInnerStuffCache from the platform caches everything with platform OUT_OF_CODE_BLOCK tracker. --- .../KotlinModificationTrackerService.kt | 2 + .../classes/KotlinClassInnerStuffCache.java | 239 ++++++++++++++++++ .../kotlin/asJava/classes/KtLightClassBase.kt | 8 +- .../KotlinCodeBlockModificationListener.kt | 13 + .../KotlinIDEModificationTrackerService.kt | 5 + 5 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.java diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/KotlinModificationTrackerService.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/KotlinModificationTrackerService.kt index 55bbf379d4a..addb44bb014 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/KotlinModificationTrackerService.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/KotlinModificationTrackerService.kt @@ -8,10 +8,12 @@ package org.jetbrains.kotlin.analyzer import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.ModificationTracker +import org.jetbrains.kotlin.psi.KtFile open class KotlinModificationTrackerService { open val modificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED open val outOfBlockModificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED + open fun fileModificationTracker(file: KtFile): ModificationTracker = ModificationTracker.NEVER_CHANGED companion object { private val NEVER_CHANGE_TRACKER_SERVICE = KotlinModificationTrackerService() diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.java b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.java new file mode 100644 index 00000000000..7ab8912afd3 --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.java @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.asJava.classes; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SimpleModificationTracker; +import com.intellij.psi.*; +import com.intellij.psi.augment.PsiAugmentProvider; +import com.intellij.psi.impl.PsiClassImplUtil; +import com.intellij.psi.impl.PsiImplUtil; +import com.intellij.psi.impl.light.LightMethod; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import com.intellij.psi.scope.ElementClassHint; +import com.intellij.psi.scope.NameHint; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.intellij.util.ObjectUtils.notNull; + +// Copy of ClassInnerStuffCache with custom list of caching dependencies +@SuppressWarnings({"WeakerAccess", "Java8MapApi"}) +public class KotlinClassInnerStuffCache { + private final PsiExtensibleClass myClass; + private final SimpleModificationTracker myTracker = new SimpleModificationTracker(); + private final List dependencies; + + public KotlinClassInnerStuffCache(@NotNull PsiExtensibleClass aClass, @NotNull List externalDependencies) { + myClass = aClass; + + dependencies = new SmartList<>(); + dependencies.addAll(externalDependencies); + dependencies.add(myTracker); + } + + @NotNull + public PsiMethod[] getConstructors() { + return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(PsiImplUtil.getConstructors(myClass)))); + } + + @NotNull + public PsiField[] getFields() { + return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllFields()))); + } + + @NotNull + public PsiMethod[] getMethods() { + return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllMethods()))); + } + + @NotNull + public PsiClass[] getInnerClasses() { + return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllInnerClasses()))); + } + + @Nullable + public PsiField findFieldByName(String name, boolean checkBases) { + if (checkBases) { + return PsiClassImplUtil.findFieldByName(myClass, name, true); + } + else { + return CachedValuesManager.getCachedValue(myClass, () -> makeResult(getFieldsMap())).get(name); + } + } + + @NotNull + public PsiMethod[] findMethodsByName(String name, boolean checkBases) { + if (checkBases) { + return PsiClassImplUtil.findMethodsByName(myClass, name, true); + } + else { + return copy(notNull(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getMethodsMap())).get(name), PsiMethod.EMPTY_ARRAY)); + } + } + + @Nullable + public PsiClass findInnerClassByName(String name, boolean checkBases) { + if (checkBases) { + return PsiClassImplUtil.findInnerByName(myClass, name, true); + } + else { + return CachedValuesManager.getCachedValue(myClass, () -> makeResult(getInnerClassesMap())).get(name); + } + } + + @Nullable + public PsiMethod getValuesMethod() { + return myClass.isEnum() && myClass.getName() != null ? CachedValuesManager.getCachedValue(myClass, () -> makeResult(makeValuesMethod())) : null; + } + + @Nullable + public PsiMethod getValueOfMethod() { + return myClass.isEnum() && myClass.getName() != null ? CachedValuesManager.getCachedValue(myClass, () -> makeResult(makeValueOfMethod())) : null; + } + + private static T[] copy(T[] value) { + return value.length == 0 ? value : value.clone(); + } + + // This method is modified + private CachedValueProvider.Result makeResult(T value) { + return CachedValueProvider.Result.create(value, dependencies); + } + + @NotNull + private PsiField[] getAllFields() { + List own = myClass.getOwnFields(); + List ext = PsiAugmentProvider.collectAugments(myClass, PsiField.class); + return ArrayUtil.mergeCollections(own, ext, PsiField.ARRAY_FACTORY); + } + + @NotNull + private PsiMethod[] getAllMethods() { + List own = myClass.getOwnMethods(); + List ext = PsiAugmentProvider.collectAugments(myClass, PsiMethod.class); + return ArrayUtil.mergeCollections(own, ext, PsiMethod.ARRAY_FACTORY); + } + + @NotNull + private PsiClass[] getAllInnerClasses() { + List own = myClass.getOwnInnerClasses(); + List ext = PsiAugmentProvider.collectAugments(myClass, PsiClass.class); + return ArrayUtil.mergeCollections(own, ext, PsiClass.ARRAY_FACTORY); + } + + @NotNull + private Map getFieldsMap() { + PsiField[] fields = getFields(); + if (fields.length == 0) return Collections.emptyMap(); + + Map cachedFields = new THashMap<>(); + for (PsiField field : fields) { + String name = field.getName(); + if (!cachedFields.containsKey(name)) { + cachedFields.put(name, field); + } + } + return cachedFields; + } + + @NotNull + private Map getMethodsMap() { + PsiMethod[] methods = getMethods(); + if (methods.length == 0) return Collections.emptyMap(); + + Map> collectedMethods = ContainerUtil.newHashMap(); + for (PsiMethod method : methods) { + List list = collectedMethods.get(method.getName()); + if (list == null) { + collectedMethods.put(method.getName(), list = ContainerUtil.newSmartList()); + } + list.add(method); + } + + Map cachedMethods = ContainerUtil.newTroveMap(); + for (Map.Entry> entry : collectedMethods.entrySet()) { + List list = entry.getValue(); + cachedMethods.put(entry.getKey(), list.toArray(PsiMethod.EMPTY_ARRAY)); + } + return cachedMethods; + } + + @NotNull + private Map getInnerClassesMap() { + PsiClass[] classes = getInnerClasses(); + if (classes.length == 0) return Collections.emptyMap(); + + Map cachedInners = new THashMap<>(); + for (PsiClass psiClass : classes) { + String name = psiClass.getName(); + if (name == null) { + Logger.getInstance(KotlinClassInnerStuffCache.class).error(psiClass); + } + else if (!(psiClass instanceof ExternallyDefinedPsiElement) || !cachedInners.containsKey(name)) { + cachedInners.put(name, psiClass); + } + } + return cachedInners; + } + + private PsiMethod makeValuesMethod() { + return getSyntheticMethod("public static " + myClass.getName() + "[] values() { }"); + } + + private PsiMethod makeValueOfMethod() { + return getSyntheticMethod("public static " + myClass.getName() + " valueOf(java.lang.String name) throws java.lang.IllegalArgumentException { }"); + } + + private PsiMethod getSyntheticMethod(String text) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(myClass.getProject()); + PsiMethod method = factory.createMethodFromText(text, myClass); + return new LightMethod(myClass.getManager(), method, myClass) { + @Override + public int getTextOffset() { + return myClass.getTextOffset(); + } + }; + } + + public void dropCaches() { + myTracker.incModificationCount(); + } + + private static final String VALUES_METHOD = "values"; + private static final String VALUE_OF_METHOD = "valueOf"; + + // Copy of PsiClassImplUtil.processDeclarationsInEnum for own cache class + public static boolean processDeclarationsInEnum(@NotNull PsiScopeProcessor processor, + @NotNull ResolveState state, + @NotNull KotlinClassInnerStuffCache innerStuffCache) { + ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); + if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) { + NameHint nameHint = processor.getHint(NameHint.KEY); + if (nameHint == null || VALUES_METHOD.equals(nameHint.getName(state))) { + PsiMethod method = innerStuffCache.getValuesMethod(); + if (method != null && !processor.execute(method, ResolveState.initial())) return false; + } + if (nameHint == null || VALUE_OF_METHOD.equals(nameHint.getName(state))) { + PsiMethod method = innerStuffCache.getValueOfMethod(); + if (method != null && !processor.execute(method, ResolveState.initial())) return false; + } + } + + return true; + } +} diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt index a3836c037ab..ca5573f3caa 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt @@ -20,16 +20,18 @@ import com.intellij.navigation.ItemPresentationProviders import com.intellij.psi.* import com.intellij.psi.impl.PsiClassImplUtil import com.intellij.psi.impl.light.AbstractLightClass -import com.intellij.psi.impl.source.ClassInnerStuffCache import com.intellij.psi.impl.source.PsiExtensibleClass import com.intellij.psi.scope.PsiScopeProcessor +import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService +import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache.processDeclarationsInEnum import org.jetbrains.kotlin.asJava.elements.KtLightFieldImpl import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl import org.jetbrains.kotlin.idea.KotlinLanguage abstract class KtLightClassBase protected constructor(manager: PsiManager) : AbstractLightClass(manager, KotlinLanguage.INSTANCE), KtLightClass, PsiExtensibleClass { - private val myInnersCache = ClassInnerStuffCache(this) + protected open val myInnersCache = KotlinClassInnerStuffCache( + this, listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker)) override fun getDelegate() = clsDelegate @@ -61,7 +63,7 @@ abstract class KtLightClassBase protected constructor(manager: PsiManager) processor: PsiScopeProcessor, state: ResolveState, lastParent: PsiElement?, place: PsiElement ): Boolean { if (isEnum) { - if (!PsiClassImplUtil.processDeclarationsInEnum(processor, state, myInnersCache)) return false + if (!processDeclarationsInEnum(processor, state, myInnersCache)) return false } return super.processDeclarations(processor, state, lastParent, place) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt index c22590c7caf..8314b2d7107 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt @@ -86,6 +86,8 @@ class KotlinCodeBlockModificationListener( val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return + incFileModificationCount(ktFile) + val changedElements = changeSet.changedElements // When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire // contents to be replaced, which is represented in a POM event as an empty list of changed elements @@ -157,6 +159,12 @@ class KotlinCodeBlockModificationListener( file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1) } + private fun incFileModificationCount(file: KtFile) { + val tracker = file.getUserData(PER_FILE_MODIFICATION_TRACKER) + ?: file.putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker()) + tracker.incModificationCount() + } + fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? { val lambda = element.getTopmostParentOfType() if (lambda is KtLambdaExpression) { @@ -216,6 +224,11 @@ class KotlinCodeBlockModificationListener( } } +private val PER_FILE_MODIFICATION_TRACKER = Key("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT") + +val KtFile.perFileModificationTracker: ModificationTracker + get() = putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker()) + private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT") val KtFile.outOfBlockModificationCount: Long diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinIDEModificationTrackerService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinIDEModificationTrackerService.kt index b05954e34e5..b73ee45ae94 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinIDEModificationTrackerService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinIDEModificationTrackerService.kt @@ -9,11 +9,16 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.ModificationTracker import com.intellij.psi.util.PsiModificationTracker import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService +import org.jetbrains.kotlin.psi.KtFile class KotlinIDEModificationTrackerService(project: Project, psiModificationTracker: PsiModificationTracker) : KotlinModificationTrackerService() { override val modificationTracker: ModificationTracker = psiModificationTracker + override val outOfBlockModificationTracker: ModificationTracker = KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker + + override fun fileModificationTracker(file: KtFile): ModificationTracker = + file.perFileModificationTracker } \ No newline at end of file