fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / resolve / JavaResolveCache.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2
3 /*
4  * @author max
5  */
6 package com.intellij.psi.impl.source.resolve;
7
8 import com.intellij.openapi.components.ServiceManager;
9 import com.intellij.openapi.diagnostic.Attachment;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.util.Key;
13 import com.intellij.openapi.util.NotNullLazyKey;
14 import com.intellij.openapi.util.RecursionGuard;
15 import com.intellij.openapi.util.RecursionManager;
16 import com.intellij.psi.*;
17 import com.intellij.psi.impl.AnyPsiChangeListener;
18 import com.intellij.psi.impl.PsiManagerImpl;
19 import com.intellij.psi.impl.source.PsiClassReferenceType;
20 import com.intellij.psi.impl.source.PsiImmediateClassType;
21 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
22 import com.intellij.psi.infos.MethodCandidateInfo;
23 import com.intellij.psi.util.TypeConversionUtil;
24 import com.intellij.util.ConcurrencyUtil;
25 import com.intellij.util.Function;
26 import com.intellij.util.containers.ContainerUtil;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentMap;
33 import java.util.concurrent.atomic.AtomicReference;
34
35 public class JavaResolveCache {
36   private static final Logger LOG = Logger.getInstance(JavaResolveCache.class);
37   private static final NotNullLazyKey<JavaResolveCache, Project> INSTANCE_KEY = ServiceManager.createLazyKey(JavaResolveCache.class);
38
39   public static JavaResolveCache getInstance(Project project) {
40     return INSTANCE_KEY.getValue(project);
41   }
42
43   private final AtomicReference<ConcurrentMap<PsiExpression, PsiType>> myCalculatedTypes = new AtomicReference<>();
44   private final AtomicReference<Map<PsiVariable,Object>> myVarToConstValueMapPhysical = new AtomicReference<>();
45   private final AtomicReference<Map<PsiVariable,Object>> myVarToConstValueMapNonPhysical = new AtomicReference<>();
46
47   private static final Object NULL = Key.create("NULL");
48
49   public JavaResolveCache(@NotNull Project project) {
50     project.getMessageBus().connect().subscribe(PsiManagerImpl.ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
51       @Override
52       public void beforePsiChanged(boolean isPhysical) {
53         clearCaches(isPhysical);
54       }
55     });
56   }
57
58   private void clearCaches(boolean isPhysical) {
59     myCalculatedTypes.set(null);
60     if (isPhysical) {
61       myVarToConstValueMapPhysical.set(null);
62     }
63     myVarToConstValueMapNonPhysical.set(null);
64   }
65
66   @Nullable
67   public <T extends PsiExpression> PsiType getType(@NotNull T expr, @NotNull Function<? super T, ? extends PsiType> f) {
68     ConcurrentMap<PsiExpression, PsiType> map = myCalculatedTypes.get();
69     if (map == null) map = ConcurrencyUtil.cacheOrGet(myCalculatedTypes, ContainerUtil.createConcurrentWeakKeySoftValueMap());
70
71     final boolean prohibitCaching = MethodCandidateInfo.isOverloadCheck() && PsiPolyExpressionUtil.isPolyExpression(expr);
72     PsiType type = prohibitCaching ? null : map.get(expr);
73     if (type == null) {
74       RecursionGuard.StackStamp dStackStamp = RecursionManager.markStack();
75       type = f.fun(expr);
76       if (prohibitCaching || !dStackStamp.mayCacheNow()) {
77         return type;
78       }
79
80       if (type == null) type = TypeConversionUtil.NULL_TYPE;
81       PsiType alreadyCached = map.put(expr, type);
82       if (alreadyCached != null && !type.equals(alreadyCached)) {
83         reportUnstableType(expr, type, alreadyCached);
84       }
85
86       if (type instanceof PsiClassReferenceType) {
87         // convert reference-based class type to the PsiImmediateClassType, since the reference may become invalid
88         PsiClassType.ClassResolveResult result = ((PsiClassReferenceType)type).resolveGenerics();
89         PsiClass psiClass = result.getElement();
90         type = psiClass == null
91                ? type // for type with unresolved reference, leave it in the cache
92                       // for clients still might be able to retrieve its getCanonicalText() from the reference text
93                : new PsiImmediateClassType(psiClass, result.getSubstitutor(), ((PsiClassReferenceType)type).getLanguageLevel(), type.getAnnotationProvider());
94       }
95     }
96
97     return type == TypeConversionUtil.NULL_TYPE ? null : type;
98   }
99
100   private static <T extends PsiExpression> void reportUnstableType(@NotNull PsiExpression expr, @NotNull PsiType type, @NotNull PsiType alreadyCached) {
101     PsiFile file = expr.getContainingFile();
102     LOG.error("Different types returned for the same PSI " + expr.getTextRange() + " on different threads: "
103               + type + " != " + alreadyCached,
104               new Attachment(file.getName(), file.getText()));
105   }
106
107   @Nullable
108   public Object computeConstantValueWithCaching(@NotNull PsiVariable variable, @NotNull ConstValueComputer computer, Set<PsiVariable> visitedVars){
109     boolean physical = variable.isPhysical();
110
111     AtomicReference<Map<PsiVariable, Object>> ref = physical ? myVarToConstValueMapPhysical : myVarToConstValueMapNonPhysical;
112     Map<PsiVariable, Object> map = ref.get();
113     if (map == null) map = ConcurrencyUtil.cacheOrGet(ref, ContainerUtil.createConcurrentWeakMap());
114
115     Object cached = map.get(variable);
116     if (cached == NULL) return null;
117     if (cached != null) return cached;
118
119     Object result = computer.execute(variable, visitedVars);
120     map.put(variable, result == null ? NULL : result);
121     return result;
122   }
123
124   @FunctionalInterface
125   public interface ConstValueComputer{
126     Object execute(@NotNull PsiVariable variable, Set<PsiVariable> visitedVars);
127   }
128 }