750ed0f6f197f26d33a658aa83b2816af9270633
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / resolve / JavaResolveCache.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * @author max
19  */
20 package com.intellij.psi.impl.source.resolve;
21
22 import com.intellij.openapi.components.ServiceManager;
23 import com.intellij.openapi.diagnostic.Attachment;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.openapi.util.NotNullLazyKey;
28 import com.intellij.openapi.util.RecursionGuard;
29 import com.intellij.openapi.util.RecursionManager;
30 import com.intellij.psi.*;
31 import com.intellij.psi.impl.AnyPsiChangeListener;
32 import com.intellij.psi.impl.PsiManagerImpl;
33 import com.intellij.psi.impl.source.PsiClassReferenceType;
34 import com.intellij.psi.impl.source.PsiImmediateClassType;
35 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
36 import com.intellij.psi.infos.MethodCandidateInfo;
37 import com.intellij.psi.util.TypeConversionUtil;
38 import com.intellij.util.ConcurrencyUtil;
39 import com.intellij.util.Function;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.messages.MessageBus;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.concurrent.ConcurrentMap;
48 import java.util.concurrent.atomic.AtomicReference;
49
50 public class JavaResolveCache {
51   private static final Logger LOG = Logger.getInstance(JavaResolveCache.class);
52   private static final NotNullLazyKey<JavaResolveCache, Project> INSTANCE_KEY = ServiceManager.createLazyKey(JavaResolveCache.class);
53
54   public static JavaResolveCache getInstance(Project project) {
55     return INSTANCE_KEY.getValue(project);
56   }
57
58   private final AtomicReference<ConcurrentMap<PsiExpression, PsiType>> myCalculatedTypes = new AtomicReference<>();
59   private final AtomicReference<Map<PsiVariable,Object>> myVarToConstValueMapPhysical = new AtomicReference<>();
60   private final AtomicReference<Map<PsiVariable,Object>> myVarToConstValueMapNonPhysical = new AtomicReference<>();
61
62   private static final Object NULL = Key.create("NULL");
63
64   public JavaResolveCache(@Nullable("can be null in com.intellij.core.JavaCoreApplicationEnvironment.JavaCoreApplicationEnvironment") MessageBus messageBus) {
65     if (messageBus != null) {
66       messageBus.connect().subscribe(PsiManagerImpl.ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
67         @Override
68         public void beforePsiChanged(boolean isPhysical) {
69           clearCaches(isPhysical);
70         }
71       });
72     }
73   }
74
75   private void clearCaches(boolean isPhysical) {
76     myCalculatedTypes.set(null);
77     if (isPhysical) {
78       myVarToConstValueMapPhysical.set(null);
79     }
80     myVarToConstValueMapNonPhysical.set(null);
81   }
82
83   @Nullable
84   public <T extends PsiExpression> PsiType getType(@NotNull T expr, @NotNull Function<? super T, ? extends PsiType> f) {
85     ConcurrentMap<PsiExpression, PsiType> map = myCalculatedTypes.get();
86     if (map == null) map = ConcurrencyUtil.cacheOrGet(myCalculatedTypes, ContainerUtil.createConcurrentWeakKeySoftValueMap());
87
88     final boolean prohibitCaching = MethodCandidateInfo.isOverloadCheck() && PsiPolyExpressionUtil.isPolyExpression(expr);
89     PsiType type = prohibitCaching ? null : map.get(expr);
90     if (type == null) {
91       RecursionGuard.StackStamp dStackStamp = RecursionManager.markStack();
92       type = f.fun(expr);
93       if (prohibitCaching || !dStackStamp.mayCacheNow()) {
94         return type;
95       }
96
97       if (type == null) type = TypeConversionUtil.NULL_TYPE;
98       PsiType alreadyCached = map.put(expr, type);
99       if (alreadyCached != null && !type.equals(alreadyCached)) {
100         reportUnstableType(expr, type, alreadyCached);
101       }
102
103       if (type instanceof PsiClassReferenceType) {
104         // convert reference-based class type to the PsiImmediateClassType, since the reference may become invalid
105         PsiClassType.ClassResolveResult result = ((PsiClassReferenceType)type).resolveGenerics();
106         PsiClass psiClass = result.getElement();
107         type = psiClass == null
108                ? type // for type with unresolved reference, leave it in the cache
109                       // for clients still might be able to retrieve its getCanonicalText() from the reference text
110                : new PsiImmediateClassType(psiClass, result.getSubstitutor(), ((PsiClassReferenceType)type).getLanguageLevel(), type.getAnnotationProvider());
111       }
112     }
113
114     return type == TypeConversionUtil.NULL_TYPE ? null : type;
115   }
116
117   private static <T extends PsiExpression> void reportUnstableType(@NotNull PsiExpression expr, @NotNull PsiType type, @NotNull PsiType alreadyCached) {
118     PsiFile file = expr.getContainingFile();
119     LOG.error("Different types returned for the same PSI " + expr.getTextRange() + " on different threads: "
120               + type + " != " + alreadyCached,
121               new Attachment(file.getName(), file.getText()));
122   }
123
124   @Nullable
125   public Object computeConstantValueWithCaching(@NotNull PsiVariable variable, @NotNull ConstValueComputer computer, Set<PsiVariable> visitedVars){
126     boolean physical = variable.isPhysical();
127
128     AtomicReference<Map<PsiVariable, Object>> ref = physical ? myVarToConstValueMapPhysical : myVarToConstValueMapNonPhysical;
129     Map<PsiVariable, Object> map = ref.get();
130     if (map == null) map = ConcurrencyUtil.cacheOrGet(ref, ContainerUtil.createConcurrentWeakMap());
131
132     Object cached = map.get(variable);
133     if (cached == NULL) return null;
134     if (cached != null) return cached;
135
136     Object result = computer.execute(variable, visitedVars);
137     map.put(variable, result == null ? NULL : result);
138     return result;
139   }
140
141   @FunctionalInterface
142   public interface ConstValueComputer{
143     Object execute(@NotNull PsiVariable variable, Set<PsiVariable> visitedVars);
144   }
145 }