| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.plugins.groovy.intentions.conversions.strings; |
| |
| import com.intellij.openapi.application.AccessToken; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ReadAction; |
| import com.intellij.openapi.application.WriteAction; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Pass; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.MethodSignatureUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.IntroduceTargetChooser; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.util.ErrorUtil; |
| import org.jetbrains.plugins.groovy.intentions.base.Intention; |
| import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; |
| import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class ConvertConcatenationToGstringIntention extends Intention { |
| private static final String END_BRACE = "}"; |
| private static final String START_BRACE = "${"; |
| |
| @NotNull |
| @Override |
| protected PsiElementPredicate getElementPredicate() { |
| return new MyPredicate(); |
| } |
| |
| private static List<GrExpression> collectExpressions(final PsiFile file, final int offset) { |
| final List<GrExpression> expressions = new ArrayList<GrExpression>(); |
| |
| _collect(file, offset, expressions); |
| if (expressions.isEmpty()) _collect(file, offset, expressions); |
| return expressions; |
| } |
| |
| private static void _collect(PsiFile file, int offset, List<GrExpression> expressions) { |
| final PsiElement elementAtCaret = file.findElementAt(offset); |
| for (GrExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, GrExpression.class); |
| expression != null; |
| expression = PsiTreeUtil.getParentOfType(expression, GrExpression.class)) { |
| if (MyPredicate.satisfied(expression)) { |
| expressions.add(expression); |
| } |
| else if (!expressions.isEmpty()) break; |
| } |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return false; |
| } |
| |
| @Override |
| protected void processIntention(@NotNull PsiElement element, Project project, Editor editor) throws IncorrectOperationException { |
| final PsiFile file = element.getContainingFile(); |
| final int offset = editor.getCaretModel().getOffset(); |
| final AccessToken accessToken = ReadAction.start(); |
| final List<GrExpression> expressions; |
| try { |
| expressions = collectExpressions(file, offset); |
| } |
| finally { |
| accessToken.finish(); |
| } |
| final Document document = editor.getDocument(); |
| if (expressions.size() == 1) { |
| invokeImpl(expressions.get(0), document); |
| } |
| else if (!expressions.isEmpty()) { |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| invokeImpl(expressions.get(expressions.size() - 1), document); |
| return; |
| } |
| IntroduceTargetChooser.showChooser(editor, expressions, |
| new Pass<GrExpression>() { |
| @Override |
| public void pass(final GrExpression selectedValue) { |
| invokeImpl(selectedValue, document); |
| } |
| }, |
| new Function<GrExpression, String>() { |
| @Override |
| public String fun(GrExpression grExpression) { |
| return grExpression.getText(); |
| } |
| } |
| ); |
| } |
| } |
| |
| private static void invokeImpl(final PsiElement element, Document document) { |
| boolean isMultiline = containsMultilineStrings((GrExpression)element); |
| |
| StringBuilder builder = new StringBuilder(element.getTextLength()); |
| if (element instanceof GrBinaryExpression) { |
| performIntention((GrBinaryExpression)element, builder, isMultiline); |
| } |
| else if (element instanceof GrLiteral) { |
| getOperandText((GrExpression)element, builder, isMultiline); |
| } |
| else { |
| return; |
| } |
| |
| String text = builder.toString(); |
| final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(element.getProject()); |
| final GrExpression newExpr = factory.createExpressionFromText(GrStringUtil.addQuotes(text, true)); |
| |
| CommandProcessor.getInstance().executeCommand(element.getProject(), new Runnable() { |
| @Override |
| public void run() { |
| final AccessToken accessToken = WriteAction.start(); |
| try { |
| final GrExpression expression = ((GrExpression)element).replaceWithExpression(newExpr, true); |
| if (expression instanceof GrString) { |
| GrStringUtil.removeUnnecessaryBracesInGString((GrString)expression); |
| } |
| } |
| finally { |
| accessToken.finish(); |
| } |
| } |
| }, null, null, document); |
| } |
| |
| private static boolean containsMultilineStrings(GrExpression expr) { |
| final Ref<Boolean> result = Ref.create(false); |
| expr.accept(new GroovyRecursiveElementVisitor() { |
| @Override |
| public void visitLiteralExpression(GrLiteral literal) { |
| if (GrStringUtil.isMultilineStringLiteral(literal) && literal.getText().contains("\n")) { |
| result.set(true); |
| } |
| } |
| |
| @Override |
| public void visitElement(GroovyPsiElement element) { |
| if (!result.get()) { |
| super.visitElement(element); |
| } |
| } |
| }); |
| return result.get(); |
| } |
| |
| private static void performIntention(GrBinaryExpression expr, StringBuilder builder, boolean multiline) { |
| GrExpression left = (GrExpression)PsiUtil.skipParentheses(expr.getLeftOperand(), false); |
| GrExpression right = (GrExpression)PsiUtil.skipParentheses(expr.getRightOperand(), false); |
| getOperandText(left, builder, multiline); |
| getOperandText(right, builder, multiline); |
| } |
| |
| private static void getOperandText(@Nullable GrExpression operand, StringBuilder builder, boolean multiline) { |
| if (operand instanceof GrRegex) { |
| for (GroovyPsiElement element : ((GrRegex)operand).getAllContentParts()) { |
| if (element instanceof GrStringInjection) { |
| builder.append(element.getText()); |
| } |
| else if (element instanceof GrStringContent) { |
| if (GrStringUtil.isDollarSlashyString((GrLiteral)operand)) { |
| processDollarSlashyContent(builder, multiline, element.getText()); |
| } |
| else { |
| processSlashyContent(builder, multiline, element.getText()); |
| } |
| } |
| } |
| } |
| else if (operand instanceof GrString) { |
| boolean isMultiline = GrStringUtil.isMultilineStringLiteral((GrLiteral)operand); |
| for (GroovyPsiElement element : ((GrString)operand).getAllContentParts()) { |
| if (element instanceof GrStringInjection) { |
| builder.append(element.getText()); |
| } |
| else if (element instanceof GrStringContent) { |
| if (isMultiline) { |
| processMultilineGString(builder, element.getText()); |
| } |
| else { |
| processSinglelineGString(builder, element.getText()); |
| } |
| } |
| } |
| } |
| else if (operand instanceof GrLiteral) { |
| String text = GrStringUtil.removeQuotes(operand.getText()); |
| GrLiteral literal = (GrLiteral)operand; |
| |
| if (GrStringUtil.isSingleQuoteString(literal)) { |
| processSinglelineString(builder, text); |
| } |
| else if (GrStringUtil.isTripleQuoteString(literal)) { |
| processMultilineString(builder, text); |
| } |
| else if (GrStringUtil.isDoubleQuoteString(literal)) { |
| processSinglelineGString(builder, text); |
| } |
| else if (GrStringUtil.isTripleDoubleQuoteString(literal)) { |
| processMultilineGString(builder, text); |
| } |
| else if (GrStringUtil.isSlashyString(literal)) { |
| processSlashyContent(builder, multiline, text); |
| } |
| else if (GrStringUtil.isDollarSlashyString(literal)) { |
| processDollarSlashyContent(builder, multiline, text); |
| } |
| } |
| else if (MyPredicate.satisfied(operand)) { |
| performIntention((GrBinaryExpression)operand, builder, multiline); |
| } |
| else if (isToStringMethod(operand, builder)) { |
| //nothing to do |
| } |
| else { |
| builder.append(START_BRACE).append(operand == null ? "" : operand.getText()).append(END_BRACE); |
| } |
| } |
| |
| private static void processMultilineString(StringBuilder builder, String text) { |
| final int position = builder.length(); |
| GrStringUtil.escapeAndUnescapeSymbols(text, "$", "'\"", builder); |
| GrStringUtil.fixAllTripleDoubleQuotes(builder, position); |
| } |
| |
| private static void processSinglelineString(StringBuilder builder, String text) { |
| GrStringUtil.escapeAndUnescapeSymbols(text, "$\"", "'", builder); |
| } |
| |
| private static StringBuilder processSinglelineGString(StringBuilder builder, String text) { |
| return builder.append(text); |
| } |
| |
| private static void processMultilineGString(StringBuilder builder, String text) { |
| StringBuilder raw = new StringBuilder(text); |
| GrStringUtil.unescapeCharacters(raw, "\"", true); |
| builder.append(raw); |
| } |
| |
| private static void processDollarSlashyContent(StringBuilder builder, boolean multiline, String text) { |
| GrStringUtil.escapeSymbolsForGString(text, !multiline, false, builder); |
| } |
| |
| private static void processSlashyContent(StringBuilder builder, boolean multiline, String text) { |
| String unescaped = GrStringUtil.unescapeSlashyString(text); |
| GrStringUtil.escapeSymbolsForGString(unescaped, !multiline, false, builder); |
| } |
| |
| /** |
| * append text to builder if the operand is 'something'.toString() |
| */ |
| private static boolean isToStringMethod(GrExpression operand, StringBuilder builder) { |
| if (!(operand instanceof GrMethodCallExpression)) return false; |
| |
| final GrExpression expression = ((GrMethodCallExpression)operand).getInvokedExpression(); |
| if (!(expression instanceof GrReferenceExpression)) return false; |
| |
| final GrReferenceExpression refExpr = (GrReferenceExpression)expression; |
| final GrExpression qualifier = refExpr.getQualifierExpression(); |
| if (qualifier == null) return false; |
| |
| final GroovyResolveResult[] results = refExpr.multiResolve(false); |
| if (results.length != 1) return false; |
| |
| final PsiElement element = results[0].getElement(); |
| if (!(element instanceof PsiMethod)) return false; |
| |
| final PsiMethod method = (PsiMethod)element; |
| final PsiClass objectClass = |
| JavaPsiFacade.getInstance(operand.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, operand.getResolveScope()); |
| if (objectClass == null) return false; |
| |
| final PsiMethod[] toStringMethod = objectClass.findMethodsByName("toString", true); |
| if (MethodSignatureUtil.isSubsignature(toStringMethod[0].getHierarchicalMethodSignature(), method.getHierarchicalMethodSignature())) { |
| builder.append(START_BRACE).append(qualifier.getText()).append(END_BRACE); |
| return true; |
| } |
| return false; |
| } |
| |
| private static class MyPredicate implements PsiElementPredicate { |
| @Override |
| public boolean satisfiedBy(PsiElement element) { |
| return satisfied(element); |
| } |
| |
| public static boolean satisfied(PsiElement element) { |
| if (element instanceof GrLiteral && |
| ((GrLiteral)element).getValue() instanceof String && |
| GrLiteralImpl.getLiteralType((GrLiteral)element) != GroovyTokenTypes.mGSTRING_LITERAL) { |
| return true; |
| } |
| |
| if (!(element instanceof GrBinaryExpression)) return false; |
| |
| GrBinaryExpression binaryExpression = (GrBinaryExpression)element; |
| if (!GroovyTokenTypes.mPLUS.equals(binaryExpression.getOperationTokenType())) return false; |
| |
| if (ErrorUtil.containsError(element)) return false; |
| |
| final PsiType type = binaryExpression.getType(); |
| if (type == null) return false; |
| |
| final PsiClassType stringType = TypesUtil.createType(CommonClassNames.JAVA_LANG_STRING, element); |
| final PsiClassType gstringType = TypesUtil.createType(GroovyCommonClassNames.GROOVY_LANG_GSTRING, element); |
| if (!(TypeConversionUtil.isAssignable(stringType, type) || TypeConversionUtil.isAssignable(gstringType, type))) return false; |
| |
| return true; |
| } |
| } |
| } |