diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java new file mode 100644 index 000000000000..b70655361b11 --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java @@ -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 { + /** + * Lock to sync + */ + @NotNull + private final Object myLock = new Object(); + @NotNull + private final CachedValue> myCachedMapStorage; + + @NotNull + private final Function 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 valueProvider) { + myCachedMapStorage = manager.createCachedValue(new MapCreator(), 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 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 implements CachedValueProvider> { + @Nullable + @Override + public Result> compute() { + // This method is called if cache is empty. Create new map for it. + final HashMap map = new HashMap(); + return new Result>(map, PsiModificationTracker.MODIFICATION_COUNT); + } + } +} diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java index ab40e47309e3..9d5f9f69fbf0 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java @@ -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 VALUE_PROVIDER = new MyValueProvider(); @NotNull - private final Object myLock = new Object(); - - @NotNull - private final CachedValue> myCachedMapStorage; - + private final TypeEvalContextBasedCache myCache; TypeEvalContextCacheImpl(@NotNull final CachedValuesManager manager) { - myCachedMapStorage = manager.createCachedValue(MAP_CREATOR, false); + myCache = new TypeEvalContextBasedCache(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 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> { - @Nullable + private static class MyValueProvider implements Function { @Override - public Result> compute() { - // This method is called if cache is empty. Create new map for it. - final HashMap map = new HashMap(); - return new Result>(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; } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 5c0414276a99..3ac39612cc39 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -72,14 +72,25 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0]; - private List 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> myAncestorsCache; - private volatile Map myPropertyCache; + /** + * Lock to create {@link #myAncestorsCache} in lazy way. + */ + @NotNull + private final Object myAncestorsCacheLock = new Object(); - private class CachedAncestorsProvider implements ParameterizedCachedValueProvider, TypeEvalContext> { + /** + * Engine to create list of ancestors based on context + */ + private class CachedAncestorsProvider implements Function> { @Nullable @Override - public CachedValueProvider.Result> compute(@NotNull TypeEvalContext context) { + public List fun(@NotNull TypeEvalContext context) { List ancestorTypes; if (isNewStyleClass(context)) { try { @@ -102,10 +113,14 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla else { ancestorTypes = getOldStyleAncestorTypes(context); } - return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.MODIFICATION_COUNT); + return ancestorTypes; } } + private List myInstanceAttributes; + + private volatile Map myPropertyCache; + private final Key, TypeEvalContext>> myCachedValueKey = Key.create("cached ancestors"); private final CachedAncestorsProvider myCachedAncestorsProvider = new CachedAncestorsProvider(); @@ -135,6 +150,25 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla super(stub, nodeType); } + /** + * @return ancestors cache. It is created lazyly if needed. + */ + @NotNull + private TypeEvalContextBasedCache> getAncestorsCache() { + if (myAncestorsCache != null) { + return myAncestorsCache; + } + synchronized (myAncestorsCacheLock) { + if (myAncestorsCache == null) { + myAncestorsCache = new TypeEvalContextBasedCache>( + 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 implements PyCla @NotNull @Override public List 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