blob: a3ef61a5313e4d0389efd2011b757f3e1d2bda24 [file] [log] [blame]
/*
* 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);
}
}
}