IDEA-80510
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / style / StringBufferReplaceableByStringInspection.java
1 /*
2  * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.siyeh.ig.style;
17
18 import com.intellij.codeInspection.ProblemDescriptor;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.psi.*;
21 import com.intellij.psi.util.PsiTreeUtil;
22 import com.intellij.util.IncorrectOperationException;
23 import com.siyeh.InspectionGadgetsBundle;
24 import com.siyeh.ig.BaseInspection;
25 import com.siyeh.ig.BaseInspectionVisitor;
26 import com.siyeh.ig.InspectionGadgetsFix;
27 import com.siyeh.ig.psiutils.TypeUtils;
28 import com.siyeh.ig.psiutils.VariableAccessUtils;
29 import org.jetbrains.annotations.NotNull;
30
31 public class StringBufferReplaceableByStringInspection extends BaseInspection {
32
33   @Override
34   @NotNull
35   public String getDisplayName() {
36     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.display.name");
37   }
38
39   @Override
40   @NotNull
41   public String buildErrorString(Object... infos) {
42     final PsiElement element = (PsiElement)infos[0];
43     if (element instanceof PsiNewExpression) {
44       return InspectionGadgetsBundle.message("new.string.buffer.replaceable.by.string.problem.descriptor");
45     }
46     final String typeText = ((PsiType)infos[1]).getPresentableText();
47     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.problem.descriptor", typeText);
48   }
49
50   @Override
51   protected InspectionGadgetsFix buildFix(Object... infos) {
52     final String typeText = ((PsiType)infos[1]).getCanonicalText();
53     return new StringBufferReplaceableByStringFix(CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(typeText));
54   }
55
56   private static class StringBufferReplaceableByStringFix extends InspectionGadgetsFix {
57
58     private final boolean isStringBuilder;
59
60     private StringBufferReplaceableByStringFix(boolean isStringBuilder) {
61       this.isStringBuilder = isStringBuilder;
62     }
63
64     @NotNull
65     @Override
66     public String getName() {
67       if (isStringBuilder) {
68         return InspectionGadgetsBundle.message("string.builder.replaceable.by.string.quickfix");
69       } else {
70         return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.quickfix");
71       }
72     }
73
74     @Override
75     protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException {
76       final PsiElement element = descriptor.getPsiElement();
77       final PsiElement parent = element.getParent();
78       if (!(parent instanceof PsiVariable)) {
79         if (parent instanceof PsiNewExpression) {
80           final PsiNewExpression newExpression = (PsiNewExpression)parent;
81           final PsiExpression stringBuilderExpression = getCompleteExpression(newExpression);
82           final StringBuilder stringExpression = buildStringExpression(stringBuilderExpression, new StringBuilder());
83           replaceExpression(stringBuilderExpression, stringExpression.toString());
84           return;
85         }
86         return;
87       }
88       final PsiVariable variable = (PsiVariable)parent;
89       final PsiTypeElement originalTypeElement = variable.getTypeElement();
90       if (originalTypeElement == null) {
91         return;
92       }
93       final PsiExpression initializer = variable.getInitializer();
94       if (initializer == null) {
95         return;
96       }
97       final StringBuilder stringExpression = buildStringExpression(initializer, new StringBuilder());
98       if (stringExpression == null) {
99         return;
100       }
101       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
102       final PsiClassType javaLangString = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING, variable.getResolveScope());
103       final PsiTypeElement typeElement = factory.createTypeElement(javaLangString);
104       replaceExpression(initializer, stringExpression.toString());
105       originalTypeElement.replace(typeElement);
106     }
107
108     private static StringBuilder buildStringExpression(PsiExpression initializer, StringBuilder result) {
109       if (initializer instanceof PsiNewExpression) {
110         final PsiNewExpression newExpression = (PsiNewExpression)initializer;
111         final PsiExpressionList argumentList = newExpression.getArgumentList();
112         final PsiExpression[] arguments = argumentList.getExpressions();
113         if (arguments.length == 0) {
114           result.append("\"\"");
115         } else if (arguments.length == 1) {
116           final PsiExpression argument = arguments[0];
117           final PsiType type = argument.getType();
118           if (PsiType.INT.equals(type)) {
119             result.append("\"\"");
120           } else {
121             result.append(argument.getText());
122           }
123         } else {
124           return null;
125         }
126       } else if (initializer instanceof PsiMethodCallExpression) {
127         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)initializer;
128         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
129         final PsiExpression qualifier = methodExpression.getQualifierExpression();
130         result = buildStringExpression(qualifier, result);
131         if (result == null) {
132           return null;
133         }
134         if (!"toString".equals(methodExpression.getReferenceName())) {
135           final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
136           final PsiExpression[] arguments = argumentList.getExpressions();
137           if (arguments.length != 1) {
138             return null;
139           }
140           final PsiExpression argument = arguments[0];
141           result.append('+');
142           result.append(argument.getText());
143         }
144       } else {
145         return null;
146       }
147       return result;
148     }
149   }
150
151   @Override
152   public BaseInspectionVisitor buildVisitor() {
153     return new StringBufferReplaceableByStringVisitor();
154   }
155
156   private static class StringBufferReplaceableByStringVisitor extends BaseInspectionVisitor {
157
158     @Override
159     public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
160       super.visitLocalVariable(variable);
161       final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
162       if (codeBlock == null) {
163         return;
164       }
165       final PsiType type = variable.getType();
166       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
167           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
168         return;
169       }
170       final PsiExpression initializer = variable.getInitializer();
171       if (initializer == null) {
172         return;
173       }
174       if (!isNewStringBufferOrStringBuilder(initializer)) {
175         return;
176       }
177       if (VariableAccessUtils.variableIsAssigned(variable, codeBlock)) {
178         return;
179       }
180       if (VariableAccessUtils.variableIsAssignedFrom(variable, codeBlock)) {
181         return;
182       }
183       if (VariableAccessUtils.variableIsReturned(variable, codeBlock)) {
184         return;
185       }
186       if (VariableAccessUtils.variableIsPassedAsMethodArgument(variable, codeBlock)) {
187         return;
188       }
189       if (variableIsModified(variable, codeBlock)) {
190         return;
191       }
192       registerVariableError(variable, variable, type);
193     }
194
195     @Override
196     public void visitNewExpression(PsiNewExpression expression) {
197       super.visitNewExpression(expression);
198       final PsiType type = expression.getType();
199       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
200           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
201         return;
202       }
203       final PsiExpression completeExpression = getCompleteExpression(expression);
204       if (completeExpression == null) {
205         return;
206       }
207       registerNewExpressionError(expression, expression, type);
208     }
209
210     public static boolean variableIsModified(PsiVariable variable, PsiElement context) {
211       final VariableIsModifiedVisitor visitor = new VariableIsModifiedVisitor(variable);
212       context.accept(visitor);
213       return visitor.isModified();
214     }
215
216     private static boolean isNewStringBufferOrStringBuilder(PsiExpression expression) {
217       if (expression == null) {
218         return false;
219       }
220       else if (expression instanceof PsiNewExpression) {
221         return true;
222       }
223       else if (expression instanceof PsiMethodCallExpression) {
224         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
225         if (!isAppend(methodCallExpression)) {
226           return false;
227         }
228         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
229         final PsiExpression qualifier = methodExpression.getQualifierExpression();
230         return isNewStringBufferOrStringBuilder(qualifier);
231       }
232       return false;
233     }
234
235     public static boolean isAppend(PsiMethodCallExpression methodCallExpression) {
236       final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
237       final String methodName = methodExpression.getReferenceName();
238       return "append".equals(methodName);
239     }
240   }
241
242   private static PsiExpression getCompleteExpression(PsiNewExpression expression) {
243     PsiElement completeExpression = expression;
244     boolean found = false;
245     while (true) {
246       final PsiElement parent = completeExpression.getParent();
247       if (!(parent instanceof PsiReferenceExpression)) {
248         break;
249       }
250       final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)parent;
251       final String name = referenceExpression.getReferenceName();
252       if (!"append".equals(name)) {
253         if (!"toString".equals(name)) {
254           return null;
255         }
256         found = true;
257       }
258       final PsiElement grandParent = parent.getParent();
259       if (!(grandParent instanceof PsiMethodCallExpression)) {
260         break;
261       }
262       completeExpression = grandParent;
263       if (found) {
264         return (PsiExpression) completeExpression;
265       }
266     }
267     return null;
268   }
269 }