mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-3999 PY-8207 PY-7309
PyClass ancestors are now cached in smart way using type eval context as key
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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 com.jetbrains.python.psi.types;
|
||||
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Engine to cache something in map, where {@link TypeEvalContext} is used as key.
|
||||
* This cache is weak-based (no memory leaks), thread safe and purges on any PSI change.
|
||||
*
|
||||
* @author Ilya.Kazakevich
|
||||
*/
|
||||
public final class TypeEvalContextBasedCache<T> {
|
||||
/**
|
||||
* Lock to sync
|
||||
*/
|
||||
@NotNull
|
||||
private final Object myLock = new Object();
|
||||
@NotNull
|
||||
private final CachedValue<Map<TypeEvalConstraints, T>> myCachedMapStorage;
|
||||
|
||||
@NotNull
|
||||
private final Function<TypeEvalContext, T> myProvider;
|
||||
|
||||
/**
|
||||
* @param manager Cache manager to be used to store cache
|
||||
* @param valueProvider engine to create value based on context.
|
||||
*/
|
||||
public TypeEvalContextBasedCache(@NotNull final CachedValuesManager manager,
|
||||
@NotNull final Function<TypeEvalContext, T> valueProvider) {
|
||||
myCachedMapStorage = manager.createCachedValue(new MapCreator<T>(), false);
|
||||
myProvider = valueProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value (executes provider to obtain new if no any and stores it in cache)
|
||||
* @param context to be used as key
|
||||
* @return value
|
||||
*/
|
||||
@NotNull
|
||||
public T getValue(@NotNull final TypeEvalContext context) {
|
||||
|
||||
// Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created.
|
||||
synchronized (myLock) {
|
||||
final Map<TypeEvalConstraints, T> map = myCachedMapStorage.getValue();
|
||||
T value = map.get(context.getConstraints());
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
// This is the same value, semantically: value for context-key
|
||||
//noinspection ReuseOfLocalVariable
|
||||
value = myProvider.fun(context);
|
||||
map.put(context.getConstraints(), value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider that creates map to store cache. Map depends on PSI modification
|
||||
*/
|
||||
private static final class MapCreator<T> implements CachedValueProvider<Map<TypeEvalConstraints, T>> {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<Map<TypeEvalConstraints, T>> compute() {
|
||||
// This method is called if cache is empty. Create new map for it.
|
||||
final HashMap<TypeEvalConstraints, T> map = new HashMap<TypeEvalConstraints, T>();
|
||||
return new Result<Map<TypeEvalConstraints, T>>(map, PsiModificationTracker.MODIFICATION_COUNT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,9 @@
|
||||
*/
|
||||
package com.jetbrains.python.psi.types;
|
||||
|
||||
import com.intellij.psi.util.CachedValue;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiModificationTracker;
|
||||
import com.intellij.util.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions.
|
||||
@@ -34,53 +28,27 @@ import java.util.Map;
|
||||
*/
|
||||
final class TypeEvalContextCacheImpl implements TypeEvalContextCache {
|
||||
|
||||
/**
|
||||
* Producer to create map to store cache
|
||||
*/
|
||||
@NotNull
|
||||
private static final MapCreator MAP_CREATOR = new MapCreator();
|
||||
|
||||
/**
|
||||
* Lock to sync
|
||||
*/
|
||||
private static final Function<TypeEvalContext, TypeEvalContext> VALUE_PROVIDER = new MyValueProvider();
|
||||
@NotNull
|
||||
private final Object myLock = new Object();
|
||||
|
||||
@NotNull
|
||||
private final CachedValue<Map<TypeEvalConstraints, TypeEvalContext>> myCachedMapStorage;
|
||||
|
||||
private final TypeEvalContextBasedCache<TypeEvalContext> myCache;
|
||||
|
||||
TypeEvalContextCacheImpl(@NotNull final CachedValuesManager manager) {
|
||||
myCachedMapStorage = manager.createCachedValue(MAP_CREATOR, false);
|
||||
myCache = new TypeEvalContextBasedCache<TypeEvalContext>(manager, VALUE_PROVIDER);
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public TypeEvalContext getContext(@NotNull final TypeEvalContext standard) {
|
||||
|
||||
// Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created.
|
||||
synchronized (myLock) {
|
||||
final Map<TypeEvalConstraints, TypeEvalContext> map = myCachedMapStorage.getValue();
|
||||
final TypeEvalContext context = map.get(standard.getConstraints());
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
map.put(standard.getConstraints(), standard);
|
||||
return standard;
|
||||
}
|
||||
return myCache.getValue(standard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider that creates map to store cache. Map depends on PSI modification
|
||||
*/
|
||||
private static final class MapCreator implements CachedValueProvider<Map<TypeEvalConstraints, TypeEvalContext>> {
|
||||
@Nullable
|
||||
private static class MyValueProvider implements Function<TypeEvalContext, TypeEvalContext> {
|
||||
@Override
|
||||
public Result<Map<TypeEvalConstraints, TypeEvalContext>> compute() {
|
||||
// This method is called if cache is empty. Create new map for it.
|
||||
final HashMap<TypeEvalConstraints, TypeEvalContext> map = new HashMap<TypeEvalConstraints, TypeEvalContext>();
|
||||
return new Result<Map<TypeEvalConstraints, TypeEvalContext>>(map, PsiModificationTracker.MODIFICATION_COUNT);
|
||||
public TypeEvalContext fun(final TypeEvalContext param) {
|
||||
// key and value are both context here. If no context stored, then key is stored. Old one is returned otherwise to cache.
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +72,25 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
|
||||
public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
|
||||
|
||||
private List<PyTargetExpression> myInstanceAttributes;
|
||||
/**
|
||||
* Ancestors cache is lazy because provider ({@link com.jetbrains.python.psi.impl.PyClassImpl.CachedAncestorsProvider}) needs
|
||||
* {@link #getProject()} which is notavailable during consructor time.
|
||||
*/
|
||||
private TypeEvalContextBasedCache<List<PyClassLikeType>> myAncestorsCache;
|
||||
|
||||
private volatile Map<String, Property> myPropertyCache;
|
||||
/**
|
||||
* Lock to create {@link #myAncestorsCache} in lazy way.
|
||||
*/
|
||||
@NotNull
|
||||
private final Object myAncestorsCacheLock = new Object();
|
||||
|
||||
private class CachedAncestorsProvider implements ParameterizedCachedValueProvider<List<PyClassLikeType>, TypeEvalContext> {
|
||||
/**
|
||||
* Engine to create list of ancestors based on context
|
||||
*/
|
||||
private class CachedAncestorsProvider implements Function<TypeEvalContext, List<PyClassLikeType>> {
|
||||
@Nullable
|
||||
@Override
|
||||
public CachedValueProvider.Result<List<PyClassLikeType>> compute(@NotNull TypeEvalContext context) {
|
||||
public List<PyClassLikeType> fun(@NotNull TypeEvalContext context) {
|
||||
List<PyClassLikeType> ancestorTypes;
|
||||
if (isNewStyleClass(context)) {
|
||||
try {
|
||||
@@ -102,10 +113,14 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
else {
|
||||
ancestorTypes = getOldStyleAncestorTypes(context);
|
||||
}
|
||||
return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.MODIFICATION_COUNT);
|
||||
return ancestorTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private List<PyTargetExpression> myInstanceAttributes;
|
||||
|
||||
private volatile Map<String, Property> myPropertyCache;
|
||||
|
||||
private final Key<ParameterizedCachedValue<List<PyClassLikeType>, TypeEvalContext>> myCachedValueKey = Key.create("cached ancestors");
|
||||
private final CachedAncestorsProvider myCachedAncestorsProvider = new CachedAncestorsProvider();
|
||||
|
||||
@@ -135,6 +150,25 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
super(stub, nodeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ancestors cache. It is created lazyly if needed.
|
||||
*/
|
||||
@NotNull
|
||||
private TypeEvalContextBasedCache<List<PyClassLikeType>> getAncestorsCache() {
|
||||
if (myAncestorsCache != null) {
|
||||
return myAncestorsCache;
|
||||
}
|
||||
synchronized (myAncestorsCacheLock) {
|
||||
if (myAncestorsCache == null) {
|
||||
myAncestorsCache = new TypeEvalContextBasedCache<List<PyClassLikeType>>(
|
||||
CachedValuesManager.getManager(getProject()),
|
||||
new CachedAncestorsProvider()
|
||||
);
|
||||
}
|
||||
return myAncestorsCache;
|
||||
}
|
||||
}
|
||||
|
||||
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
|
||||
final ASTNode nameElement = PyUtil.createNewName(this, name);
|
||||
final ASTNode node = getNameNode();
|
||||
@@ -1274,9 +1308,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context) {
|
||||
// TODO: Return different cached copies depending on the type eval context parameters
|
||||
final CachedValuesManager manager = CachedValuesManager.getManager(getProject());
|
||||
return manager.getParameterizedCachedValue(this, myCachedValueKey, myCachedAncestorsProvider, false, context);
|
||||
return getAncestorsCache().getValue(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user