/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.lang.psi.impl.source.resolve.cache; import java.util.Map; import java.util.concurrent.ConcurrentMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredReadAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.NotNullLazyKey; import com.intellij.openapi.util.RecursionGuard; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.util.StaticGetter; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.reference.SoftReference; import com.intellij.util.containers.ConcurrentWeakHashMap; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; /** * @author VISTALl * @since 18:06 26.10.2014 * <p/> * This is variant of {@link com.intellij.psi.impl.source.resolve.ResolveCache} with 'resolveFromParent' and 'incompleteCode' */ public class CSharpResolveCache { private static final NotNullLazyKey<CSharpResolveCache, Project> ourInstanceKey = ServiceManager.createLazyKey(CSharpResolveCache.class); private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.resolve.ResolveCache"); private final ConcurrentMap[] myMaps = new ConcurrentMap[2 * 2 * 2 * 2]; //boolean physical, boolean incompleteCode, boolean resolveFromParent, boolean isPoly private final RecursionGuard myGuard = RecursionManager.createGuard("csharpResolveCache"); public static CSharpResolveCache getInstance(Project project) { ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly return ourInstanceKey.getValue(project); } public interface AbstractResolver<TRef extends PsiElement, TResult> { @RequiredReadAction TResult resolve(@NotNull TRef ref, boolean incompleteCode, boolean resolveFromParent); } public interface PolyVariantResolver<T extends PsiElement> extends AbstractResolver<T, ResolveResult[]> { @Override @NotNull @RequiredReadAction ResolveResult[] resolve(@NotNull T t, boolean incompleteCode, boolean resolveFromParent); } public CSharpResolveCache(@NotNull MessageBus messageBus) { for(int i = 0; i < myMaps.length; i++) { myMaps[i] = createWeakMap(); } messageBus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { @Override public void modificationCountChanged() { clearCache(true); clearCache(false); } }); } public static <K, V> ConcurrentWeakHashMap<K, V> createWeakMap() { return new ConcurrentWeakHashMap<K, V>(100, 0.75f, Runtime.getRuntime().availableProcessors(), ContainerUtil.<K>canonicalStrategy()); } public void clearCache(boolean isPhysical) { int startIndex = isPhysical ? 0 : 1; // (physical ? 0 : 1) * 8 + (incompleteCode ? 0 : 1) * 4 + (resolveFromParent ? 0 : 1) * 2 + (isPoly ? 0 : 1) for(int physicalIndex = startIndex; physicalIndex < 2; physicalIndex++) { for(int incompleteCodeIndex = 0; incompleteCodeIndex < 2; incompleteCodeIndex++) { for(int resolveFromParentIndex = 0; resolveFromParentIndex < 2; resolveFromParentIndex++) { for(int polyIndex = 0; polyIndex < 2; polyIndex++) { myMaps[physicalIndex * 8 + incompleteCodeIndex * 4 + resolveFromParentIndex * 2 + polyIndex].clear(); } } } } } @Nullable @RequiredReadAction private <TRef extends PsiElement, TResult> TResult resolve(@NotNull final TRef ref, @NotNull final AbstractResolver<TRef, TResult> resolver, boolean needToPreventRecursion, final boolean incompleteCode, final boolean resolveFromParent, boolean isPoly, boolean isPhysical) { ProgressIndicatorProvider.checkCanceled(); ApplicationManager.getApplication().assertReadAccessAllowed(); ConcurrentMap<TRef, Getter<TResult>> map = getMap(isPhysical, incompleteCode, resolveFromParent, isPoly); Getter<TResult> reference = map.get(ref); TResult result = reference == null ? null : reference.get(); if(result != null) { return result; } RecursionGuard.StackStamp stamp = myGuard.markStack(); result = needToPreventRecursion ? myGuard.doPreventingRecursion(Quaternary.create(ref, incompleteCode, resolveFromParent, isPoly), true, new Computable<TResult>() { @Override @RequiredReadAction public TResult compute() { return resolver.resolve(ref, incompleteCode, resolveFromParent); } }) : resolver.resolve(ref, incompleteCode, resolveFromParent); PsiElement element = result instanceof ResolveResult ? ((ResolveResult) result).getElement() : null; LOG.assertTrue(element == null || element.isValid(), result); if(stamp.mayCacheNow()) { cache(ref, map, result, isPoly); } return result; } @NotNull @RequiredReadAction public <T extends PsiElement> ResolveResult[] resolveWithCaching(@NotNull T ref, @NotNull PolyVariantResolver<T> resolver, boolean needToPreventRecursion, boolean incompleteCode, boolean resolveFromParent) { return resolveWithCaching(ref, resolver, needToPreventRecursion, incompleteCode, resolveFromParent, ref.getContainingFile()); } @NotNull @RequiredReadAction public <T extends PsiElement> ResolveResult[] resolveWithCaching(@NotNull T ref, @NotNull PolyVariantResolver<T> resolver, boolean needToPreventRecursion, boolean incompleteCode, boolean resolveFromParent, @NotNull PsiFile containingFile) { ResolveResult[] result = resolve(ref, resolver, needToPreventRecursion, incompleteCode, resolveFromParent, true, containingFile.isPhysical()); return result == null ? ResolveResult.EMPTY_ARRAY : result; } @Nullable public <T extends PsiPolyVariantReference & PsiElement> ResolveResult[] getCachedResults(@NotNull T ref, boolean physical, boolean incompleteCode, boolean resolveFromParent, boolean isPoly) { Map<T, Getter<ResolveResult[]>> map = getMap(physical, incompleteCode, resolveFromParent, isPoly); Getter<ResolveResult[]> reference = map.get(ref); return reference == null ? null : reference.get(); } @Nullable @RequiredReadAction public <TRef extends PsiElement, TResult> TResult resolveWithCaching(@NotNull TRef ref, @NotNull AbstractResolver<TRef, TResult> resolver, boolean needToPreventRecursion, boolean incompleteCode, boolean resolveFromParent) { return resolve(ref, resolver, needToPreventRecursion, incompleteCode, resolveFromParent, false, ref.isPhysical()); } private <TRef extends PsiElement, TResult> ConcurrentMap<TRef, Getter<TResult>> getMap(boolean physical, boolean incompleteCode, boolean resolveFromParent, boolean isPoly) { //noinspection unchecked return myMaps[(physical ? 0 : 1) * 8 + (incompleteCode ? 0 : 1) * 4 + (resolveFromParent ? 0 : 1) * 2 + (isPoly ? 0 : 1)]; } protected static class SoftGetter<T> extends SoftReference<T> implements Getter<T> { public SoftGetter(T referent) { super(referent); } } private static final Getter<ResolveResult[]> EMPTY_POLY_RESULT = new StaticGetter<ResolveResult[]>(ResolveResult.EMPTY_ARRAY); private static final Getter<Object> NULL_RESULT = new StaticGetter<Object>(null); private static <TRef extends PsiElement, TResult> void cache(@NotNull TRef ref, @NotNull ConcurrentMap<TRef, Getter<TResult>> map, TResult result, boolean isPoly) { // optimization: less contention Getter<TResult> cached = map.get(ref); if(cached != null && cached.get() == result) { return; } if(result == null) { // no use in creating SoftReference to null //noinspection unchecked cached = (Getter<TResult>) NULL_RESULT; } else if(isPoly && ((Object[]) result).length == 0) { // no use in creating SoftReference to empty array //noinspection unchecked cached = result.getClass() == ResolveResult[].class ? (Getter<TResult>) EMPTY_POLY_RESULT : new StaticGetter<TResult>(result); } else { cached = new SoftGetter<TResult>(result); } map.put(ref, cached); } }