rename: remember 'Search for text occurrences' checkbox state (IDEA-21328)
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / ExceptionUtil.java
blob7bd9639aa431ad02021c40100088763c1e603526
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.util.Computable;
20 import com.intellij.psi.*;
21 import com.intellij.psi.controlFlow.*;
22 import com.intellij.psi.search.GlobalSearchScope;
23 import com.intellij.psi.util.InheritanceUtil;
24 import com.intellij.psi.util.TypeConversionUtil;
25 import gnu.trove.THashSet;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
30 import java.util.*;
32 /**
33 * @author mike
35 public class ExceptionUtil {
36 @NonNls private static final String CLONE_METHOD_NAME = "clone";
38 private ExceptionUtil() {}
40 @NotNull
41 public static List<PsiClassType> getThrownExceptions(@NotNull PsiElement[] elements) {
42 List<PsiClassType> array = new ArrayList<PsiClassType>();
43 for (PsiElement element : elements) {
44 List<PsiClassType> exceptions = getThrownExceptions(element);
45 addExceptions(array, exceptions);
48 return array;
51 @NotNull
52 public static List<PsiClassType> getThrownCheckedExceptions(@NotNull PsiElement[] elements) {
53 List<PsiClassType> exceptions = getThrownExceptions(elements);
54 if (exceptions.isEmpty()) return exceptions;
55 exceptions = filterOutUncheckedExceptions(exceptions);
56 return exceptions;
59 @NotNull
60 private static List<PsiClassType> filterOutUncheckedExceptions(List<PsiClassType> exceptions) {
61 List<PsiClassType> array = new ArrayList<PsiClassType>();
62 for (PsiClassType exception : exceptions) {
63 if (!isUncheckedException(exception)) array.add(exception);
65 return array;
68 @NotNull
69 private static List<PsiClassType> getThrownExceptions(@NotNull PsiElement element) {
70 if (element instanceof PsiClass) {
71 if (element instanceof PsiAnonymousClass) {
72 final PsiExpressionList argumentList = ((PsiAnonymousClass)element).getArgumentList();
73 if (argumentList != null){
74 return getThrownExceptions(argumentList);
77 // filter class declaration in code
78 return Collections.emptyList();
80 else if (element instanceof PsiMethodCallExpression) {
81 PsiReferenceExpression methodRef = ((PsiMethodCallExpression)element).getMethodExpression();
82 JavaResolveResult result = methodRef.advancedResolve(false);
83 return getExceptionsByMethodAndChildren(element, result);
85 else if (element instanceof PsiNewExpression) {
86 JavaResolveResult result = ((PsiNewExpression)element).resolveMethodGenerics();
87 return getExceptionsByMethodAndChildren(element, result);
89 else if (element instanceof PsiThrowStatement) {
90 PsiExpression expr = ((PsiThrowStatement)element).getException();
91 if (expr == null) return Collections.emptyList();
92 PsiType exception = expr.getType();
93 List<PsiClassType> array = new ArrayList<PsiClassType>();
94 if (exception instanceof PsiClassType) {
95 array.add((PsiClassType)exception);
97 addExceptions(array, getThrownExceptions(expr));
98 return array;
100 else if (element instanceof PsiTryStatement) {
101 PsiTryStatement tryStatement = (PsiTryStatement)element;
102 final PsiCodeBlock tryBlock = tryStatement.getTryBlock();
103 List<PsiClassType> array = new ArrayList<PsiClassType>();
104 if (tryBlock != null) {
105 List<PsiClassType> exceptions = getThrownExceptions(tryBlock);
106 array.addAll(exceptions);
109 PsiParameter[] parameters = tryStatement.getCatchBlockParameters();
110 for (PsiParameter parm : parameters) {
111 PsiType exception = parm.getType();
112 for (int j = array.size() - 1; j >= 0; j--) {
113 PsiClassType exception1 = array.get(j);
114 if (exception.isAssignableFrom(exception1)) {
115 array.remove(exception1);
120 PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks();
121 for (PsiCodeBlock catchBlock : catchBlocks) {
122 addExceptions(array, getThrownExceptions(catchBlock));
125 PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
126 if (finallyBlock != null) {
127 // if finally block completes normally, exception not catched
128 // if finally block completes abruptly, exception gets lost
129 try {
130 ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()).getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false);
131 int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize());
132 List<PsiClassType> thrownExceptions = getThrownExceptions(finallyBlock);
133 if ((completionReasons & ControlFlowUtil.NORMAL_COMPLETION_REASON) == 0) {
134 array = new ArrayList<PsiClassType>(thrownExceptions);
136 else {
137 addExceptions(array, thrownExceptions);
140 catch (AnalysisCanceledException e) {
141 // incomplete code
145 return array;
147 return getThrownExceptions(element.getChildren());
150 @NotNull
151 private static List<PsiClassType> getExceptionsByMethodAndChildren(PsiElement element, JavaResolveResult resolveResult) {
152 PsiMethod method = (PsiMethod)resolveResult.getElement();
153 List<PsiClassType> result = new ArrayList<PsiClassType>();
154 if (method != null) {
155 PsiSubstitutor substitutor = resolveResult.getSubstitutor();
156 final PsiClassType[] referenceTypes = method.getThrowsList().getReferencedTypes();
157 for (PsiType type : referenceTypes) {
158 type = substitutor.substitute(type);
159 if (type instanceof PsiClassType) {
160 result.add((PsiClassType)type);
165 PsiElement[] children = element.getChildren();
166 for (PsiElement child : children) {
167 addExceptions(result, getThrownExceptions(child));
169 return result;
172 private static void addExceptions(List<PsiClassType> array, Collection<PsiClassType> exceptions) {
173 for (PsiClassType exception : exceptions) {
174 addException(array, exception);
178 private static void addException(List<PsiClassType> array, PsiClassType exception) {
179 if (exception == null) return ;
180 for (int i = array.size()-1; i>=0; i--) {
181 PsiClassType exception1 = array.get(i);
182 if (exception1.isAssignableFrom(exception)) return;
183 if (exception.isAssignableFrom(exception1)) {
184 array.remove(i);
187 array.add(exception);
190 @NotNull
191 public static Collection<PsiClassType> collectUnhandledExceptions(@NotNull PsiElement element, PsiElement topElement) {
192 final Set<PsiClassType> set = collectUnhandledExceptions(element, topElement, null);
193 return set == null ? Collections.<PsiClassType>emptyList() : set;
196 @Nullable
197 private static Set<PsiClassType> collectUnhandledExceptions(@NotNull PsiElement element, PsiElement topElement, Set<PsiClassType> foundExceptions) {
198 Collection<PsiClassType> unhandledExceptions = null;
199 if (element instanceof PsiCallExpression) {
200 PsiCallExpression expression = (PsiCallExpression)element;
201 unhandledExceptions = getUnhandledExceptions(expression, topElement);
203 else if (element instanceof PsiThrowStatement) {
204 PsiThrowStatement statement = (PsiThrowStatement)element;
205 PsiClassType exception = getUnhandledException(statement, topElement);
206 unhandledExceptions = exception == null ? Collections.<PsiClassType>emptyList() : Collections.singletonList(exception);
208 else if (element instanceof PsiCodeBlock
209 && element.getParent() instanceof PsiMethod
210 && ((PsiMethod)element.getParent()).isConstructor()
211 && !firstStatementIsConstructorCall((PsiCodeBlock)element)) {
212 // there is implicit parent constructor call
213 final PsiMethod constructor = (PsiMethod)element.getParent();
214 final PsiClass aClass = constructor.getContainingClass();
215 final PsiClass superClass = aClass == null ? null : aClass.getSuperClass();
216 final PsiMethod[] superConstructors = superClass == null ? PsiMethod.EMPTY_ARRAY : superClass.getConstructors();
217 Set<PsiClassType> unhandled = new HashSet<PsiClassType>();
218 for (PsiMethod superConstructor : superConstructors) {
219 if (!superConstructor.hasModifierProperty(PsiModifier.PRIVATE) && superConstructor.getParameterList().getParametersCount() == 0) {
220 final PsiClassType[] exceptionTypes = superConstructor.getThrowsList().getReferencedTypes();
221 for (PsiClassType exceptionType : exceptionTypes) {
222 if (!isUncheckedException(exceptionType) && !isHandled(element, exceptionType, topElement)) {
223 unhandled.add(exceptionType);
226 break;
230 // plus all exceptions thrown in instance class initializers
231 if (aClass != null) {
232 final PsiClassInitializer[] initializers = aClass.getInitializers();
233 final Set<PsiClassType> thrownByInitializer = new THashSet<PsiClassType>();
234 for (PsiClassInitializer initializer : initializers) {
235 if (initializer.hasModifierProperty(PsiModifier.STATIC)) continue;
236 thrownByInitializer.clear();
237 collectUnhandledExceptions(initializer.getBody(), initializer, thrownByInitializer);
238 for (PsiClassType thrown : thrownByInitializer) {
239 if (!isHandled(constructor.getBody(), thrown, topElement)) {
240 unhandled.add(thrown);
245 unhandledExceptions = unhandled;
248 if (unhandledExceptions != null) {
249 if (foundExceptions == null) {
250 foundExceptions = new THashSet<PsiClassType>();
252 foundExceptions.addAll(unhandledExceptions);
255 for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
256 foundExceptions = collectUnhandledExceptions(child, topElement, foundExceptions);
259 return foundExceptions;
262 private static boolean firstStatementIsConstructorCall(PsiCodeBlock constructorBody) {
263 final PsiStatement[] statements = constructorBody.getStatements();
264 if (statements.length == 0) return false;
265 if (!(statements[0] instanceof PsiExpressionStatement)) return false;
267 final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
268 if (!(expression instanceof PsiMethodCallExpression)) return false;
269 final PsiMethod method = (PsiMethod)((PsiMethodCallExpression)expression).getMethodExpression().resolve();
270 return method != null && method.isConstructor();
273 @NotNull
274 public static List<PsiClassType> getUnhandledExceptions(PsiElement[] elements) {
275 final List<PsiClassType> array = new ArrayList<PsiClassType>();
276 PsiElementVisitor visitor = new JavaRecursiveElementWalkingVisitor() {
277 @Override public void visitCallExpression(PsiCallExpression expression) {
278 addExceptions(array, getUnhandledExceptions(expression, null));
279 visitElement(expression);
282 @Override public void visitThrowStatement(PsiThrowStatement statement) {
283 addException(array, getUnhandledException(statement, null));
284 visitElement(statement);
288 for (PsiElement element : elements) {
289 element.accept(visitor);
292 return array;
295 @NotNull
296 public static List<PsiClassType> getUnhandledExceptions(PsiElement element) {
297 if (element instanceof PsiCallExpression) {
298 PsiCallExpression expression = (PsiCallExpression)element;
299 return getUnhandledExceptions(expression, null);
301 else if (element instanceof PsiThrowStatement) {
302 PsiThrowStatement throwStatement = (PsiThrowStatement)element;
303 PsiClassType exception = getUnhandledException(throwStatement, null);
304 if (exception != null) return Collections.singletonList(exception);
307 return Collections.emptyList();
310 @NotNull
311 public static List<PsiClassType> getUnhandledExceptions(PsiCallExpression methodCall, PsiElement topElement) {
312 final JavaResolveResult result = methodCall.resolveMethodGenerics();
313 PsiMethod method = (PsiMethod)result.getElement();
314 return getUnhandledExceptions(method, methodCall, topElement,
315 ApplicationManager.getApplication().runReadAction(new Computable<PsiSubstitutor>() {
316 public PsiSubstitutor compute() {
317 return result.getSubstitutor();
319 }));
322 @Nullable
323 public static PsiClassType getUnhandledException(PsiThrowStatement throwStatement, PsiElement topElement){
324 final PsiExpression exception = throwStatement.getException();
325 if (exception != null) {
326 final PsiType type = exception.getType();
327 if (type instanceof PsiClassType) {
328 PsiClassType classType = (PsiClassType)type;
329 if (!isUncheckedException(classType) && !isHandled(throwStatement, classType, topElement)) {
330 return classType;
334 return null;
338 @NotNull
339 private static List<PsiClassType> getUnhandledExceptions(PsiMethod method,
340 PsiElement element,
341 PsiElement topElement,
342 PsiSubstitutor substitutor) {
343 if (method == null || isArrayClone(method, element)) {
344 return Collections.emptyList();
346 final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes();
347 if (referencedTypes.length > 0) {
348 List<PsiClassType> result = new ArrayList<PsiClassType>();
350 for (PsiClassType referencedType : referencedTypes) {
351 final PsiType type = substitutor.substitute(referencedType);
352 if (!(type instanceof PsiClassType)) continue;
353 PsiClassType classType = (PsiClassType)type;
354 PsiClass exceptionClass = ((PsiClassType)type).resolve();
355 if (exceptionClass == null) continue;
357 if (isUncheckedException(classType)) continue;
358 if (isHandled(element, classType, topElement)) continue;
360 result.add((PsiClassType)type);
363 return result;
365 return Collections.emptyList();
368 private static boolean isArrayClone(PsiMethod method, PsiElement element) {
369 if (!(element instanceof PsiMethodCallExpression)) return false;
370 if (!method.getName().equals(CLONE_METHOD_NAME)) return false;
371 PsiClass containingClass = method.getContainingClass();
372 if (containingClass == null || !"java.lang.Object".equals(containingClass.getQualifiedName())) {
373 return false;
376 PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
377 final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
378 return qualifierExpression != null && qualifierExpression.getType() instanceof PsiArrayType;
381 public static boolean isUncheckedException(PsiClassType type) {
382 final GlobalSearchScope searchScope = type.getResolveScope();
383 final PsiClass aClass = type.resolve();
384 if (aClass == null) return false;
385 final PsiClass runtimeExceptionClass = ApplicationManager.getApplication().runReadAction(
386 new Computable<PsiClass>() {
387 public PsiClass compute() {
388 return JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.RuntimeException", searchScope);
392 if (runtimeExceptionClass != null &&
393 InheritanceUtil.isInheritorOrSelf(aClass, runtimeExceptionClass, true)) {
394 return true;
397 final PsiClass errorClass = ApplicationManager.getApplication().runReadAction(
398 new Computable<PsiClass>() {
399 public PsiClass compute() {
400 return JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.Error", searchScope);
404 return errorClass != null && InheritanceUtil.isInheritorOrSelf(aClass, errorClass, true);
407 public static boolean isUncheckedExceptionOrSuperclass(@NotNull PsiClassType type) {
408 String canonicalText = type.getCanonicalText();
409 return "java.lang.Throwable".equals(canonicalText) ||
410 "java.lang.Exception".equals(canonicalText) ||
411 isUncheckedException(type);
414 public static boolean isHandled(PsiClassType exceptionType, PsiElement throwPlace) {
415 return isHandled(throwPlace, exceptionType, throwPlace.getContainingFile());
418 private static boolean isHandled(PsiElement element, PsiClassType exceptionType, PsiElement topElement) {
419 if (element == null || element.getParent() == topElement || element.getParent() == null) return false;
421 final PsiElement parent = element.getParent();
423 if (parent instanceof PsiMethod) {
424 PsiMethod method = (PsiMethod)parent;
425 return isHandledByMethodThrowsClause(method, exceptionType);
427 else if (parent instanceof PsiClass) {
428 // arguments to anon class constructor should be handled higher
429 // like in void f() throws XXX { new AA(methodThrowingXXX()) { ... }; }
430 return parent instanceof PsiAnonymousClass && isHandled(parent, exceptionType, topElement);
432 else if (parent instanceof PsiClassInitializer) {
433 if (((PsiClassInitializer)parent).hasModifierProperty(PsiModifier.STATIC)) return false;
434 // anonymous class initializers can throw any exceptions
435 if (!(parent.getParent() instanceof PsiAnonymousClass)) {
436 // exception thrown from within class instance initializer must be handled in every class constructor
437 // check each constructor throws exception or superclass (there must be at least one)
438 final PsiClass aClass = ((PsiClassInitializer)parent).getContainingClass();
439 return areAllConstructorsThrow(aClass, exceptionType);
442 else if (parent instanceof PsiTryStatement) {
443 PsiTryStatement tryStatement = (PsiTryStatement)parent;
444 if (tryStatement.getTryBlock() == element && isCatched(tryStatement, exceptionType)) {
445 return true;
447 PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
448 if (element instanceof PsiCatchSection && finallyBlock != null && blockCompletesAbruptly(finallyBlock)) {
449 // exception swallowed
450 return true;
453 else if (parent instanceof JavaCodeFragment) {
454 JavaCodeFragment codeFragment = (JavaCodeFragment)parent;
455 JavaCodeFragment.ExceptionHandler exceptionHandler = codeFragment.getExceptionHandler();
456 return exceptionHandler != null && exceptionHandler.isHandledException(exceptionType);
458 else if (JspPsiUtil.isInJspFile(parent) && parent instanceof PsiFile) {
459 return true;
461 else if (parent instanceof PsiFile) {
462 return false;
464 else if (parent instanceof PsiField && ((PsiField)parent).getInitializer() == element) {
465 final PsiClass aClass = ((PsiField)parent).getContainingClass();
466 if (aClass != null && !(aClass instanceof PsiAnonymousClass) && !((PsiField)parent).hasModifierProperty(PsiModifier.STATIC)) {
467 // exceptions thrown in field initalizers should be thrown in all class constructors
468 return areAllConstructorsThrow(aClass, exceptionType);
471 return isHandled(parent, exceptionType, topElement);
474 private static boolean areAllConstructorsThrow(final PsiClass aClass, PsiClassType exceptionType) {
475 if (aClass == null) return false;
476 final PsiMethod[] constructors = aClass.getConstructors();
477 boolean thrown = constructors.length != 0;
478 for (PsiMethod constructor : constructors) {
479 if (!isHandledByMethodThrowsClause(constructor, exceptionType)) {
480 thrown = false;
481 break;
484 return thrown;
487 private static boolean isCatched(PsiTryStatement tryStatement, PsiClassType exceptionType) {
488 PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
489 if (finallyBlock != null) {
490 List<PsiClassType> exceptions = getUnhandledExceptions(finallyBlock);
491 if (exceptions.contains(exceptionType)) return false;
492 // if finally block completes normally, exception not catched
493 // if finally block completes abruptly, exception gets lost
494 if (blockCompletesAbruptly(finallyBlock)) return true;
497 final PsiParameter[] catchBlockParameters = tryStatement.getCatchBlockParameters();
498 for (PsiParameter parameter : catchBlockParameters) {
499 PsiType paramType = parameter.getType();
500 if (paramType.isAssignableFrom(exceptionType)) return true;
503 return false;
506 private static boolean blockCompletesAbruptly(final PsiCodeBlock finallyBlock) {
507 try {
508 ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()).getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false);
509 int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize());
510 if ((completionReasons & ControlFlowUtil.NORMAL_COMPLETION_REASON) == 0) return true;
512 catch (AnalysisCanceledException e) {
513 return true;
515 return false;
518 private static boolean isHandledByMethodThrowsClause(PsiMethod method, PsiClassType exceptionType) {
519 final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes();
520 return isHandledBy(exceptionType, referencedTypes);
523 public static boolean isHandledBy(PsiClassType exceptionType, @NotNull PsiClassType[] referencedTypes) {
524 for (PsiClassType classType : referencedTypes) {
525 if (classType.isAssignableFrom(exceptionType)) return true;
527 return false;
530 public static void sortExceptionsByHierarchy(List<PsiClassType> exceptions) {
531 if (exceptions.size() <= 1) return;
532 sortExceptionsByHierarchy(exceptions.subList(1, exceptions.size()));
533 for (int i=0; i<exceptions.size()-1;i++) {
534 if (TypeConversionUtil.isAssignable(exceptions.get(i), exceptions.get(i+1))) {
535 Collections.swap(exceptions, i,i+1);