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