| /* |
| * Copyright 2006-2011 Bas Leijdekkers |
| * |
| * 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.performance; |
| |
| 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.PsiUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.siyeh.InspectionGadgetsBundle; |
| import com.siyeh.ig.BaseInspection; |
| import com.siyeh.ig.BaseInspectionVisitor; |
| import com.siyeh.ig.InspectionGadgetsFix; |
| import com.siyeh.ig.PsiReplacementUtil; |
| import com.siyeh.ig.psiutils.ExpressionUtils; |
| import com.siyeh.ig.psiutils.ParenthesesUtils; |
| import com.siyeh.ig.psiutils.SideEffectChecker; |
| import com.siyeh.ig.psiutils.VariableAccessUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| public class ManualArrayToCollectionCopyInspection extends BaseInspection { |
| |
| @Override |
| @NotNull |
| public String getDisplayName() { |
| return InspectionGadgetsBundle.message( |
| "manual.array.to.collection.copy.display.name"); |
| } |
| |
| @Override |
| public boolean isEnabledByDefault() { |
| return true; |
| } |
| |
| @Override |
| @NotNull |
| protected String buildErrorString(Object... infos) { |
| return InspectionGadgetsBundle.message( |
| "manual.array.to.collection.copy.problem.descriptor"); |
| } |
| |
| @Override |
| public InspectionGadgetsFix buildFix(Object... infos) { |
| return new ManualArrayToCollectionCopyFix(); |
| } |
| |
| private static class ManualArrayToCollectionCopyFix |
| extends InspectionGadgetsFix { |
| |
| @Override |
| @NotNull |
| public String getName() { |
| return InspectionGadgetsBundle.message( |
| "manual.array.to.collection.copy.replace.quickfix"); |
| } |
| |
| @NotNull |
| @Override |
| public String getFamilyName() { |
| return getName(); |
| } |
| |
| @Override |
| public void doFix(Project project, ProblemDescriptor descriptor) |
| throws IncorrectOperationException { |
| final PsiElement forElement = descriptor.getPsiElement(); |
| final PsiElement parent = forElement.getParent(); |
| final String newExpression; |
| if (parent instanceof PsiForStatement) { |
| final PsiForStatement forStatement = |
| (PsiForStatement)parent; |
| newExpression = getCollectionsAddAllText(forStatement); |
| if (newExpression == null) { |
| return; |
| } |
| PsiReplacementUtil.replaceStatementAndShortenClassNames(forStatement, |
| newExpression); |
| } |
| else { |
| final PsiForeachStatement foreachStatement = |
| (PsiForeachStatement)parent; |
| newExpression = getCollectionsAddAllText(foreachStatement); |
| if (newExpression == null) { |
| return; |
| } |
| PsiReplacementUtil.replaceStatementAndShortenClassNames(foreachStatement, |
| newExpression); |
| } |
| } |
| |
| @Nullable |
| private static String getCollectionsAddAllText( |
| PsiForeachStatement foreachStatement) |
| throws IncorrectOperationException { |
| final PsiStatement body = getBody(foreachStatement); |
| if (!(body instanceof PsiExpressionStatement)) { |
| return null; |
| } |
| final PsiExpressionStatement expressionStatement = |
| (PsiExpressionStatement)body; |
| final PsiMethodCallExpression methodCallExpression = |
| (PsiMethodCallExpression)expressionStatement.getExpression(); |
| final PsiReferenceExpression methodExpression = |
| methodCallExpression.getMethodExpression(); |
| final PsiElement collection = methodExpression.getQualifier(); |
| if (collection == null) { |
| // fixme for when the array is added to 'this' |
| return null; |
| } |
| final String collectionText = collection.getText(); |
| final PsiExpression iteratedValue = |
| foreachStatement.getIteratedValue(); |
| if (iteratedValue == null) { |
| return null; |
| } |
| final String arrayText = iteratedValue.getText(); |
| @NonNls final StringBuilder buffer = new StringBuilder(); |
| if (PsiUtil.isLanguageLevel5OrHigher(foreachStatement)) { |
| buffer.append("java.util.Collections.addAll("); |
| buffer.append(collectionText); |
| buffer.append(','); |
| buffer.append(arrayText); |
| buffer.append(");"); |
| } |
| else { |
| buffer.append(collectionText); |
| buffer.append(".addAll(java.util.Arrays.asList("); |
| buffer.append(arrayText); |
| buffer.append("));"); |
| } |
| return buffer.toString(); |
| } |
| |
| @Nullable |
| private static String getCollectionsAddAllText( |
| PsiForStatement forStatement) |
| throws IncorrectOperationException { |
| final PsiExpression expression = forStatement.getCondition(); |
| final PsiBinaryExpression condition = |
| (PsiBinaryExpression)ParenthesesUtils.stripParentheses( |
| expression); |
| if (condition == null) { |
| return null; |
| } |
| final PsiStatement initialization = |
| forStatement.getInitialization(); |
| if (initialization == null) { |
| return null; |
| } |
| if (!(initialization instanceof PsiDeclarationStatement)) { |
| return null; |
| } |
| final PsiDeclarationStatement declaration = |
| (PsiDeclarationStatement)initialization; |
| final PsiElement[] declaredElements = |
| declaration.getDeclaredElements(); |
| if (declaredElements.length != 1) { |
| return null; |
| } |
| final PsiElement declaredElement = declaredElements[0]; |
| if (!(declaredElement instanceof PsiLocalVariable)) { |
| return null; |
| } |
| final PsiLocalVariable variable = (PsiLocalVariable)declaredElement; |
| final String collectionText = buildCollectionText(forStatement); |
| final PsiArrayAccessExpression arrayAccessExpression = |
| getArrayAccessExpression(forStatement); |
| if (arrayAccessExpression == null) { |
| return null; |
| } |
| final PsiExpression arrayExpression = |
| arrayAccessExpression.getArrayExpression(); |
| final String arrayText = arrayExpression.getText(); |
| final PsiExpression indexExpression = |
| arrayAccessExpression.getIndexExpression(); |
| final String fromOffsetText = |
| buildFromOffsetText(indexExpression, variable); |
| if (fromOffsetText == null) { |
| return null; |
| } |
| final PsiExpression limit; |
| final IElementType tokenType = condition.getOperationTokenType(); |
| if (tokenType == JavaTokenType.LT || |
| tokenType == JavaTokenType.LE) { |
| limit = condition.getROperand(); |
| } |
| else { |
| limit = condition.getLOperand(); |
| } |
| @NonNls final String toOffsetText = |
| buildToOffsetText(limit, tokenType == JavaTokenType.LE || |
| tokenType == JavaTokenType.GE); |
| if (toOffsetText == null) { |
| return null; |
| } |
| if (fromOffsetText.equals("0") && |
| toOffsetText.equals(arrayText + ".length") && |
| PsiUtil.isLanguageLevel5OrHigher(forStatement)) { |
| @NonNls final StringBuilder buffer = |
| new StringBuilder("java.util.Collections.addAll("); |
| buffer.append(collectionText); |
| buffer.append(','); |
| buffer.append(arrayText); |
| buffer.append(");"); |
| return buffer.toString(); |
| } |
| else { |
| @NonNls final StringBuilder buffer = new StringBuilder(); |
| buffer.append(collectionText); |
| buffer.append('.'); |
| buffer.append("addAll(java.util.Arrays.asList("); |
| buffer.append(arrayText); |
| buffer.append(')'); |
| if (!fromOffsetText.equals("0") || |
| !toOffsetText.equals(arrayText + ".length")) { |
| buffer.append(".subList("); |
| buffer.append(fromOffsetText); |
| buffer.append(", "); |
| buffer.append(toOffsetText); |
| buffer.append(')'); |
| } |
| buffer.append(");"); |
| return buffer.toString(); |
| } |
| } |
| |
| private static PsiArrayAccessExpression getArrayAccessExpression( |
| PsiForStatement forStatement) { |
| final PsiStatement body = getBody(forStatement); |
| if (body == null) { |
| return null; |
| } |
| final PsiExpression arrayAccessExpression; |
| if (body instanceof PsiExpressionStatement) { |
| final PsiExpressionStatement expressionStatement = |
| (PsiExpressionStatement)body; |
| final PsiExpression expression = |
| expressionStatement.getExpression(); |
| if (!(expression instanceof PsiMethodCallExpression)) { |
| return null; |
| } |
| final PsiMethodCallExpression methodCallExpression = |
| (PsiMethodCallExpression)expression; |
| final PsiExpressionList argumentList = |
| methodCallExpression.getArgumentList(); |
| arrayAccessExpression = argumentList.getExpressions()[0]; |
| } |
| else if (body instanceof PsiDeclarationStatement) { |
| final PsiDeclarationStatement declarationStatement = |
| (PsiDeclarationStatement)body; |
| final PsiElement[] declaredElements = |
| declarationStatement.getDeclaredElements(); |
| if (declaredElements.length != 1) { |
| return null; |
| } |
| final PsiElement declaredElement = declaredElements[0]; |
| if (!(declaredElement instanceof PsiVariable)) { |
| return null; |
| } |
| final PsiVariable variable = (PsiVariable)declaredElement; |
| arrayAccessExpression = variable.getInitializer(); |
| } |
| else { |
| return null; |
| } |
| final PsiExpression deparenthesizedArgument = |
| ParenthesesUtils.stripParentheses(arrayAccessExpression); |
| if (!(deparenthesizedArgument instanceof |
| PsiArrayAccessExpression)) { |
| return null; |
| } |
| return (PsiArrayAccessExpression)deparenthesizedArgument; |
| } |
| |
| public static String buildCollectionText(PsiForStatement forStatement) { |
| PsiStatement body = forStatement.getBody(); |
| while (body instanceof PsiBlockStatement) { |
| final PsiBlockStatement blockStatement = |
| (PsiBlockStatement)body; |
| final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); |
| final PsiStatement[] statements = codeBlock.getStatements(); |
| if (statements.length == 2) { |
| body = statements[1]; |
| } |
| else if (statements.length == 1) { |
| body = statements[0]; |
| } |
| else { |
| return null; |
| } |
| } |
| if (!(body instanceof PsiExpressionStatement)) { |
| return null; |
| } |
| final PsiExpressionStatement expressionStatement = |
| (PsiExpressionStatement)body; |
| final PsiExpression expression = |
| expressionStatement.getExpression(); |
| if (!(expression instanceof PsiMethodCallExpression)) { |
| return null; |
| } |
| final PsiMethodCallExpression methodCallExpression = |
| (PsiMethodCallExpression)expression; |
| final PsiReferenceExpression methodExpression = |
| methodCallExpression.getMethodExpression(); |
| final PsiElement qualifier = methodExpression.getQualifier(); |
| if (qualifier == null) { |
| // fixme for when the array is added to 'this' |
| return null; |
| } |
| return qualifier.getText(); |
| } |
| |
| @Nullable |
| private static String buildFromOffsetText(PsiExpression expression, |
| PsiLocalVariable variable) |
| throws IncorrectOperationException { |
| expression = ParenthesesUtils.stripParentheses(expression); |
| if (expression == null) { |
| return null; |
| } |
| final String expressionText = expression.getText(); |
| final String variableName = variable.getName(); |
| if (expressionText.equals(variableName)) { |
| final PsiExpression initialValue = variable.getInitializer(); |
| if (initialValue == null) { |
| return null; |
| } |
| return initialValue.getText(); |
| } |
| if (expression instanceof PsiBinaryExpression) { |
| final PsiBinaryExpression binaryExpression = |
| (PsiBinaryExpression)expression; |
| final PsiExpression lhs = binaryExpression.getLOperand(); |
| final PsiExpression rhs = binaryExpression.getROperand(); |
| final String rhsText = buildFromOffsetText(rhs, variable); |
| final PsiJavaToken sign = binaryExpression.getOperationSign(); |
| final IElementType tokenType = sign.getTokenType(); |
| if (ExpressionUtils.isZero(lhs)) { |
| if (tokenType.equals(JavaTokenType.MINUS)) { |
| return '-' + rhsText; |
| } |
| return rhsText; |
| } |
| final String lhsText = buildFromOffsetText(lhs, variable); |
| if (ExpressionUtils.isZero(rhs)) { |
| return lhsText; |
| } |
| return collapseConstant(lhsText + sign.getText() + rhsText, |
| variable); |
| } |
| return collapseConstant(expression.getText(), variable); |
| } |
| |
| private static String buildToOffsetText(PsiExpression expression, |
| boolean plusOne) { |
| expression = ParenthesesUtils.stripParentheses(expression); |
| if (expression == null) { |
| return null; |
| } |
| if (!plusOne) { |
| return expression.getText(); |
| } |
| if (expression instanceof PsiBinaryExpression) { |
| final PsiBinaryExpression binaryExpression = |
| (PsiBinaryExpression)expression; |
| final IElementType tokenType = |
| binaryExpression.getOperationTokenType(); |
| if (tokenType == JavaTokenType.MINUS) { |
| final PsiExpression rhs = |
| binaryExpression.getROperand(); |
| if (ExpressionUtils.isOne(rhs)) { |
| return binaryExpression.getLOperand().getText(); |
| } |
| } |
| } |
| final int precedence = ParenthesesUtils.getPrecedence(expression); |
| if (precedence > ParenthesesUtils.ADDITIVE_PRECEDENCE) { |
| return '(' + expression.getText() + ")+1"; |
| } |
| else { |
| return expression.getText() + "+1"; |
| } |
| } |
| |
| private static String collapseConstant(String expressionText, |
| PsiElement context) |
| throws IncorrectOperationException { |
| final Project project = context.getProject(); |
| final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); |
| final PsiElementFactory factory = psiFacade.getElementFactory(); |
| final PsiExpression fromOffsetExpression = |
| factory.createExpressionFromText(expressionText, context); |
| final Object fromOffsetConstant = |
| ExpressionUtils.computeConstantExpression( |
| fromOffsetExpression); |
| if (fromOffsetConstant != null) { |
| return fromOffsetConstant.toString(); |
| } |
| else { |
| return expressionText; |
| } |
| } |
| |
| @Nullable |
| private static PsiStatement getBody(PsiLoopStatement forStatement) { |
| PsiStatement body = forStatement.getBody(); |
| while (body instanceof PsiBlockStatement) { |
| final PsiBlockStatement blockStatement = |
| (PsiBlockStatement)body; |
| final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); |
| final PsiStatement[] statements = codeBlock.getStatements(); |
| body = statements[0]; |
| } |
| return body; |
| } |
| } |
| |
| @Override |
| public BaseInspectionVisitor buildVisitor() { |
| return new ManualArrayToCollectionCopyVisitor(); |
| } |
| |
| private static class ManualArrayToCollectionCopyVisitor |
| extends BaseInspectionVisitor { |
| |
| @Override |
| public void visitForStatement( |
| @NotNull PsiForStatement statement) { |
| super.visitForStatement(statement); |
| final PsiStatement initialization = statement.getInitialization(); |
| if (!(initialization instanceof PsiDeclarationStatement)) { |
| return; |
| } |
| final PsiDeclarationStatement declaration = |
| (PsiDeclarationStatement)initialization; |
| final PsiElement[] declaredElements = |
| declaration.getDeclaredElements(); |
| if (declaredElements.length != 1) { |
| return; |
| } |
| final PsiElement declaredElement = declaredElements[0]; |
| if (!(declaredElement instanceof PsiLocalVariable)) { |
| return; |
| } |
| final PsiLocalVariable variable = (PsiLocalVariable)declaredElement; |
| final PsiExpression initialValue = variable.getInitializer(); |
| if (initialValue == null) { |
| return; |
| } |
| final PsiExpression condition = statement.getCondition(); |
| if (!ExpressionUtils.isVariableLessThanComparison(condition, |
| variable)) { |
| return; |
| } |
| final PsiStatement update = statement.getUpdate(); |
| if (!VariableAccessUtils.variableIsIncremented(variable, update)) { |
| return; |
| } |
| final PsiStatement body = statement.getBody(); |
| if (!bodyIsArrayToCollectionCopy(body, variable, true)) { |
| return; |
| } |
| registerStatementError(statement); |
| } |
| |
| @Override |
| public void visitForeachStatement( |
| PsiForeachStatement statement) { |
| super.visitForeachStatement(statement); |
| final PsiExpression iteratedValue = statement.getIteratedValue(); |
| if (iteratedValue == null) { |
| return; |
| } |
| final PsiType type = iteratedValue.getType(); |
| if (!(type instanceof PsiArrayType)) { |
| return; |
| } |
| final PsiArrayType arrayType = (PsiArrayType)type; |
| final PsiType componentType = arrayType.getComponentType(); |
| if (componentType instanceof PsiPrimitiveType) { |
| return; |
| } |
| final PsiParameter parameter = statement.getIterationParameter(); |
| final PsiStatement body = statement.getBody(); |
| if (!bodyIsArrayToCollectionCopy(body, parameter, false)) { |
| return; |
| } |
| registerStatementError(statement); |
| } |
| |
| private static boolean bodyIsArrayToCollectionCopy( |
| PsiStatement body, PsiVariable variable, |
| boolean shouldBeOffsetArrayAccess) { |
| if (body instanceof PsiExpressionStatement) { |
| final PsiExpressionStatement expressionStatement = |
| (PsiExpressionStatement)body; |
| final PsiExpression expression = |
| expressionStatement.getExpression(); |
| return expressionIsArrayToCollectionCopy(expression, variable, |
| shouldBeOffsetArrayAccess); |
| } |
| else if (body instanceof PsiBlockStatement) { |
| final PsiBlockStatement blockStatement = |
| (PsiBlockStatement)body; |
| final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); |
| final PsiStatement[] statements = codeBlock.getStatements(); |
| if (statements.length == 1) { |
| return bodyIsArrayToCollectionCopy(statements[0], variable, |
| shouldBeOffsetArrayAccess); |
| } |
| else if (statements.length == 2) { |
| final PsiStatement statement = statements[0]; |
| if (!(statement instanceof PsiDeclarationStatement)) { |
| return false; |
| } |
| final PsiDeclarationStatement declarationStatement = |
| (PsiDeclarationStatement)statement; |
| final PsiElement[] declaredElements = |
| declarationStatement.getDeclaredElements(); |
| if (declaredElements.length != 1) { |
| return false; |
| } |
| final PsiElement declaredElement = declaredElements[0]; |
| if (!(declaredElement instanceof PsiVariable)) { |
| return false; |
| } |
| final PsiVariable localVariable = |
| (PsiVariable)declaredElement; |
| final PsiExpression initializer = |
| localVariable.getInitializer(); |
| if (!ExpressionUtils.isOffsetArrayAccess(initializer, |
| variable)) { |
| return false; |
| } |
| return bodyIsArrayToCollectionCopy(statements[1], |
| localVariable, false); |
| } |
| } |
| return false; |
| } |
| |
| private static boolean expressionIsArrayToCollectionCopy( |
| PsiExpression expression, PsiVariable variable, |
| boolean shouldBeOffsetArrayAccess) { |
| expression = ParenthesesUtils.stripParentheses(expression); |
| if (expression == null) { |
| return false; |
| } |
| if (!(expression instanceof PsiMethodCallExpression)) { |
| return false; |
| } |
| final PsiMethodCallExpression methodCallExpression = |
| (PsiMethodCallExpression)expression; |
| final PsiExpressionList argumentList = |
| methodCallExpression.getArgumentList(); |
| final PsiExpression[] arguments = argumentList.getExpressions(); |
| if (arguments.length != 1) { |
| return false; |
| } |
| final PsiReferenceExpression methodExpression = |
| methodCallExpression.getMethodExpression(); |
| final PsiExpression qualifier = |
| methodExpression.getQualifierExpression(); |
| if (!(qualifier instanceof PsiReferenceExpression) && |
| !(qualifier instanceof PsiThisExpression) && |
| !(qualifier instanceof PsiSuperExpression)) { |
| return false; |
| } |
| final PsiExpression argument = arguments[0]; |
| final PsiType argumentType = argument.getType(); |
| if (argumentType instanceof PsiPrimitiveType) { |
| return false; |
| } |
| if (SideEffectChecker.mayHaveSideEffects(argument)) { |
| return false; |
| } |
| if (shouldBeOffsetArrayAccess) { |
| if (!ExpressionUtils.isOffsetArrayAccess(argument, variable)) { |
| return false; |
| } |
| } |
| else if (!VariableAccessUtils.evaluatesToVariable(argument, |
| variable)) { |
| return false; |
| } |
| final PsiMethod method = methodCallExpression.resolveMethod(); |
| if (method == null) { |
| return false; |
| } |
| @NonNls final String name = method.getName(); |
| if (!name.equals("add")) { |
| return false; |
| } |
| final PsiClass containingClass = method.getContainingClass(); |
| return InheritanceUtil.isInheritor(containingClass, |
| CommonClassNames.JAVA_UTIL_COLLECTION); |
| } |
| } |
| } |