| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.siyeh.ig.migration; |
| |
| import com.intellij.codeInspection.ProblemDescriptor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.*; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.siyeh.HardcodedMethodConstants; |
| import com.siyeh.InspectionGadgetsBundle; |
| import com.siyeh.ig.BaseInspection; |
| import com.siyeh.ig.BaseInspectionVisitor; |
| import com.siyeh.ig.InspectionGadgetsFix; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author Bas Leijdekkers |
| */ |
| public class TryFinallyCanBeTryWithResourcesInspection extends BaseInspection { |
| |
| @Override |
| public boolean isEnabledByDefault() { |
| return true; |
| } |
| |
| @Nls |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return InspectionGadgetsBundle.message("try.finally.can.be.try.with.resources.display.name"); |
| } |
| |
| @NotNull |
| @Override |
| protected String buildErrorString(Object... infos) { |
| return InspectionGadgetsBundle.message("try.finally.can.be.try.with.resources.problem.descriptor"); |
| } |
| |
| @Override |
| protected InspectionGadgetsFix buildFix(Object... infos) { |
| return new TryFinallyCanBeTryWithResourcesFix(); |
| } |
| |
| private static class TryFinallyCanBeTryWithResourcesFix extends InspectionGadgetsFix { |
| |
| public TryFinallyCanBeTryWithResourcesFix() {} |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return getName(); |
| } |
| |
| @Override |
| @NotNull |
| public String getName() { |
| return InspectionGadgetsBundle.message("try.finally.can.be.try.with.resources.quickfix"); |
| } |
| |
| @Override |
| protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException { |
| final PsiElement element = descriptor.getPsiElement(); |
| final PsiElement parent = element.getParent(); |
| if (!(parent instanceof PsiTryStatement)) { |
| return; |
| } |
| final PsiTryStatement tryStatement = (PsiTryStatement)parent; |
| final PsiCodeBlock tryBlock = tryStatement.getTryBlock(); |
| if (tryBlock == null) { |
| return; |
| } |
| final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); |
| if (finallyBlock == null) { |
| return; |
| } |
| final PsiElement[] tryBlockChildren = tryBlock.getChildren(); |
| final Set<PsiLocalVariable> variables = new HashSet(); |
| for (final PsiLocalVariable variable : collectVariables(tryStatement)) { |
| if (!isVariableUsedOutsideContext(variable, tryBlock)) { |
| variables.add(variable); |
| } |
| } |
| final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); |
| @NonNls final StringBuilder newTryStatementText = new StringBuilder("try ("); |
| final Set<Integer> unwantedChildren = new HashSet(2); |
| boolean separator = false; |
| for (final PsiLocalVariable variable : variables) { |
| final boolean hasInitializer; |
| final PsiExpression initializer = variable.getInitializer(); |
| if (initializer == null) { |
| hasInitializer = false; |
| } |
| else { |
| final PsiType type = initializer.getType(); |
| hasInitializer = !PsiType.NULL.equals(type); |
| } |
| if (separator) { |
| newTryStatementText.append(';'); |
| } |
| newTryStatementText.append(variable.getTypeElement().getText()).append(' ').append(variable.getName()).append('='); |
| if (hasInitializer) { |
| newTryStatementText.append(initializer.getText()); |
| } |
| else { |
| final int index = findInitialization(tryBlockChildren, variable, hasInitializer); |
| if (index < 0) { |
| return; |
| } |
| unwantedChildren.add(Integer.valueOf(index)); |
| final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)tryBlockChildren[index]; |
| if (expressionStatement.getNextSibling() instanceof PsiWhiteSpace) { |
| unwantedChildren.add(Integer.valueOf(index + 1)); |
| } |
| final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expressionStatement.getExpression(); |
| final PsiExpression rhs = assignmentExpression.getRExpression(); |
| if (rhs == null) { |
| return; |
| } |
| newTryStatementText.append(rhs.getText()); |
| } |
| separator = true; |
| } |
| newTryStatementText.append(") {"); |
| final int tryBlockStatementsLength = tryBlockChildren.length - 1; |
| for (int i = 1; i < tryBlockStatementsLength; i++) { |
| final PsiElement child = tryBlockChildren[i]; |
| if (unwantedChildren.contains(Integer.valueOf(i))) { |
| continue; |
| } |
| newTryStatementText.append(child.getText()); |
| } |
| newTryStatementText.append('}'); |
| final PsiCatchSection[] catchSections = tryStatement.getCatchSections(); |
| for (final PsiCatchSection catchSection : catchSections) { |
| newTryStatementText.append(catchSection.getText()); |
| } |
| final PsiElement[] finallyChildren = finallyBlock.getChildren(); |
| boolean appended = false; |
| final int finallyChildrenLength = finallyChildren.length - 1; |
| final List<PsiElement> savedComments = new ArrayList(); |
| for (int i = 1; i < finallyChildrenLength; i++) { |
| final PsiElement child = finallyChildren[i]; |
| if (isCloseStatement(child, variables)) { |
| continue; |
| } |
| if (!appended) { |
| if (child instanceof PsiComment) { |
| final PsiComment comment = (PsiComment)child; |
| final PsiElement prevSibling = child.getPrevSibling(); |
| if (prevSibling instanceof PsiWhiteSpace && savedComments.isEmpty()) { |
| savedComments.add(prevSibling); |
| } |
| savedComments.add(comment); |
| final PsiElement nextSibling = child.getNextSibling(); |
| if (nextSibling instanceof PsiWhiteSpace) { |
| savedComments.add(nextSibling); |
| } |
| } |
| else if (!(child instanceof PsiWhiteSpace)) { |
| newTryStatementText.append(" finally {"); |
| for (final PsiElement savedComment : savedComments) { |
| newTryStatementText.append(savedComment.getText()); |
| } |
| newTryStatementText.append(child.getText()); |
| appended = true; |
| } |
| } |
| else { |
| newTryStatementText.append(child.getText()); |
| } |
| } |
| if (appended) { |
| newTryStatementText.append('}'); |
| } |
| for (final PsiLocalVariable variable : variables) { |
| variable.delete(); |
| } |
| if (!appended) { |
| final int savedCommentsSize = savedComments.size(); |
| final PsiElement parent1 = tryStatement.getParent(); |
| for (int i = savedCommentsSize - 1; i >= 0; i--) { |
| final PsiElement savedComment = savedComments.get(i); |
| parent1.addAfter(savedComment, tryStatement); |
| } |
| } |
| final PsiStatement newTryStatement = factory.createStatementFromText(newTryStatementText.toString(), element); |
| tryStatement.replace(newTryStatement); |
| } |
| |
| private static boolean isCloseStatement(PsiElement element, Set<PsiLocalVariable> variables) { |
| if (element instanceof PsiExpressionStatement) { |
| final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)element; |
| final PsiExpression expression = expressionStatement.getExpression(); |
| if (!(expression instanceof PsiMethodCallExpression)) { |
| return false; |
| } |
| final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; |
| final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); |
| final String methodName = methodExpression.getReferenceName(); |
| if (!HardcodedMethodConstants.CLOSE.equals(methodName)) { |
| return false; |
| } |
| final PsiExpression qualifier = methodExpression.getQualifierExpression(); |
| if (!(qualifier instanceof PsiReferenceExpression)) { |
| return false; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifier; |
| final PsiElement target = referenceExpression.resolve(); |
| if (!(target instanceof PsiLocalVariable)) { |
| return false; |
| } |
| final PsiLocalVariable variable = (PsiLocalVariable)target; |
| return variables.contains(variable); |
| } |
| else if (element instanceof PsiIfStatement) { |
| final PsiIfStatement ifStatement = (PsiIfStatement)element; |
| if (ifStatement.getElseBranch() != null) { |
| return false; |
| } |
| final PsiExpression condition = ifStatement.getCondition(); |
| if (!(condition instanceof PsiBinaryExpression)) { |
| return false; |
| } |
| final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)condition; |
| final IElementType tokenType = binaryExpression.getOperationTokenType(); |
| if (!JavaTokenType.NE.equals(tokenType)) { |
| return false; |
| } |
| final PsiExpression lhs = binaryExpression.getLOperand(); |
| final PsiExpression rhs = binaryExpression.getROperand(); |
| if (rhs == null) { |
| return false; |
| } |
| final PsiElement variable; |
| if (PsiType.NULL.equals(rhs.getType())) { |
| if (!(lhs instanceof PsiReferenceExpression)) { |
| return false; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lhs; |
| variable = referenceExpression.resolve(); |
| if (!(variable instanceof PsiLocalVariable)) { |
| return false; |
| } |
| } |
| else if (PsiType.NULL.equals(lhs.getType())) { |
| if (!(rhs instanceof PsiReferenceExpression)) { |
| return false; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)rhs; |
| variable = referenceExpression.resolve(); |
| if (!(variable instanceof PsiLocalVariable)) { |
| return false; |
| } |
| } |
| else { |
| return false; |
| } |
| final PsiStatement thenBranch = ifStatement.getThenBranch(); |
| if (thenBranch instanceof PsiExpressionStatement) { |
| return isCloseStatement(thenBranch, variables); |
| } |
| else if (thenBranch instanceof PsiBlockStatement) { |
| final PsiBlockStatement blockStatement = (PsiBlockStatement)thenBranch; |
| final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); |
| final PsiStatement[] statements = codeBlock.getStatements(); |
| return statements.length == 1 && isCloseStatement(statements[0], variables); |
| } |
| else { |
| return false; |
| } |
| } |
| else { |
| return false; |
| } |
| } |
| } |
| |
| @Override |
| public BaseInspectionVisitor buildVisitor() { |
| return new TryFinallyCanBeTryWithResourcesVisitor(); |
| } |
| |
| private static class TryFinallyCanBeTryWithResourcesVisitor extends BaseInspectionVisitor { |
| |
| @Override |
| public void visitTryStatement(PsiTryStatement tryStatement) { |
| super.visitTryStatement(tryStatement); |
| if (!PsiUtil.isLanguageLevel7OrHigher(tryStatement)) { |
| return; |
| } |
| final PsiResourceList resourceList = tryStatement.getResourceList(); |
| if (resourceList != null) { |
| return; |
| } |
| final PsiCodeBlock tryBlock = tryStatement.getTryBlock(); |
| if (tryBlock == null) { |
| return; |
| } |
| final List<PsiLocalVariable> variables = collectVariables(tryStatement); |
| if (variables.isEmpty()) { |
| return; |
| } |
| final PsiStatement[] tryBlockStatements = tryBlock.getStatements(); |
| boolean found = false; |
| for (PsiVariable variable : variables) { |
| final boolean hasInitializer; |
| final PsiExpression initializer = variable.getInitializer(); |
| if (initializer == null) { |
| hasInitializer = false; |
| } |
| else { |
| final PsiType type = initializer.getType(); |
| hasInitializer = !PsiType.NULL.equals(type); |
| } |
| final int index = findInitialization(tryBlockStatements, variable, hasInitializer); |
| if (index >= 0 ^ hasInitializer) { |
| if (isVariableUsedOutsideContext(variable, tryBlock)) { |
| continue; |
| } |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| return; |
| } |
| registerStatementError(tryStatement); |
| } |
| } |
| |
| static boolean isVariableUsedOutsideContext(PsiVariable variable, PsiElement context) { |
| final VariableUsedOutsideContextVisitor visitor = new VariableUsedOutsideContextVisitor(variable, context); |
| final PsiElement declarationScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); |
| if (declarationScope == null) { |
| return true; |
| } |
| declarationScope.accept(visitor); |
| return visitor.variableIsUsed(); |
| } |
| |
| static List<PsiLocalVariable> collectVariables(PsiTryStatement tryStatement) { |
| final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); |
| if (finallyBlock == null) { |
| return Collections.EMPTY_LIST; |
| } |
| final PsiStatement[] statements = finallyBlock.getStatements(); |
| if (statements.length == 0) { |
| return Collections.EMPTY_LIST; |
| } |
| final List<PsiLocalVariable> variables = new ArrayList(); |
| for (PsiStatement statement : statements) { |
| final PsiLocalVariable variable = findAutoCloseableVariable(statement); |
| if (variable != null) { |
| variables.add(variable); |
| } |
| } |
| return variables; |
| } |
| |
| @Nullable |
| static PsiLocalVariable findAutoCloseableVariable(PsiStatement statement) { |
| if (statement instanceof PsiIfStatement) { |
| final PsiIfStatement ifStatement = (PsiIfStatement)statement; |
| if (ifStatement.getElseBranch() != null) { |
| return null; |
| } |
| final PsiExpression condition = ifStatement.getCondition(); |
| if (!(condition instanceof PsiBinaryExpression)) { |
| return null; |
| } |
| final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)condition; |
| final IElementType tokenType = binaryExpression.getOperationTokenType(); |
| if (!JavaTokenType.NE.equals(tokenType)) { |
| return null; |
| } |
| final PsiExpression lhs = binaryExpression.getLOperand(); |
| final PsiExpression rhs = binaryExpression.getROperand(); |
| if (rhs == null) { |
| return null; |
| } |
| final PsiElement variable; |
| if (PsiType.NULL.equals(rhs.getType())) { |
| if (!(lhs instanceof PsiReferenceExpression)) { |
| return null; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lhs; |
| variable = referenceExpression.resolve(); |
| if (!(variable instanceof PsiLocalVariable)) { |
| return null; |
| } |
| } |
| else if (PsiType.NULL.equals(lhs.getType())) { |
| if (!(rhs instanceof PsiReferenceExpression)) { |
| return null; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)rhs; |
| variable = referenceExpression.resolve(); |
| if (!(variable instanceof PsiLocalVariable)) { |
| return null; |
| } |
| } |
| else { |
| return null; |
| } |
| final PsiStatement thenBranch = ifStatement.getThenBranch(); |
| final PsiLocalVariable resourceVariable; |
| if (thenBranch instanceof PsiExpressionStatement) { |
| resourceVariable = findAutoCloseableVariable(thenBranch); |
| } |
| else if (thenBranch instanceof PsiBlockStatement) { |
| final PsiBlockStatement blockStatement = (PsiBlockStatement)thenBranch; |
| final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); |
| final PsiStatement[] statements = codeBlock.getStatements(); |
| if (statements.length != 1) { |
| return null; |
| } |
| resourceVariable = findAutoCloseableVariable(statements[0]); |
| } |
| else { |
| return null; |
| } |
| if (variable.equals(resourceVariable)) { |
| return resourceVariable; |
| } |
| } |
| else if (statement instanceof PsiExpressionStatement) { |
| final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)statement; |
| final PsiExpression expression = expressionStatement.getExpression(); |
| if (!(expression instanceof PsiMethodCallExpression)) { |
| return null; |
| } |
| final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; |
| final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); |
| final String methodName = methodExpression.getReferenceName(); |
| if (!HardcodedMethodConstants.CLOSE.equals(methodName)) { |
| return null; |
| } |
| final PsiExpression qualifier = methodExpression.getQualifierExpression(); |
| if (!(qualifier instanceof PsiReferenceExpression)) { |
| return null; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifier; |
| final PsiElement target = referenceExpression.resolve(); |
| if (!(target instanceof PsiLocalVariable) || target instanceof PsiResourceVariable) { |
| return null; |
| } |
| final PsiLocalVariable variable = (PsiLocalVariable)target; |
| if (!isAutoCloseable(variable)) { |
| return null; |
| } |
| return variable; |
| } |
| return null; |
| } |
| |
| private static boolean isAutoCloseable(PsiVariable variable) { |
| final PsiType type = variable.getType(); |
| if (!(type instanceof PsiClassType)) { |
| return false; |
| } |
| final PsiClassType classType = (PsiClassType)type; |
| final PsiClass aClass = classType.resolve(); |
| return aClass != null && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE); |
| } |
| |
| static int findInitialization(PsiElement[] elements, PsiVariable variable, |
| boolean hasInitializer) { |
| int result = -1; |
| final int statementsLength = elements.length; |
| for (int i = 0; i < statementsLength; i++) { |
| final PsiElement element = elements[i]; |
| if (!(element instanceof PsiExpressionStatement)) { |
| continue; |
| } |
| final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)element; |
| final PsiExpression expression = expressionStatement.getExpression(); |
| if (!(expression instanceof PsiAssignmentExpression)) { |
| continue; |
| } |
| final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression; |
| final PsiExpression lhs = assignmentExpression.getLExpression(); |
| if (!(lhs instanceof PsiReferenceExpression)) { |
| continue; |
| } |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lhs; |
| final PsiElement target = referenceExpression.resolve(); |
| if (variable.equals(target)) { |
| if (result >= 0 && !hasInitializer) { |
| return -1; |
| } |
| result = i; |
| } |
| } |
| return result; |
| } |
| |
| static class VariableUsedOutsideContextVisitor extends JavaRecursiveElementVisitor { |
| |
| private boolean used = false; |
| @NotNull private final PsiVariable variable; |
| private final PsiElement skipContext; |
| |
| public VariableUsedOutsideContextVisitor(@NotNull PsiVariable variable, PsiElement skipContext) { |
| this.variable = variable; |
| this.skipContext = skipContext; |
| } |
| |
| @Override |
| public void visitElement(@NotNull PsiElement element) { |
| if (element.equals(skipContext)) { |
| return; |
| } |
| if (used) { |
| return; |
| } |
| super.visitElement(element); |
| } |
| |
| @Override |
| public void visitReferenceExpression(@NotNull PsiReferenceExpression referenceExpression) { |
| if (used) { |
| return; |
| } |
| super.visitReferenceExpression(referenceExpression); |
| final PsiElement target = referenceExpression.resolve(); |
| if (target == null) { |
| return; |
| } |
| if (target.equals(variable) && !isCloseMethodCalled(referenceExpression)) { |
| used = true; |
| } |
| } |
| |
| private static boolean isCloseMethodCalled(PsiReferenceExpression referenceExpression) { |
| final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(referenceExpression, PsiMethodCallExpression.class); |
| if (methodCallExpression == null) { |
| return false; |
| } |
| final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); |
| if (argumentList.getExpressions().length != 0) { |
| return false; |
| } |
| final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); |
| final String name = methodExpression.getReferenceName(); |
| return HardcodedMethodConstants.CLOSE.equals(name); |
| } |
| |
| public boolean variableIsUsed() { |
| return used; |
| } |
| } |
| } |