ce539165b14023aeaea5581c661cebd685bdf8f2
[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.ParenthesesUtils;
28 import com.siyeh.ig.psiutils.TypeUtils;
29 import com.siyeh.ig.psiutils.VariableAccessUtils;
30 import org.jetbrains.annotations.NotNull;
31
32 public class StringBufferReplaceableByStringInspection extends BaseInspection {
33
34   @Override
35   @NotNull
36   public String getDisplayName() {
37     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.display.name");
38   }
39
40   @Override
41   @NotNull
42   public String buildErrorString(Object... infos) {
43     final PsiElement element = (PsiElement)infos[0];
44     if (element instanceof PsiNewExpression) {
45       return InspectionGadgetsBundle.message("new.string.buffer.replaceable.by.string.problem.descriptor");
46     }
47     final String typeText = ((PsiType)infos[1]).getPresentableText();
48     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.problem.descriptor", typeText);
49   }
50
51   @Override
52   protected InspectionGadgetsFix buildFix(Object... infos) {
53     final String typeText = ((PsiType)infos[1]).getCanonicalText();
54     return new StringBufferReplaceableByStringFix(CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(typeText));
55   }
56
57   private static class StringBufferReplaceableByStringFix extends InspectionGadgetsFix {
58
59     private final boolean isStringBuilder;
60
61     private StringBufferReplaceableByStringFix(boolean isStringBuilder) {
62       this.isStringBuilder = isStringBuilder;
63     }
64
65     @NotNull
66     @Override
67     public String getName() {
68       if (isStringBuilder) {
69         return InspectionGadgetsBundle.message("string.builder.replaceable.by.string.quickfix");
70       }
71       else {
72         return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.quickfix");
73       }
74     }
75
76     @Override
77     protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException {
78       final PsiElement element = descriptor.getPsiElement();
79       final PsiElement parent = element.getParent();
80       if (!(parent instanceof PsiVariable)) {
81         if (parent instanceof PsiNewExpression) {
82           final PsiNewExpression newExpression = (PsiNewExpression)parent;
83           final PsiExpression stringBuilderExpression = getCompleteExpression(newExpression);
84           final StringBuilder stringExpression = buildStringExpression(stringBuilderExpression, new StringBuilder());
85           replaceExpression(stringBuilderExpression, stringExpression.toString());
86           return;
87         }
88         return;
89       }
90       final PsiVariable variable = (PsiVariable)parent;
91       final PsiTypeElement originalTypeElement = variable.getTypeElement();
92       if (originalTypeElement == null) {
93         return;
94       }
95       final PsiExpression initializer = variable.getInitializer();
96       if (initializer == null) {
97         return;
98       }
99       final StringBuilder stringExpression = buildStringExpression(initializer, new StringBuilder());
100       if (stringExpression == null) {
101         return;
102       }
103       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
104       final PsiClassType javaLangString = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING, variable.getResolveScope());
105       final PsiTypeElement typeElement = factory.createTypeElement(javaLangString);
106       replaceExpression(initializer, stringExpression.toString());
107       originalTypeElement.replace(typeElement);
108     }
109
110     private static StringBuilder buildStringExpression(PsiExpression initializer, StringBuilder result) {
111       if (initializer instanceof PsiNewExpression) {
112         final PsiNewExpression newExpression = (PsiNewExpression)initializer;
113         final PsiExpressionList argumentList = newExpression.getArgumentList();
114         final PsiExpression[] arguments = argumentList.getExpressions();
115         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(argument.getText());
120           }
121         }
122         return result;
123       }
124       else if (initializer instanceof PsiMethodCallExpression) {
125         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)initializer;
126         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
127         final PsiExpression qualifier = methodExpression.getQualifierExpression();
128         result = buildStringExpression(qualifier, result);
129         if (result == null) {
130           return null;
131         }
132         if ("toString".equals(methodExpression.getReferenceName())) {
133           if (result.length() == 0) {
134             result.append("\"\"");
135           }
136         }
137         else {
138           final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
139           final PsiExpression[] arguments = argumentList.getExpressions();
140           if (arguments.length != 1) {
141             return null;
142           }
143           final PsiExpression argument = arguments[0];
144           if (result.length() != 0) {
145             result.append('+');
146             if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE) {
147               result.append('(').append(argument.getText()).append(')');
148             }
149             else {
150               result.append(argument.getText());
151             }
152           }
153           else {
154             final PsiType type = argument.getType();
155             if (type instanceof PsiPrimitiveType) {
156               result.append("String.valueOf(").append(argument.getText()).append(")");
157             }
158             else {
159               if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE) {
160                 result.append('(').append(argument.getText()).append(')');
161               }
162               else {
163                 result.append(argument.getText());
164               }
165             }
166           }
167         }
168         return result;
169       }
170       return null;
171     }
172   }
173
174   @Override
175   public BaseInspectionVisitor buildVisitor() {
176     return new StringBufferReplaceableByStringVisitor();
177   }
178
179   private static class StringBufferReplaceableByStringVisitor extends BaseInspectionVisitor {
180
181     @Override
182     public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
183       super.visitLocalVariable(variable);
184       final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
185       if (codeBlock == null) {
186         return;
187       }
188       final PsiType type = variable.getType();
189       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
190           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
191         return;
192       }
193       final PsiExpression initializer = variable.getInitializer();
194       if (initializer == null) {
195         return;
196       }
197       if (!isNewStringBufferOrStringBuilder(initializer)) {
198         return;
199       }
200       if (VariableAccessUtils.variableIsAssigned(variable, codeBlock)) {
201         return;
202       }
203       if (VariableAccessUtils.variableIsAssignedFrom(variable, codeBlock)) {
204         return;
205       }
206       if (VariableAccessUtils.variableIsReturned(variable, codeBlock)) {
207         return;
208       }
209       if (VariableAccessUtils.variableIsPassedAsMethodArgument(variable, codeBlock)) {
210         return;
211       }
212       if (variableIsModified(variable, codeBlock)) {
213         return;
214       }
215       registerVariableError(variable, variable, type);
216     }
217
218     @Override
219     public void visitNewExpression(PsiNewExpression expression) {
220       super.visitNewExpression(expression);
221       final PsiType type = expression.getType();
222       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
223           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
224         return;
225       }
226       final PsiExpression completeExpression = getCompleteExpression(expression);
227       if (completeExpression == null) {
228         return;
229       }
230       registerNewExpressionError(expression, expression, type);
231     }
232
233     public static boolean variableIsModified(PsiVariable variable, PsiElement context) {
234       final VariableIsModifiedVisitor visitor = new VariableIsModifiedVisitor(variable);
235       context.accept(visitor);
236       return visitor.isModified();
237     }
238
239     private static boolean isNewStringBufferOrStringBuilder(PsiExpression expression) {
240       if (expression == null) {
241         return false;
242       }
243       else if (expression instanceof PsiNewExpression) {
244         return true;
245       }
246       else if (expression instanceof PsiMethodCallExpression) {
247         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
248         if (!isAppend(methodCallExpression)) {
249           return false;
250         }
251         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
252         final PsiExpression qualifier = methodExpression.getQualifierExpression();
253         return isNewStringBufferOrStringBuilder(qualifier);
254       }
255       return false;
256     }
257
258     public static boolean isAppend(PsiMethodCallExpression methodCallExpression) {
259       final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
260       final String methodName = methodExpression.getReferenceName();
261       return "append".equals(methodName);
262     }
263   }
264
265   private static PsiExpression getCompleteExpression(PsiNewExpression expression) {
266     PsiElement completeExpression = expression;
267     boolean found = false;
268     while (true) {
269       final PsiElement parent = completeExpression.getParent();
270       if (!(parent instanceof PsiReferenceExpression)) {
271         break;
272       }
273       final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)parent;
274       final String name = referenceExpression.getReferenceName();
275       if (!"append".equals(name)) {
276         if (!"toString".equals(name)) {
277           return null;
278         }
279         found = true;
280       }
281       final PsiElement grandParent = parent.getParent();
282       if (!(grandParent instanceof PsiMethodCallExpression)) {
283         break;
284       }
285       completeExpression = grandParent;
286       if (found) {
287         return (PsiExpression)completeExpression;
288       }
289     }
290     return null;
291   }
292 }