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.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()
|
||||
|
||||
+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.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)
|
||||
|
||||
+13
@@ -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<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")
|
||||
|
||||
val KtFile.outOfBlockModificationCount: Long
|
||||
|
||||
+5
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user