Cleanup: NotNull/Nullable
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / tree / java / PsiCatchSectionImpl.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.psi.impl.source.tree.java;
17
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.pom.java.LanguageLevel;
22 import com.intellij.psi.*;
23 import com.intellij.psi.impl.source.Constants;
24 import com.intellij.psi.impl.source.tree.ChildRole;
25 import com.intellij.psi.impl.source.tree.CompositePsiElement;
26 import com.intellij.psi.scope.PsiScopeProcessor;
27 import com.intellij.psi.scope.util.PsiScopesUtil;
28 import com.intellij.psi.tree.ChildRoleBase;
29 import com.intellij.psi.tree.IElementType;
30 import com.intellij.psi.util.*;
31 import com.intellij.util.ArrayUtil;
32 import com.intellij.util.NullableFunction;
33 import com.intellij.util.containers.ContainerUtil;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.List;
41
42 /**
43  * @author ven
44  */
45 public class PsiCatchSectionImpl extends CompositePsiElement implements PsiCatchSection, Constants {
46   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiCatchSectionImpl");
47
48   private final Object myTypesCacheLock = new Object();
49   private CachedValue<List<PsiType>> myTypesCache;
50
51   public PsiCatchSectionImpl() {
52     super(CATCH_SECTION);
53   }
54
55   @Override
56   public PsiParameter getParameter() {
57     return (PsiParameter)findChildByRoleAsPsiElement(ChildRole.PARAMETER);
58   }
59
60   @Override
61   public PsiCodeBlock getCatchBlock() {
62     return (PsiCodeBlock)findChildByRoleAsPsiElement(ChildRole.CATCH_BLOCK);
63   }
64
65   @Override
66   public PsiType getCatchType() {
67     PsiParameter parameter = getParameter();
68     if (parameter == null) return null;
69     return parameter.getType();
70   }
71
72   @Override
73   @NotNull
74   public List<PsiType> getPreciseCatchTypes() {
75     final PsiParameter parameter = getParameter();
76     if (parameter == null) return Collections.emptyList();
77
78     return getTypesCache().getValue();
79   }
80
81   @Override
82   public void clearCaches() {
83     super.clearCaches();
84     synchronized (myTypesCacheLock) {
85       myTypesCache = null;
86     }
87   }
88
89   private CachedValue<List<PsiType>> getTypesCache() {
90     synchronized (myTypesCacheLock) {
91       if (myTypesCache == null) {
92         final CachedValuesManager cacheManager = CachedValuesManager.getManager(getProject());
93         myTypesCache = cacheManager.createCachedValue(() -> {
94           final List<PsiType> types = computePreciseCatchTypes(getParameter());
95           return CachedValueProvider.Result.create(types, PsiModificationTracker.MODIFICATION_COUNT);
96         }, false);
97       }
98       return myTypesCache;
99     }
100   }
101
102   private List<PsiType> computePreciseCatchTypes(@Nullable final PsiParameter parameter) {
103     if (parameter == null) {
104       return ContainerUtil.emptyList();
105     }
106
107     PsiType declaredType = parameter.getType();
108
109     // When the thrown expression is an ... exception parameter Ej (parameter) of a catch clause Cj (this) ...
110     if (PsiUtil.getLanguageLevel(parameter).isAtLeast(LanguageLevel.JDK_1_7) &&
111         isCatchParameterEffectivelyFinal(parameter, getCatchBlock())) {
112       PsiTryStatement statement = getTryStatement();
113       // ... and the try block of the try statement which declares Cj (tryBlock) can throw T ...
114       Collection<PsiClassType> thrownTypes = getThrownTypes(statement);
115       if (thrownTypes.isEmpty()) return Collections.emptyList();
116       // ... and for all exception parameters Ei declared by any catch clauses Ci, 1 <= i < j,
117       //     declared to the left of Cj for the same try statement, T is not assignable to Ei ...
118       final PsiParameter[] parameters = statement.getCatchBlockParameters();
119       final int currentIdx = ArrayUtil.find(parameters, parameter);
120       List<PsiType> uncaughtTypes = ContainerUtil.mapNotNull(thrownTypes, (NullableFunction<PsiClassType, PsiType>)thrownType -> {
121         for (int i = 0; i < currentIdx; i++) {
122           final PsiType catchType = parameters[i].getType();
123           if (catchType.isAssignableFrom(thrownType)) return null;
124         }
125         return thrownType;
126       });
127       if (uncaughtTypes.isEmpty()) return Collections.emptyList();  // unreachable catch section
128       // ... and T is assignable to Ej ...
129       List<PsiType> types = new ArrayList<>();
130       for (PsiType type : uncaughtTypes) {
131         if (declaredType.isAssignableFrom(type) ||
132             // JLS 11.2.3 "Exception Checking":
133             // "It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case
134             // that the try block corresponding to the catch clause can throw a checked exception class that is
135             // a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception."
136             // So here unchecked exception can sneak through Exception or Throwable catch type only.
137             ExceptionUtil.isGeneralExceptionType(declaredType) && type instanceof PsiClassType && ExceptionUtil.isUncheckedException((PsiClassType)type)) {
138           types.add(type);
139         }
140       }
141       // ... the throw statement throws precisely the set of exception types T.
142       if (!types.isEmpty()) return types;
143     }
144
145     return Collections.singletonList(declaredType);
146   }
147
148   private static Collection<PsiClassType> getThrownTypes(@NotNull PsiTryStatement statement) {
149     Collection<PsiClassType> types = ContainerUtil.newArrayList();
150     PsiCodeBlock tryBlock = statement.getTryBlock();
151     if (tryBlock != null) {
152       types.addAll(ExceptionUtil.getThrownExceptions(tryBlock));
153     }
154     PsiResourceList resourceList = statement.getResourceList();
155     if (resourceList != null) {
156       types.addAll(ExceptionUtil.getThrownExceptions(resourceList));
157     }
158     return types;
159   }
160
161   // do not use control flow here to avoid dead loop
162   private static boolean isCatchParameterEffectivelyFinal(final PsiParameter parameter, @Nullable final PsiCodeBlock catchBlock) {
163     final boolean[] result = {true};
164     if (catchBlock != null) {
165       catchBlock.accept(new JavaRecursiveElementWalkingVisitor() {
166         @Override
167         public void visitReferenceExpression(PsiReferenceExpression expression) {
168           super.visitReferenceExpression(expression);
169           if (expression.resolve() == parameter && PsiUtil.isAccessedForWriting(expression)) {
170             result[0] = false;
171             stopWalking();
172           }
173         }
174       });
175     }
176     return result[0];
177   }
178
179   @Override
180   @NotNull
181   public PsiTryStatement getTryStatement() {
182     return (PsiTryStatement)getParent();
183   }
184
185   @Override
186   @Nullable
187   public PsiJavaToken getLParenth() {
188     return (PsiJavaToken)findChildByRole(ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH);
189   }
190
191   @Override
192   @Nullable
193   public PsiJavaToken getRParenth() {
194     return (PsiJavaToken)findChildByRole(ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH);
195   }
196
197   @Override
198   public void accept(@NotNull PsiElementVisitor visitor) {
199     if (visitor instanceof JavaElementVisitor) {
200       ((JavaElementVisitor)visitor).visitCatchSection(this);
201     }
202     else {
203       visitor.visitElement(this);
204     }
205   }
206
207   @Override
208   public String toString() {
209     return "PsiCatchSection";
210   }
211
212   @Override
213   public ASTNode findChildByRole(int role) {
214     switch(role) {
215       default:
216         return null;
217
218       case ChildRole.PARAMETER:
219         return findChildByType(PARAMETER);
220
221       case ChildRole.CATCH_KEYWORD:
222         return findChildByType(CATCH_KEYWORD);
223
224       case ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH:
225         return findChildByType(LPARENTH);
226
227       case ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH:
228         return findChildByType(RPARENTH);
229
230       case ChildRole.CATCH_BLOCK:
231         return findChildByType(CODE_BLOCK);
232     }
233   }
234
235   @Override
236   public int getChildRole(@NotNull ASTNode child) {
237     LOG.assertTrue(child.getTreeParent() == this);
238     IElementType i = child.getElementType();
239     if (i == PARAMETER) {
240       return ChildRole.PARAMETER;
241     }
242     if (i == CODE_BLOCK) {
243       return ChildRole.CATCH_BLOCK;
244     }
245     if (i == CATCH_KEYWORD) {
246       return ChildRole.CATCH_KEYWORD;
247     }
248     if (i == LPARENTH) {
249       return ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH;
250     }
251     if (i == RPARENTH) {
252       return ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH;
253     }
254
255     return ChildRoleBase.NONE;
256   }
257
258   @Override
259   public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
260                                      @NotNull ResolveState state,
261                                      PsiElement lastParent,
262                                      @NotNull PsiElement place) {
263     processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this);
264     if (lastParent == null || lastParent.getParent() != this)
265       // Parent element should not see our vars
266       return true;
267
268     final PsiParameter catchParameter = getParameter();
269     if (catchParameter != null) {
270       return processor.execute(catchParameter, state);
271     }
272
273     return PsiScopesUtil.walkChildrenScopes(this, processor, state, lastParent, place);
274   }
275 }