Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / intentions / conversions / ConvertConcatenationToGstringIntention.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
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
17 package org.jetbrains.plugins.groovy.intentions.conversions;
18
19 import com.intellij.psi.*;
20 import com.intellij.psi.util.MethodSignatureUtil;
21 import com.intellij.psi.util.TypeConversionUtil;
22 import com.intellij.util.IncorrectOperationException;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.plugins.groovy.intentions.base.ErrorUtil;
25 import org.jetbrains.plugins.groovy.intentions.base.Intention;
26 import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
27 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
28 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
29 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
30 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
31 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
32 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrParenthesizedExpression;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
37 import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
38
39 /**
40  * @author Maxim.Medvedev
41  */
42 public class ConvertConcatenationToGstringIntention extends Intention {
43   private static final String END_BRACE = "}";
44   private static final String START_BRACE = "${";
45
46   @NotNull
47   @Override
48   protected PsiElementPredicate getElementPredicate() {
49     return new MyPredicate();
50   }
51
52   @Override
53   protected void processIntention(@NotNull PsiElement element) throws IncorrectOperationException {
54     StringBuilder builder = new StringBuilder(element.getTextLength());
55     performIntention((GrBinaryExpression)element, builder);
56     final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(element.getProject());
57     final GrExpression newExpr = factory.createExpressionFromText(GrStringUtil.addQuotes(builder.toString(), true));
58     final GrExpression expression = ((GrBinaryExpression)element).replaceWithExpression(newExpr, true);
59     GrStringUtil.removeUnnecessaryBracesInGString((GrString)expression);
60   }
61
62   private static void performIntention(GrBinaryExpression expr, StringBuilder builder) {
63     GrExpression left = (GrExpression)skipParentheses(expr.getLeftOperand(), false);
64     GrExpression right = (GrExpression)skipParentheses(expr.getRightOperand(), false);
65     getOperandText(left, builder);
66     getOperandText(right, builder);
67   }
68
69   private static void getOperandText(GrExpression operand, StringBuilder builder) {
70     if (operand instanceof GrString) {
71       builder.append(GrStringUtil.removeQuotes(operand.getText()));
72     }
73     else if (operand instanceof GrLiteral) {
74       String text = GrStringUtil.escapeSymbolsForGString(GrStringUtil.removeQuotes(operand.getText()));
75       builder.append(text);
76     }
77     else if (MyPredicate.satisfiedBy(operand, false)) {
78       performIntention((GrBinaryExpression)operand, builder);
79     }
80     else if (isToStringMethod(operand, builder)) {
81       //nothing to do
82     }
83     else {
84       builder.append(START_BRACE).append(operand.getText()).append(END_BRACE);
85     }
86   }
87
88   /**
89    * append text to builder if the operand is 'something'.toString()
90    */
91   private static boolean isToStringMethod(GrExpression operand, StringBuilder builder) {
92     if (!(operand instanceof GrMethodCallExpression)) return false;
93
94     final GrExpression expression = ((GrMethodCallExpression)operand).getInvokedExpression();
95     if (!(expression instanceof GrReferenceExpression)) return false;
96
97     final GrReferenceExpression refExpr = (GrReferenceExpression)expression;
98     final GrExpression qualifier = refExpr.getQualifierExpression();
99     if (qualifier == null) return false;
100
101     final GroovyResolveResult[] results = refExpr.multiResolve(false);
102     if (results.length != 1) return false;
103
104     final PsiElement element = results[0].getElement();
105     if (!(element instanceof PsiMethod)) return false;
106
107     final PsiMethod method = (PsiMethod)element;
108     final PsiClass objectClass =
109       JavaPsiFacade.getInstance(operand.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, operand.getResolveScope());
110     if (objectClass == null) return false;
111
112     final PsiMethod[] toStringMethod = objectClass.findMethodsByName("toString", true);
113     if (MethodSignatureUtil.isSubsignature(toStringMethod[0].getHierarchicalMethodSignature(), method.getHierarchicalMethodSignature())) {
114       builder.append(START_BRACE).append(qualifier.getText()).append(END_BRACE);
115       return true;
116     }
117     return false;
118   }
119
120   private static PsiElement skipParentheses(PsiElement element, boolean up) {
121     if (up) {
122       PsiElement parent = element.getParent();
123       while (parent instanceof GrParenthesizedExpression) {
124         parent = parent.getParent();
125       }
126       return parent;
127     }
128     else {
129       while (element instanceof GrParenthesizedExpression) {
130         element = ((GrParenthesizedExpression)element).getOperand();
131       }
132       return element;
133     }
134   }
135
136   private static class MyPredicate implements PsiElementPredicate {
137     public boolean satisfiedBy(PsiElement element) {
138       return satisfiedBy(element, true);
139     }
140
141     public static boolean satisfiedBy(PsiElement element, boolean checkForParent) {
142       if (!(element instanceof GrBinaryExpression)) return false;
143       GrBinaryExpression binaryExpression = (GrBinaryExpression)element;
144       if (!GroovyTokenTypes.mPLUS.equals(binaryExpression.getOperationTokenType())) return false;
145
146       if (checkForParent) {
147         PsiElement parent = skipParentheses(binaryExpression, true);
148         if (parent instanceof GrBinaryExpression && GroovyTokenTypes.mPLUS.equals(((GrBinaryExpression)parent).getOperationTokenType())) {
149           return false;
150         }
151       }
152       if (ErrorUtil.containsError(element)) return false;
153
154       final PsiType type = binaryExpression.getType();
155       if (type == null) return false;
156
157       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject());
158       final PsiClassType stringType = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING, element.getResolveScope());
159       final PsiClassType gstringType = factory.createTypeByFQClassName("groovy.lang.GString", element.getResolveScope());
160       if (!TypeConversionUtil.isAssignable(stringType, type) && !TypeConversionUtil.isAssignable(gstringType, type)) return false;
161
162       return true;
163     }
164   }
165 }