| /* |
| * Copyright 2000-2014 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.intellij.psi.impl.source.resolve; |
| |
| 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.*; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.AnyPsiChangeListener; |
| import com.intellij.psi.impl.PsiManagerImpl; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.util.containers.ConcurrentWeakHashMap; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.messages.MessageBus; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentMap; |
| |
| public class ResolveCache { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.resolve.ResolveCache"); |
| private final ConcurrentMap[] myMaps = new ConcurrentMap[2*2*2]; //boolean physical, boolean incompleteCode, boolean isPoly |
| private final RecursionGuard myGuard = RecursionManager.createGuard("resolveCache"); |
| |
| public static ResolveCache getInstance(Project project) { |
| ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly |
| return ServiceManager.getService(project, ResolveCache.class); |
| } |
| |
| public interface AbstractResolver<TRef extends PsiReference,TResult> { |
| TResult resolve(@NotNull TRef ref, boolean incompleteCode); |
| } |
| public interface PolyVariantResolver<T extends PsiPolyVariantReference> extends AbstractResolver<T,ResolveResult[]> { |
| @Override |
| @NotNull |
| ResolveResult[] resolve(@NotNull T t, boolean incompleteCode); |
| } |
| public interface PolyVariantContextResolver<T extends PsiPolyVariantReference> { |
| @NotNull |
| ResolveResult[] resolve(@NotNull T ref, @NotNull PsiFile containingFile, boolean incompleteCode); |
| } |
| |
| public interface Resolver extends AbstractResolver<PsiReference,PsiElement>{ |
| } |
| |
| public ResolveCache(@NotNull MessageBus messageBus) { |
| for (int i = 0; i < myMaps.length; i++) { |
| myMaps[i] = createWeakMap(); |
| } |
| messageBus.connect().subscribe(PsiManagerImpl.ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { |
| @Override |
| public void beforePsiChanged(boolean isPhysical) { |
| clearCache(isPhysical); |
| } |
| |
| @Override |
| public void afterPsiChanged(boolean isPhysical) { |
| } |
| }); |
| } |
| |
| private 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; |
| for (int i=startIndex;i<2;i++)for (int j=0;j<2;j++)for (int k=0;k<2;k++) myMaps[i*4+j*2+k].clear(); |
| } |
| |
| @Nullable |
| private <TRef extends PsiReference, TResult> TResult resolve(@NotNull final TRef ref, |
| @NotNull final AbstractResolver<TRef, TResult> resolver, |
| boolean needToPreventRecursion, |
| final boolean incompleteCode, |
| boolean isPoly, |
| boolean isPhysical) { |
| ProgressIndicatorProvider.checkCanceled(); |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| |
| ConcurrentMap<TRef, Getter<TResult>> map = getMap(isPhysical, incompleteCode, 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(Trinity.create(ref, incompleteCode, isPoly), true, new Computable<TResult>() { |
| @Override |
| public TResult compute() { |
| return resolver.resolve(ref, incompleteCode); |
| } |
| }) : resolver.resolve(ref, incompleteCode); |
| 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 |
| public <T extends PsiPolyVariantReference> ResolveResult[] resolveWithCaching(@NotNull T ref, |
| @NotNull PolyVariantResolver<T> resolver, |
| boolean needToPreventRecursion, |
| boolean incompleteCode) { |
| return resolveWithCaching(ref, resolver, needToPreventRecursion, incompleteCode, ref.getElement().getContainingFile()); |
| } |
| @NotNull |
| public <T extends PsiPolyVariantReference> ResolveResult[] resolveWithCaching(@NotNull T ref, |
| @NotNull PolyVariantResolver<T> resolver, |
| boolean needToPreventRecursion, |
| boolean incompleteCode, |
| @NotNull PsiFile containingFile) { |
| ResolveResult[] result = resolve(ref, resolver, needToPreventRecursion, incompleteCode, true, containingFile.isPhysical()); |
| return result == null ? ResolveResult.EMPTY_ARRAY : result; |
| } |
| |
| @NotNull |
| public <T extends PsiPolyVariantReference> ResolveResult[] resolveWithCaching(@NotNull final T ref, |
| @NotNull final PolyVariantContextResolver<T> resolver, |
| boolean needToPreventRecursion, |
| final boolean incompleteCode, |
| @NotNull final PsiFile containingFile) { |
| ProgressIndicatorProvider.checkCanceled(); |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| |
| ConcurrentMap<T, Getter<ResolveResult[]>> map = getMap(containingFile.isPhysical(), incompleteCode, true); |
| Getter<ResolveResult[]> reference = map.get(ref); |
| ResolveResult[] result = reference == null ? null : reference.get(); |
| if (result != null) { |
| return result; |
| } |
| |
| RecursionGuard.StackStamp stamp = myGuard.markStack(); |
| result = needToPreventRecursion ? myGuard.doPreventingRecursion(Pair.create(ref, incompleteCode), true, new Computable<ResolveResult[]>() { |
| @Override |
| public ResolveResult[] compute() { |
| return resolver.resolve(ref, containingFile, incompleteCode); |
| } |
| }) : resolver.resolve(ref, containingFile, incompleteCode); |
| |
| if (stamp.mayCacheNow()) { |
| cache(ref, map, result, true); |
| } |
| return result == null ? ResolveResult.EMPTY_ARRAY : result; |
| } |
| |
| @Nullable |
| public <T extends PsiPolyVariantReference> ResolveResult[] getCachedResults(@NotNull T ref, boolean physical, boolean incompleteCode, boolean isPoly) { |
| Map<T, Getter<ResolveResult[]>> map = getMap(physical, incompleteCode, isPoly); |
| Getter<ResolveResult[]> reference = map.get(ref); |
| return reference == null ? null : reference.get(); |
| } |
| |
| @Nullable |
| public <TRef extends PsiReference, TResult> |
| TResult resolveWithCaching(@NotNull TRef ref, |
| @NotNull AbstractResolver<TRef, TResult> resolver, |
| boolean needToPreventRecursion, |
| boolean incompleteCode) { |
| return resolve(ref, resolver, needToPreventRecursion, incompleteCode, false, ref.getElement().isPhysical()); |
| } |
| |
| @NotNull |
| private <TRef extends PsiReference,TResult> ConcurrentMap<TRef, Getter<TResult>> getMap(boolean physical, boolean incompleteCode, boolean isPoly) { |
| //noinspection unchecked |
| return myMaps[(physical ? 0 : 1)*4 + (incompleteCode ? 0 : 1)*2 + (isPoly ? 0 : 1)]; |
| } |
| |
| private static class SoftGetter<T> extends SoftReference<T> implements Getter<T> { |
| public SoftGetter(@NotNull 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 PsiReference, 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); |
| } |
| } |