Use kotlin specific OOCB tracker for light classes inner cache
ClassInnerStuffCache from the platform caches everything with platform OUT_OF_CODE_BLOCK tracker.
This commit is contained in:
committed by
Nikolay Krasko
parent
78ae1a5745
commit
ce3ad8f4da
+2
@@ -8,10 +8,12 @@ package org.jetbrains.kotlin.analyzer
|
|||||||
import com.intellij.openapi.components.ServiceManager
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.ModificationTracker
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
open class KotlinModificationTrackerService {
|
open class KotlinModificationTrackerService {
|
||||||
open val modificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED
|
open val modificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED
|
||||||
open val outOfBlockModificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED
|
open val outOfBlockModificationTracker: ModificationTracker = ModificationTracker.NEVER_CHANGED
|
||||||
|
open fun fileModificationTracker(file: KtFile): ModificationTracker = ModificationTracker.NEVER_CHANGED
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val NEVER_CHANGE_TRACKER_SERVICE = KotlinModificationTrackerService()
|
private val NEVER_CHANGE_TRACKER_SERVICE = KotlinModificationTrackerService()
|
||||||
|
|||||||
+239
@@ -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<Object> dependencies;
|
||||||
|
|
||||||
|
public KotlinClassInnerStuffCache(@NotNull PsiExtensibleClass aClass, @NotNull List<Object> 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> T[] copy(T[] value) {
|
||||||
|
return value.length == 0 ? value : value.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is modified
|
||||||
|
private <T> CachedValueProvider.Result<T> makeResult(T value) {
|
||||||
|
return CachedValueProvider.Result.create(value, dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PsiField[] getAllFields() {
|
||||||
|
List<PsiField> own = myClass.getOwnFields();
|
||||||
|
List<PsiField> ext = PsiAugmentProvider.collectAugments(myClass, PsiField.class);
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiField.ARRAY_FACTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PsiMethod[] getAllMethods() {
|
||||||
|
List<PsiMethod> own = myClass.getOwnMethods();
|
||||||
|
List<PsiMethod> ext = PsiAugmentProvider.collectAugments(myClass, PsiMethod.class);
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiMethod.ARRAY_FACTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PsiClass[] getAllInnerClasses() {
|
||||||
|
List<PsiClass> own = myClass.getOwnInnerClasses();
|
||||||
|
List<PsiClass> ext = PsiAugmentProvider.collectAugments(myClass, PsiClass.class);
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiClass.ARRAY_FACTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map<String, PsiField> getFieldsMap() {
|
||||||
|
PsiField[] fields = getFields();
|
||||||
|
if (fields.length == 0) return Collections.emptyMap();
|
||||||
|
|
||||||
|
Map<String, PsiField> cachedFields = new THashMap<>();
|
||||||
|
for (PsiField field : fields) {
|
||||||
|
String name = field.getName();
|
||||||
|
if (!cachedFields.containsKey(name)) {
|
||||||
|
cachedFields.put(name, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map<String, PsiMethod[]> getMethodsMap() {
|
||||||
|
PsiMethod[] methods = getMethods();
|
||||||
|
if (methods.length == 0) return Collections.emptyMap();
|
||||||
|
|
||||||
|
Map<String, List<PsiMethod>> collectedMethods = ContainerUtil.newHashMap();
|
||||||
|
for (PsiMethod method : methods) {
|
||||||
|
List<PsiMethod> list = collectedMethods.get(method.getName());
|
||||||
|
if (list == null) {
|
||||||
|
collectedMethods.put(method.getName(), list = ContainerUtil.newSmartList());
|
||||||
|
}
|
||||||
|
list.add(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, PsiMethod[]> cachedMethods = ContainerUtil.newTroveMap();
|
||||||
|
for (Map.Entry<String, List<PsiMethod>> entry : collectedMethods.entrySet()) {
|
||||||
|
List<PsiMethod> list = entry.getValue();
|
||||||
|
cachedMethods.put(entry.getKey(), list.toArray(PsiMethod.EMPTY_ARRAY));
|
||||||
|
}
|
||||||
|
return cachedMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map<String, PsiClass> getInnerClassesMap() {
|
||||||
|
PsiClass[] classes = getInnerClasses();
|
||||||
|
if (classes.length == 0) return Collections.emptyMap();
|
||||||
|
|
||||||
|
Map<String, PsiClass> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,16 +20,18 @@ import com.intellij.navigation.ItemPresentationProviders
|
|||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.impl.PsiClassImplUtil
|
import com.intellij.psi.impl.PsiClassImplUtil
|
||||||
import com.intellij.psi.impl.light.AbstractLightClass
|
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.impl.source.PsiExtensibleClass
|
||||||
import com.intellij.psi.scope.PsiScopeProcessor
|
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.KtLightFieldImpl
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
|
||||||
abstract class KtLightClassBase protected constructor(manager: PsiManager)
|
abstract class KtLightClassBase protected constructor(manager: PsiManager)
|
||||||
: AbstractLightClass(manager, KotlinLanguage.INSTANCE), KtLightClass, PsiExtensibleClass {
|
: 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
|
override fun getDelegate() = clsDelegate
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ abstract class KtLightClassBase protected constructor(manager: PsiManager)
|
|||||||
processor: PsiScopeProcessor, state: ResolveState, lastParent: PsiElement?, place: PsiElement
|
processor: PsiScopeProcessor, state: ResolveState, lastParent: PsiElement?, place: PsiElement
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (isEnum) {
|
if (isEnum) {
|
||||||
if (!PsiClassImplUtil.processDeclarationsInEnum(processor, state, myInnersCache)) return false
|
if (!processDeclarationsInEnum(processor, state, myInnersCache)) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.processDeclarations(processor, state, lastParent, place)
|
return super.processDeclarations(processor, state, lastParent, place)
|
||||||
|
|||||||
+13
@@ -86,6 +86,8 @@ class KotlinCodeBlockModificationListener(
|
|||||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||||
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
||||||
|
|
||||||
|
incFileModificationCount(ktFile)
|
||||||
|
|
||||||
val changedElements = changeSet.changedElements
|
val changedElements = changeSet.changedElements
|
||||||
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
|
// 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
|
// 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)
|
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? {
|
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
|
||||||
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||||
if (lambda is KtLambdaExpression) {
|
if (lambda is KtLambdaExpression) {
|
||||||
@@ -216,6 +224,11 @@ class KotlinCodeBlockModificationListener(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val PER_FILE_MODIFICATION_TRACKER = Key<SimpleModificationTracker>("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<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
||||||
|
|
||||||
val KtFile.outOfBlockModificationCount: Long
|
val KtFile.outOfBlockModificationCount: Long
|
||||||
|
|||||||
+5
@@ -9,11 +9,16 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.openapi.util.ModificationTracker
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
|
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
class KotlinIDEModificationTrackerService(project: Project, psiModificationTracker: PsiModificationTracker) :
|
class KotlinIDEModificationTrackerService(project: Project, psiModificationTracker: PsiModificationTracker) :
|
||||||
KotlinModificationTrackerService() {
|
KotlinModificationTrackerService() {
|
||||||
|
|
||||||
override val modificationTracker: ModificationTracker = psiModificationTracker
|
override val modificationTracker: ModificationTracker = psiModificationTracker
|
||||||
|
|
||||||
override val outOfBlockModificationTracker: ModificationTracker =
|
override val outOfBlockModificationTracker: ModificationTracker =
|
||||||
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker
|
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker
|
||||||
|
|
||||||
|
override fun fileModificationTracker(file: KtFile): ModificationTracker =
|
||||||
|
file.perFileModificationTracker
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user