blob: 0d086823efc469b931b74d3b5372ef933ee8099b [file] [log] [blame]
/*
* Copyright 2003-2013 Dave Griffith, 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.style;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
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.psiutils.ParenthesesUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class StringBufferReplaceableByStringInspection extends BaseInspection {
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.display.name");
}
@Override
@NotNull
public String buildErrorString(Object... infos) {
final PsiElement element = (PsiElement)infos[0];
if (element instanceof PsiNewExpression) {
return InspectionGadgetsBundle.message("new.string.buffer.replaceable.by.string.problem.descriptor");
}
final String typeText = ((PsiType)infos[1]).getPresentableText();
return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.problem.descriptor", typeText);
}
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
final String typeText = ((PsiType)infos[1]).getCanonicalText();
return new StringBufferReplaceableByStringFix(CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(typeText));
}
private static class StringBufferReplaceableByStringFix extends InspectionGadgetsFix {
private final boolean isStringBuilder;
private StringBufferReplaceableByStringFix(boolean isStringBuilder) {
this.isStringBuilder = isStringBuilder;
}
@NotNull
@Override
public String getName() {
if (isStringBuilder) {
return InspectionGadgetsBundle.message("string.builder.replaceable.by.string.quickfix");
}
else {
return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.quickfix");
}
}
@NotNull
@Override
public String getFamilyName() {
return "Replace with 'String'";
}
@Override
protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException {
final PsiElement element = descriptor.getPsiElement();
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiVariable)) {
if (parent instanceof PsiNewExpression) {
final PsiExpression stringBuilderExpression = getCompleteExpression(parent);
final StringBuilder stringExpression = buildStringExpression(stringBuilderExpression, new StringBuilder());
if (stringExpression != null && stringBuilderExpression != null) {
replaceExpression(stringBuilderExpression, stringExpression.toString());
}
}
return;
}
final PsiVariable variable = (PsiVariable)parent;
final PsiTypeElement originalTypeElement = variable.getTypeElement();
if (originalTypeElement == null) {
return;
}
final PsiExpression initializer = variable.getInitializer();
if (initializer == null) {
return;
}
final StringBuilder stringExpression;
if (isAppendCall(initializer)) {
stringExpression = buildStringExpression(initializer, new StringBuilder());
if (stringExpression == null) {
return;
}
} else if (initializer instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)initializer;
final PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) {
return;
}
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length == 0 || PsiType.INT.equals(arguments[0].getType())) {
stringExpression = new StringBuilder();
} else {
stringExpression = new StringBuilder(arguments[0].getText());
}
} else {
return;
}
final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
if (codeBlock == null) {
return;
}
final StringBuildingVisitor visitor = new StringBuildingVisitor(variable, stringExpression);
codeBlock.accept(visitor);
if (visitor.hadProblem()) {
return;
}
final List<PsiMethodCallExpression> expressions = visitor.getExpressions();
variable.delete();
for (int i = 0, size = expressions.size() - 1; i < size; i++) {
final PsiMethodCallExpression expression = expressions.get(i);
expression.getParent().delete();
}
final PsiMethodCallExpression lastExpression = expressions.get(expressions.size() - 1);
replaceExpression(lastExpression, stringExpression.toString());
}
@Nullable
private static StringBuilder buildStringExpression(PsiExpression expression, @NonNls StringBuilder result) {
if (expression instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)expression;
final PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) {
return null;
}
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length == 1) {
final PsiExpression argument = arguments[0];
final PsiType type = argument.getType();
if (!PsiType.INT.equals(type)) {
if (type != null && type.equalsToText("java.lang.CharSequence")) {
result.append("String.valueOf(").append(argument.getText()).append(')');
}
else if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE) {
result.append('(').append(argument.getText()).append(')');
}
else {
result.append(argument.getText());
}
}
}
final PsiElement parent = expression.getParent();
if (result.length() == 0 && parent instanceof PsiVariable) {
result.append("\"\"");
}
}
else if (expression instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
final PsiExpression qualifier = methodExpression.getQualifierExpression();
result = buildStringExpression(qualifier, result);
if (result == null) {
return null;
}
if ("toString".equals(methodExpression.getReferenceName())) {
if (result.length() == 0) {
result.append("\"\"");
}
}
else {
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length == 0) {
return null;
}
else if (arguments.length > 1) {
if (result.length() != 0) {
result.append('+');
}
result.append("String.valueOf").append(argumentList.getText());
return result;
}
final PsiExpression argument = arguments[0];
final PsiType type = argument.getType();
final String argumentText = argument.getText();
if (result.length() != 0) {
result.append('+');
if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE ||
(type instanceof PsiPrimitiveType && ParenthesesUtils.getPrecedence(argument) == ParenthesesUtils.ADDITIVE_PRECEDENCE)) {
result.append('(').append(argumentText).append(')');
}
else {
if (StringUtil.startsWithChar(argumentText, '+')) {
result.append(' ');
}
result.append(argumentText);
}
}
else {
if (type instanceof PsiPrimitiveType) {
if (argument instanceof PsiLiteralExpression) {
final PsiLiteralExpression literalExpression = (PsiLiteralExpression)argument;
result.append('"').append(literalExpression.getValue()).append('"');
}
else {
result.append("String.valueOf(").append(argumentText).append(")");
}
}
else {
if (ParenthesesUtils.getPrecedence(argument) >= ParenthesesUtils.ADDITIVE_PRECEDENCE) {
result.append('(').append(argumentText).append(')');
}
else {
if (type != null && !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
result.append("String.valueOf(").append(argumentText).append(")");
}
else {
result.append(argumentText);
}
}
}
}
}
}
return result;
}
private static class StringBuildingVisitor extends JavaRecursiveElementVisitor {
private final PsiVariable myVariable;
private final StringBuilder myBuilder;
private final List<PsiMethodCallExpression> expressions = new ArrayList();
private boolean myProblem = false;
public StringBuildingVisitor(@NotNull PsiVariable variable, StringBuilder builder) {
myVariable = variable;
myBuilder = builder;
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
if (myProblem) {
return;
}
super.visitReferenceExpression(expression);
if (expression.getQualifierExpression() != null) {
return;
}
final PsiElement target = expression.resolve();
if (!myVariable.equals(target)) {
return;
}
PsiMethodCallExpression methodCallExpression = null;
PsiElement parent = expression.getParent();
PsiElement grandParent = parent.getParent();
while (parent instanceof PsiReferenceExpression && grandParent instanceof PsiMethodCallExpression) {
methodCallExpression = (PsiMethodCallExpression)grandParent;
parent = methodCallExpression.getParent();
grandParent = parent.getParent();
}
if (buildStringExpression(methodCallExpression, myBuilder) == null) {
myProblem = true;
}
expressions.add(methodCallExpression);
}
public List<PsiMethodCallExpression> getExpressions() {
return expressions;
}
public boolean hadProblem() {
return myProblem;
}
}
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new StringBufferReplaceableByStringVisitor();
}
private static class StringBufferReplaceableByStringVisitor extends BaseInspectionVisitor {
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
super.visitLocalVariable(variable);
final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
if (codeBlock == null) {
return;
}
final PsiType type = variable.getType();
if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
return;
}
final PsiExpression initializer = variable.getInitializer();
if (!isNewStringBufferOrStringBuilder(initializer)) {
return;
}
final ReplaceableByStringVisitor visitor = new ReplaceableByStringVisitor(variable);
codeBlock.accept(visitor);
if (!visitor.isReplaceable()) {
return;
}
registerVariableError(variable, variable, type);
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
super.visitNewExpression(expression);
final PsiType type = expression.getType();
if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
return;
}
final PsiExpression completeExpression = getCompleteExpression(expression);
if (completeExpression == null) {
return;
}
registerNewExpressionError(expression, expression, type);
}
private static boolean isNewStringBufferOrStringBuilder(PsiExpression expression) {
if (expression == null) {
return false;
}
if (expression instanceof PsiNewExpression) {
return true;
}
if (!isAppendCall(expression) || !(expression instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
final PsiExpression qualifier = methodExpression.getQualifierExpression();
return isNewStringBufferOrStringBuilder(qualifier);
}
}
public static boolean isAppendCall(PsiElement element) {
if (!(element instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
@NonNls final String methodName = methodExpression.getReferenceName();
if (!"append".equals(methodName)) {
return false;
}
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length == 3) {
return arguments[0].getType() instanceof PsiArrayType &&
arguments[1].getType() == PsiType.INT && arguments[2].getType() == PsiType.INT;
}
return arguments.length == 1;
}
public static boolean isToStringCall(PsiElement element) {
if (!(element instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
@NonNls final String methodName = methodExpression.getReferenceName();
if (!"toString".equals(methodName)) {
return false;
}
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
return arguments.length == 0;
}
@Nullable
private static PsiExpression getCompleteExpression(PsiElement element) {
PsiElement completeExpression = element;
boolean found = false;
while (true) {
final PsiElement parent = completeExpression.getParent();
if (!(parent instanceof PsiReferenceExpression)) {
break;
}
final PsiElement grandParent = parent.getParent();
if (isToStringCall(grandParent)) {
found = true;
} else if (!isAppendCall(grandParent)) {
return null;
}
completeExpression = grandParent;
if (found) {
return (PsiExpression)completeExpression;
}
}
return null;
}
private static class ReplaceableByStringVisitor extends JavaRecursiveElementVisitor {
private final PsiElement myParent;
private PsiVariable myVariable;
private boolean myReplaceable = true;
private boolean myToStringFound = false;
public ReplaceableByStringVisitor(@NotNull PsiVariable variable) {
myVariable = variable;
myParent = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class, PsiIfStatement.class, PsiLoopStatement.class);
}
public boolean isReplaceable() {
return myReplaceable && myToStringFound;
}
@Override
public void visitElement(PsiElement element) {
if (!myReplaceable) {
return;
}
super.visitElement(element);
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
super.visitAssignmentExpression(expression);
if (expression.getTextOffset() > myVariable.getTextOffset() && !myToStringFound) {
myReplaceable = false;
}
}
@Override
public void visitPostfixExpression(PsiPostfixExpression expression) {
super.visitPostfixExpression(expression);
if (expression.getTextOffset() > myVariable.getTextOffset() && !myToStringFound) {
myReplaceable = false;
}
}
@Override
public void visitPrefixExpression(PsiPrefixExpression expression) {
super.visitPrefixExpression(expression);
if (expression.getTextOffset() > myVariable.getTextOffset() && !myToStringFound) {
myReplaceable = false;
}
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
if (!myReplaceable || expression.getTextOffset() < myVariable.getTextOffset()) {
return;
}
super.visitReferenceExpression(expression);
final PsiExpression qualifier = expression.getQualifierExpression();
if (qualifier != null) {
return;
}
final PsiElement target = expression.resolve();
if (!myVariable.equals(target)) {
return;
}
if (myToStringFound) {
myReplaceable = false;
return;
}
final PsiElement element = PsiTreeUtil.getParentOfType(expression, PsiCodeBlock.class, PsiIfStatement.class, PsiLoopStatement.class);
if (!myParent.equals(element)) {
myReplaceable = false;
return;
}
PsiElement parent = expression.getParent();
while (true) {
if (!(parent instanceof PsiReferenceExpression)) {
myReplaceable = false;
return;
}
final PsiElement grandParent = parent.getParent();
if (!isAppendCall(grandParent)) {
if (!isToStringCall(grandParent)) {
myReplaceable = false;
return;
}
myToStringFound = true;
return;
}
parent = grandParent.getParent();
if (parent instanceof PsiExpressionStatement) {
return;
}
}
}
}
}