fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / controlFlow / ControlFlowFactory.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 package com.intellij.psi.controlFlow;
3
4 import com.intellij.openapi.components.ServiceManager;
5 import com.intellij.openapi.project.Project;
6 import com.intellij.openapi.util.NotNullLazyKey;
7 import com.intellij.psi.PsiElement;
8 import com.intellij.psi.impl.PsiManagerEx;
9 import com.intellij.util.containers.ConcurrentList;
10 import com.intellij.util.containers.ContainerUtil;
11 import org.jetbrains.annotations.NotNull;
12
13 import java.util.Map;
14
15 public final class ControlFlowFactory {
16   // psiElements hold weakly, controlFlows softly
17   private final Map<PsiElement, ConcurrentList<ControlFlowContext>> cachedFlows = ContainerUtil.createConcurrentWeakKeySoftValueMap();
18
19   private static final NotNullLazyKey<ControlFlowFactory, Project> INSTANCE_KEY = ServiceManager.createLazyKey(ControlFlowFactory.class);
20
21   public static ControlFlowFactory getInstance(Project project) {
22     return INSTANCE_KEY.getValue(project);
23   }
24
25   public ControlFlowFactory(@NotNull Project project) {
26     PsiManagerEx.getInstanceEx(project).registerRunnableToRunOnChange(() -> clearCache());
27   }
28
29   private void clearCache() {
30     cachedFlows.clear();
31   }
32
33   void registerSubRange(final PsiElement codeFragment,
34                         final ControlFlowSubRange flow,
35                         final boolean evaluateConstantIfConfition,
36                         boolean enableShortCircuit, final ControlFlowPolicy policy) {
37     registerControlFlow(codeFragment, flow, evaluateConstantIfConfition, enableShortCircuit, policy);
38   }
39
40   private static class ControlFlowContext {
41     private final ControlFlowPolicy policy;
42     private final boolean evaluateConstantIfCondition;
43     private final boolean enableShortCircuit;
44     private final long modificationCount;
45     private final ControlFlow controlFlow;
46
47     private ControlFlowContext(boolean evaluateConstantIfCondition, boolean enableShortCircuit, @NotNull ControlFlowPolicy policy, long modificationCount, @NotNull ControlFlow controlFlow) {
48       this.evaluateConstantIfCondition = evaluateConstantIfCondition;
49       this.enableShortCircuit = enableShortCircuit;
50       this.policy = policy;
51       this.modificationCount = modificationCount;
52       this.controlFlow = controlFlow;
53     }
54
55     @Override
56     public boolean equals(final Object o) {
57       if (this == o) return true;
58       if (o == null || getClass() != o.getClass()) return false;
59
60       final ControlFlowContext that = (ControlFlowContext)o;
61
62       return isFor(that);
63     }
64
65     @Override
66     public int hashCode() {
67       int result = policy.hashCode();
68       result = 31 * result + (evaluateConstantIfCondition ? 1 : 0);
69       result = 31 * result + (int)(modificationCount ^ (modificationCount >>> 32));
70       return result;
71     }
72
73     private boolean isFor(@NotNull ControlFlowPolicy policy,
74                           final boolean evaluateConstantIfCondition,
75                           final boolean enableShortCircuit,
76                           long modificationCount) {
77       if (modificationCount != this.modificationCount) return false;
78       if (!policy.equals(this.policy)) return false;
79       if (enableShortCircuit != this.enableShortCircuit) return false;
80
81       // optimization: when no constant condition were computed, both control flows are the same
82       if (this.evaluateConstantIfCondition && !controlFlow.isConstantConditionOccurred()) return true;
83
84       return evaluateConstantIfCondition == this.evaluateConstantIfCondition;
85     }
86
87     private boolean isFor(@NotNull ControlFlowContext that) {
88       return isFor(that.policy, that.evaluateConstantIfCondition, that.enableShortCircuit, that.modificationCount);
89     }
90   }
91
92   @NotNull
93   public ControlFlow getControlFlow(@NotNull PsiElement element, @NotNull ControlFlowPolicy policy) throws AnalysisCanceledException {
94     return getControlFlow(element, policy, true, true);
95   }
96
97   @NotNull
98   public ControlFlow getControlFlow(@NotNull PsiElement element, @NotNull ControlFlowPolicy policy, boolean evaluateConstantIfCondition) throws AnalysisCanceledException {
99     return getControlFlow(element, policy, true, evaluateConstantIfCondition);
100   }
101
102   @NotNull
103   public ControlFlow getControlFlow(@NotNull PsiElement element,
104                                     @NotNull ControlFlowPolicy policy,
105                                     boolean enableShortCircuit,
106                                     boolean evaluateConstantIfCondition) throws AnalysisCanceledException {
107     if (!element.isPhysical()) {
108       return new ControlFlowAnalyzer(element, policy, enableShortCircuit, evaluateConstantIfCondition).buildControlFlow();
109     }
110     final long modificationCount = element.getManager().getModificationTracker().getModificationCount();
111     ConcurrentList<ControlFlowContext> cached = getOrCreateCachedFlowsForElement(element);
112     for (ControlFlowContext context : cached) {
113       if (context.isFor(policy, evaluateConstantIfCondition, enableShortCircuit, modificationCount)) return context.controlFlow;
114     }
115     ControlFlow controlFlow = new ControlFlowAnalyzer(element, policy, enableShortCircuit, evaluateConstantIfCondition).buildControlFlow();
116     ControlFlowContext context = createContext(evaluateConstantIfCondition, enableShortCircuit, policy, controlFlow, modificationCount);
117     cached.addIfAbsent(context);
118     return controlFlow;
119   }
120
121   @NotNull
122   private static ControlFlowContext createContext(final boolean evaluateConstantIfCondition,
123                                                   boolean enableShortCircuit,
124                                                   @NotNull ControlFlowPolicy policy,
125                                                   @NotNull ControlFlow controlFlow,
126                                                   final long modificationCount) {
127     return new ControlFlowContext(evaluateConstantIfCondition, enableShortCircuit, policy, modificationCount,controlFlow);
128   }
129
130   private void registerControlFlow(@NotNull PsiElement element,
131                                    @NotNull ControlFlow flow,
132                                    boolean evaluateConstantIfCondition,
133                                    boolean enableShortCircuit,
134                                    @NotNull ControlFlowPolicy policy) {
135     final long modificationCount = element.getManager().getModificationTracker().getModificationCount();
136     ControlFlowContext controlFlowContext = createContext(evaluateConstantIfCondition, enableShortCircuit, policy, flow, modificationCount);
137
138     ConcurrentList<ControlFlowContext> cached = getOrCreateCachedFlowsForElement(element);
139     cached.addIfAbsent(controlFlowContext);
140   }
141
142   @NotNull
143   private ConcurrentList<ControlFlowContext> getOrCreateCachedFlowsForElement(@NotNull PsiElement element) {
144     return cachedFlows.computeIfAbsent(element, __ -> ContainerUtil.createConcurrentList());
145   }
146 }
147