| /* |
| * 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; |
| } |
| } |
| } |
| } |
| } |