| /* |
| * 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.lang.psi.util; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; |
| import com.intellij.psi.tree.IElementType; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; |
| import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| 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.impl.statements.expressions.literals.GrLiteralImpl; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class GrStringUtil { |
| private static final Logger LOG = Logger.getInstance(GrStringUtil.class); |
| |
| public static final String TRIPLE_QUOTES = "'''"; |
| public static final String QUOTE = "'"; |
| public static final String DOUBLE_QUOTES = "\""; |
| public static final String TRIPLE_DOUBLE_QUOTES = "\"\"\""; |
| public static final String SLASH = "/"; |
| public static final String DOLLAR_SLASH = "$/"; |
| public static final String SLASH_DOLLAR = "/$"; |
| |
| private GrStringUtil() { |
| } |
| |
| public static String unescapeString(String s) { |
| final int length = s.length(); |
| StringBuilder buffer = new StringBuilder(length); |
| boolean escaped = false; |
| for (int idx = 0; idx < length; idx++) { |
| char ch = s.charAt(idx); |
| if (!escaped) { |
| if (ch == '\\') { |
| escaped = true; |
| } |
| else { |
| buffer.append(ch); |
| } |
| } |
| else { |
| switch (ch) { |
| case 'n': |
| buffer.append('\n'); |
| break; |
| |
| case 'r': |
| buffer.append('\r'); |
| break; |
| |
| case 'b': |
| buffer.append('\b'); |
| break; |
| |
| case 't': |
| buffer.append('\t'); |
| break; |
| |
| case 'f': |
| buffer.append('\f'); |
| break; |
| |
| case '\'': |
| buffer.append('\''); |
| break; |
| |
| case '\"': |
| buffer.append('\"'); |
| break; |
| |
| case '\\': |
| buffer.append('\\'); |
| break; |
| case '\n': |
| //do nothing |
| break; |
| |
| case 'u': |
| if (idx + 4 < length) { |
| try { |
| int code = Integer.valueOf(s.substring(idx + 1, idx + 5), 16).intValue(); |
| idx += 4; |
| buffer.append((char)code); |
| } |
| catch (NumberFormatException e) { |
| buffer.append("\\u"); |
| } |
| } |
| else { |
| buffer.append("\\u"); |
| } |
| break; |
| |
| default: |
| buffer.append('\\'); |
| buffer.append(ch); |
| break; |
| } |
| escaped = false; |
| } |
| } |
| |
| return buffer.toString(); |
| } |
| |
| public static String unescapeSlashyString(String s) { |
| return unescapeRegex(s, true); |
| } |
| |
| public static String unescapeDollarSlashyString(String s) { |
| return unescapeRegex(s, false); |
| } |
| |
| private static String unescapeRegex(String s, boolean unescapeSlash) { |
| final int length = s.length(); |
| StringBuilder buffer = new StringBuilder(length); |
| |
| boolean escaped = false; |
| for (int idx = 0; idx < length; idx++) { |
| char ch = s.charAt(idx); |
| if (!escaped) { |
| if (ch == '\\') { |
| escaped = true; |
| } |
| else { |
| buffer.append(ch); |
| } |
| } |
| else { |
| switch (ch) { |
| case '/': |
| if (!unescapeSlash) { |
| buffer.append('\\'); |
| } |
| buffer.append('/'); |
| break; |
| case 'u': |
| if (idx + 4 < length) { |
| try { |
| int code = Integer.valueOf(s.substring(idx + 1, idx + 5), 16).intValue(); |
| idx += 4; |
| buffer.append((char)code); |
| } |
| catch (NumberFormatException e) { |
| buffer.append("\\u"); |
| } |
| } |
| else { |
| buffer.append("\\u"); |
| } |
| break; |
| |
| default: |
| buffer.append('\\'); |
| buffer.append(ch); |
| break; |
| } |
| escaped = false; |
| } |
| } |
| |
| if (escaped) buffer.append('\\'); |
| return buffer.toString(); |
| } |
| |
| public static String escapeSymbolsForSlashyStrings(String str) { |
| final StringBuilder buffer = new StringBuilder(str.length()); |
| escapeSymbolsForSlashyStrings(buffer, str); |
| return buffer.toString(); |
| } |
| |
| public static void escapeSymbolsForSlashyStrings(StringBuilder buffer, String str) { |
| final int length = str.length(); |
| for (int idx = 0; idx < length; idx++) { |
| char ch = str.charAt(idx); |
| switch (ch) { |
| case '/': |
| buffer.append("\\/"); |
| break; |
| default: |
| if (Character.isISOControl(ch) || ch == '$') { |
| appendUnicode(buffer, ch); |
| } |
| else { |
| buffer.append(ch); |
| } |
| } |
| } |
| } |
| |
| public static String escapeSymbolsForDollarSlashyStrings(String str) { |
| final StringBuilder buffer = new StringBuilder(str.length()); |
| escapeSymbolsForDollarSlashyStrings(buffer, str); |
| return buffer.toString(); |
| } |
| |
| public static void escapeSymbolsForDollarSlashyStrings(StringBuilder buffer, String str) { |
| final int length = str.length(); |
| for (int idx = 0; idx < length; idx++) { |
| char ch = str.charAt(idx); |
| switch (ch) { |
| case '/': |
| if (idx + 1 < length && str.charAt(idx + 1) == '$') { |
| appendUnicode(buffer, '/'); |
| appendUnicode(buffer, '$'); |
| break; |
| } |
| default: |
| if (Character.isISOControl(ch)) { |
| appendUnicode(buffer, ch); |
| } |
| else { |
| buffer.append(ch); |
| } |
| } |
| } |
| } |
| |
| private static void appendUnicode(StringBuilder buffer, char ch) { |
| String hexCode = Integer.toHexString(ch).toUpperCase(); |
| buffer.append("\\u"); |
| int paddingCount = 4 - hexCode.length(); |
| while (paddingCount-- > 0) { |
| buffer.append(0); |
| } |
| buffer.append(hexCode); |
| } |
| |
| public static String escapeSymbolsForGString(CharSequence s, boolean isSingleLine, boolean unescapeSymbols) { |
| StringBuilder b = new StringBuilder(); |
| escapeSymbolsForGString(s, isSingleLine, unescapeSymbols, b); |
| return b.toString(); |
| } |
| |
| public static void escapeSymbolsForGString(CharSequence s, boolean isSingleLine, boolean unescapeSymbols, StringBuilder b) { |
| escapeStringCharacters(s.length(), s, isSingleLine ? "$\"" : "$", isSingleLine, true, b); |
| if (unescapeSymbols) { |
| unescapeCharacters(b, isSingleLine ? "'" : "'\"", true); |
| } |
| if (!isSingleLine) escapeLastSymbols(b, '\"'); |
| } |
| |
| public static String escapeSymbolsForString(String s, boolean isSingleLine, boolean forInjection) { |
| final StringBuilder builder = new StringBuilder(); |
| escapeStringCharacters(s.length(), s, isSingleLine ? "'" : "", isSingleLine, true, builder); |
| if (!forInjection) { |
| unescapeCharacters(builder, isSingleLine ? "$\"" : "$'\"", true); |
| } |
| if (!isSingleLine) escapeLastSymbols(builder, '\''); |
| return builder.toString(); |
| } |
| |
| private static void escapeLastSymbols(StringBuilder builder, char toEscape) { |
| for (int i = builder.length() - 1; i >= 0 && builder.charAt(i) == toEscape; i--) { |
| builder.insert(i, '\\'); |
| } |
| } |
| |
| @NotNull |
| public static StringBuilder escapeStringCharacters(int length, |
| @NotNull CharSequence str, |
| @Nullable String additionalChars, |
| boolean escapeLineFeeds, |
| boolean escapeBackSlash, |
| @NotNull @NonNls StringBuilder buffer) { |
| for (int idx = 0; idx < length; idx++) { |
| char ch = str.charAt(idx); |
| switch (ch) { |
| case '\b': |
| buffer.append("\\b"); |
| break; |
| |
| case '\t': |
| buffer.append("\\t"); |
| break; |
| |
| case '\f': |
| buffer.append("\\f"); |
| break; |
| |
| case '\\': |
| if (escapeBackSlash) { |
| buffer.append("\\\\"); |
| } |
| else { |
| buffer.append('\\'); |
| } |
| break; |
| |
| case '\n': |
| if (escapeLineFeeds) { |
| buffer.append("\\n"); |
| } |
| else { |
| buffer.append('\n'); |
| } |
| break; |
| |
| case '\r': |
| if (escapeLineFeeds) { |
| buffer.append("\\r"); |
| } |
| else { |
| buffer.append('\r'); |
| } |
| break; |
| |
| default: |
| if (additionalChars != null && additionalChars.indexOf(ch) > -1) { |
| buffer.append("\\").append(ch); |
| } |
| else if (Character.isISOControl(ch)) { |
| appendUnicode(buffer, ch); |
| } |
| else { |
| buffer.append(ch); |
| } |
| } |
| } |
| return buffer; |
| } |
| |
| |
| |
| public static void unescapeCharacters(StringBuilder builder, String toUnescape, boolean isMultiLine) { |
| for (int i = 0; i < builder.length(); i++) { |
| if (builder.charAt(i) != '\\') continue; |
| if (i + 1 == builder.length()) break; |
| char next = builder.charAt(i + 1); |
| if (next == 'n') { |
| if (isMultiLine) { |
| builder.replace(i, i + 2, "\n"); |
| } |
| } |
| else if (next == 'r') { |
| if (isMultiLine) { |
| builder.replace(i, i + 2, "\r"); |
| } |
| } |
| else if (toUnescape.indexOf(next) != -1) { |
| builder.delete(i, i + 1); |
| } |
| else { |
| i++; |
| } |
| } |
| } |
| |
| public static String escapeAndUnescapeSymbols(String s, String toEscape, String toUnescape, StringBuilder builder) { |
| boolean escaped = false; |
| for (int i = 0; i < s.length(); i++) { |
| char ch = s.charAt(i); |
| if (escaped) { |
| if (toUnescape.indexOf(ch) < 0) { |
| builder.append('\\'); |
| builder.append(ch); |
| } |
| else { |
| if (ch=='n') builder.append('\n'); |
| else if (ch=='r') builder.append('\r'); |
| else if (ch=='b') builder.append('\b'); |
| else if (ch=='t') builder.append('\t'); |
| else if (ch=='f') builder.append('\r'); |
| else builder.append(ch); |
| } |
| escaped = false; |
| continue; |
| } |
| if (ch == '\\') { |
| escaped = true; |
| continue; |
| } |
| |
| if (toEscape.indexOf(ch) >= 0) { |
| builder.append('\\'); |
| if (ch == '\n') ch = 'n'; |
| else if (ch == '\b') ch = 'b'; |
| else if (ch == '\t') ch = 't'; |
| else if (ch == '\r') ch = 'r'; |
| else if (ch == '\f') ch = 'f'; |
| } |
| |
| builder.append(ch); |
| } |
| return builder.toString(); |
| } |
| |
| public static String removeQuotes(@NotNull String s) { |
| String quote = getStartQuote(s); |
| int sL = s.length(); |
| int qL = quote.length(); |
| if (sL >= qL * 2 && DOLLAR_SLASH.equals(quote)) { |
| if (s.endsWith(SLASH_DOLLAR)) { |
| return s.substring(qL, sL - qL); |
| } |
| else { |
| return s.substring(qL); |
| } |
| } |
| |
| if (sL >= qL * 2 && s.endsWith(quote)) { |
| return s.substring(qL, sL - qL); |
| } |
| else { |
| return s.substring(qL); |
| } |
| } |
| |
| public static String addQuotes(String s, boolean forGString) { |
| if (forGString) { |
| if (s.contains("\n") || s.contains("\r")) { |
| return TRIPLE_DOUBLE_QUOTES + s + TRIPLE_DOUBLE_QUOTES; |
| } |
| else { |
| return DOUBLE_QUOTES + s + DOUBLE_QUOTES; |
| } |
| } |
| else { |
| if (s.contains("\n") || s.contains("\r")) { |
| return TRIPLE_QUOTES + s + TRIPLE_QUOTES; |
| } |
| else { |
| return QUOTE + s + QUOTE; |
| } |
| } |
| } |
| |
| public static GrString replaceStringInjectionByLiteral(GrStringInjection injection, GrLiteral literal) { |
| GrString grString = (GrString)injection.getParent(); |
| |
| final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(grString.getProject()); |
| |
| String literalText; |
| |
| //wrap last injection in inserted literal if it needed |
| // e.g.: "bla bla ${foo}bla bla" and {foo} is replaced |
| if (literal instanceof GrString) { |
| final GrStringInjection[] injections = ((GrString)literal).getInjections(); |
| if (injections.length > 0) { |
| GrStringInjection last = injections[injections.length - 1]; |
| if (last.getExpression() != null) { |
| if (!checkBraceIsUnnecessary(last.getExpression(), injection.getNextSibling())) { |
| wrapInjection(last); |
| } |
| } |
| } |
| literalText = removeQuotes(literal.getText()); |
| } |
| else { |
| final String text = removeQuotes(literal.getText()); |
| boolean escapeDoubleQuotes = !text.contains("\n") && grString.isPlainString(); |
| literalText = escapeSymbolsForGString(text, escapeDoubleQuotes, true); |
| } |
| |
| if (literalText.contains("\n")) { |
| wrapGStringInto(grString, TRIPLE_DOUBLE_QUOTES); |
| } |
| |
| final GrExpression expression = factory.createExpressionFromText("\"\"\"${}" + literalText + "\"\"\""); |
| |
| expression.getFirstChild().delete();//quote |
| expression.getFirstChild().delete();//empty gstring content |
| expression.getFirstChild().delete();//empty injection |
| |
| final ASTNode node = grString.getNode(); |
| if (expression.getFirstChild() != null) { |
| if (expression.getFirstChild() == expression.getLastChild()) { |
| node.replaceChild(injection.getNode(), expression.getFirstChild().getNode()); |
| } |
| else { |
| node.addChildren(expression.getFirstChild().getNode(), expression.getLastChild().getNode(), injection.getNode()); |
| node.removeChild(injection.getNode()); |
| } |
| } |
| return grString; |
| } |
| |
| private static void wrapGStringInto(GrString grString, String quotes) { |
| GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(grString.getProject()); |
| final PsiElement firstChild = grString.getFirstChild(); |
| final PsiElement lastChild = grString.getLastChild(); |
| |
| final GrExpression template = factory.createExpressionFromText(quotes + "$x" + quotes); |
| if (firstChild != null && |
| firstChild.getNode().getElementType() == GroovyTokenTypes.mGSTRING_BEGIN && |
| !quotes.equals(firstChild.getText())) { |
| grString.getNode().replaceChild(firstChild.getNode(), template.getFirstChild().getNode()); |
| } |
| if (lastChild != null && |
| lastChild.getNode().getElementType() == GroovyTokenTypes.mGSTRING_END && |
| !quotes.equals(lastChild.getText())) { |
| grString.getNode().replaceChild(lastChild.getNode(), template.getLastChild().getNode()); |
| } |
| } |
| |
| public static void wrapInjection(GrStringInjection injection) { |
| final GrExpression expression = injection.getExpression(); |
| LOG.assertTrue(expression != null); |
| final GroovyPsiElementFactory instance = GroovyPsiElementFactory.getInstance(injection.getProject()); |
| final GrClosableBlock closure = instance.createClosureFromText("{foo}"); |
| closure.getNode().replaceChild(closure.getStatements()[0].getNode(), expression.getNode()); |
| injection.getNode().addChild(closure.getNode()); |
| CodeEditUtil.setNodeGeneratedRecursively(expression.getNode(), true); |
| } |
| |
| public static boolean checkGStringInjectionForUnnecessaryBraces(GrStringInjection injection) { |
| final GrClosableBlock block = injection.getClosableBlock(); |
| if (block == null) return false; |
| |
| final GrStatement[] statements = block.getStatements(); |
| if (statements.length != 1) return false; |
| |
| if (!(statements[0] instanceof GrReferenceExpression)) return false; |
| |
| return checkBraceIsUnnecessary(statements[0], injection.getNextSibling()); |
| } |
| |
| private static boolean checkBraceIsUnnecessary(GrStatement injected, PsiElement next) { |
| if (next.getTextLength() == 0) next = next.getNextSibling(); |
| |
| char nextChar = next.getText().charAt(0); |
| if (nextChar == '"' || nextChar == '$') { |
| return true; |
| } |
| final GroovyPsiElementFactory elementFactory = GroovyPsiElementFactory.getInstance(injected.getProject()); |
| final GrExpression gString; |
| try { |
| gString = elementFactory.createExpressionFromText("\"$" + injected.getText() + next.getText() + '"'); |
| } |
| catch (Exception e) { |
| return false; |
| } |
| if (!(gString instanceof GrString)) return false; |
| |
| PsiElement child = gString.getFirstChild(); |
| if (!(child.getNode().getElementType() == GroovyTokenTypes.mGSTRING_BEGIN)) return false; |
| |
| child = child.getNextSibling(); |
| if (child == null || !(child instanceof GrStringContent)) return false; |
| |
| child = child.getNextSibling(); |
| if (child == null || !(child instanceof GrStringInjection)) return false; |
| |
| final PsiElement refExprCopy = ((GrStringInjection)child).getExpression(); |
| if (!(refExprCopy instanceof GrReferenceExpression)) return false; |
| |
| final GrReferenceExpression refExpr = (GrReferenceExpression)injected; |
| return PsiUtil.checkPsiElementsAreEqual(refExpr, refExprCopy); |
| } |
| |
| public static void removeUnnecessaryBracesInGString(GrString grString) { |
| for (GrStringInjection child : grString.getInjections()) { |
| if (checkGStringInjectionForUnnecessaryBraces(child)) { |
| final GrClosableBlock closableBlock = child.getClosableBlock(); |
| final GrReferenceExpression refExpr = (GrReferenceExpression)closableBlock.getStatements()[0]; |
| final GrReferenceExpression copy = (GrReferenceExpression)refExpr.copy(); |
| final ASTNode oldNode = closableBlock.getNode(); |
| oldNode.getTreeParent().replaceChild(oldNode, copy.getNode()); |
| } |
| } |
| } |
| |
| public static String getStartQuote(String text) { |
| if (text.startsWith(TRIPLE_QUOTES)) return TRIPLE_QUOTES; |
| if (text.startsWith(QUOTE)) return QUOTE; |
| if (text.startsWith(TRIPLE_DOUBLE_QUOTES)) return TRIPLE_DOUBLE_QUOTES; |
| if (text.startsWith(DOUBLE_QUOTES)) return DOUBLE_QUOTES; |
| if (text.startsWith(SLASH)) return SLASH; |
| if (text.startsWith(DOLLAR_SLASH)) return DOLLAR_SLASH; |
| return ""; |
| } |
| |
| public static String getEndQuote(String text) { |
| if (text.endsWith(TRIPLE_QUOTES)) return TRIPLE_QUOTES; |
| if (text.endsWith(QUOTE)) return QUOTE; |
| if (text.endsWith(TRIPLE_DOUBLE_QUOTES)) return TRIPLE_DOUBLE_QUOTES; |
| if (text.endsWith(DOUBLE_QUOTES)) return DOUBLE_QUOTES; |
| if (text.endsWith(SLASH)) return SLASH; |
| if (text.endsWith(SLASH_DOLLAR)) return SLASH_DOLLAR; |
| return ""; |
| } |
| |
| |
| public static boolean parseRegexCharacters(@NotNull String chars, |
| @NotNull StringBuilder outChars, |
| @Nullable int[] sourceOffsets, |
| boolean escapeSlash) { |
| assert sourceOffsets == null || sourceOffsets.length == chars.length() + 1; |
| if (chars.indexOf('\\') < 0) { |
| outChars.append(chars); |
| if (sourceOffsets != null) { |
| for (int i = 0; i < sourceOffsets.length; i++) { |
| sourceOffsets[i] = i; |
| } |
| } |
| return true; |
| } |
| |
| int index = 0; |
| final int outOffset = outChars.length(); |
| while (index < chars.length()) { |
| char c = chars.charAt(index++); |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length() - outOffset] = index - 1; |
| sourceOffsets[outChars.length() + 1 - outOffset] = index; |
| } |
| if (c != '\\') { |
| outChars.append(c); |
| continue; |
| } |
| if (index == chars.length()) { |
| outChars.append('\\'); |
| return true; |
| } |
| c = chars.charAt(index++); |
| switch (c) { |
| case '/': |
| if (escapeSlash) { |
| outChars.append(c); |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length() - outOffset] = index; |
| } |
| } |
| else { |
| outChars.append('\\').append('/'); |
| } |
| |
| break; |
| case '\n': |
| //do nothing |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length() - outOffset] = index; |
| } |
| break; |
| case 'u': |
| // uuuuu1234 is valid too |
| while (index != chars.length() && chars.charAt(index) == 'u') { |
| index++; |
| } |
| if (index + 4 <= chars.length()) { |
| try { |
| int code = Integer.parseInt(chars.substring(index, index + 4), 16); |
| c = chars.charAt(index); |
| if (c == '+' || c == '-') return false; |
| outChars.append((char)code); |
| index += 4; |
| |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length() - outOffset] = index; |
| } |
| } |
| catch (Exception e) { |
| return false; |
| } |
| } |
| else { |
| return false; |
| } |
| break; |
| default: |
| outChars.append('\\').append(c); |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length() - outOffset] = index; |
| } |
| |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @see com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl#parseStringCharacters(String, StringBuilder, int[]) |
| */ |
| public static boolean parseStringCharacters(@NotNull String chars, @NotNull StringBuilder outChars, @Nullable int[] sourceOffsets) { |
| assert sourceOffsets == null || sourceOffsets.length == chars.length()+1; |
| if (chars.indexOf('\\') < 0) { |
| outChars.append(chars); |
| if (sourceOffsets != null) { |
| for (int i = 0; i < sourceOffsets.length; i++) { |
| sourceOffsets[i] = i; |
| } |
| } |
| return true; |
| } |
| int index = 0; |
| final int outOffset = outChars.length(); |
| while (index < chars.length()) { |
| char c = chars.charAt(index++); |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length()-outOffset] = index - 1; |
| sourceOffsets[outChars.length() + 1 -outOffset] = index; |
| } |
| if (c != '\\') { |
| outChars.append(c); |
| continue; |
| } |
| if (index == chars.length()) return false; |
| c = chars.charAt(index++); |
| switch (c) { |
| case'b': |
| outChars.append('\b'); |
| break; |
| case't': |
| outChars.append('\t'); |
| break; |
| case'n': |
| outChars.append('\n'); |
| break; |
| case'f': |
| outChars.append('\f'); |
| break; |
| case'r': |
| outChars.append('\r'); |
| break; |
| case'"': |
| outChars.append('\"'); |
| break; |
| case'\'': |
| outChars.append('\''); |
| break; |
| case'$': |
| outChars.append('$'); |
| break; |
| case'\\': |
| outChars.append('\\'); |
| break; |
| case '\n': |
| //do nothing |
| break; |
| |
| case'0': |
| case'1': |
| case'2': |
| case'3': |
| case'4': |
| case'5': |
| case'6': |
| case'7': |
| char startC = c; |
| int v = (int)c - '0'; |
| if (index < chars.length()) { |
| c = chars.charAt(index++); |
| if ('0' <= c && c <= '7') { |
| v <<= 3; |
| v += c - '0'; |
| if (startC <= '3' && index < chars.length()) { |
| c = chars.charAt(index++); |
| if ('0' <= c && c <= '7') { |
| v <<= 3; |
| v += c - '0'; |
| } |
| else { |
| index--; |
| } |
| } |
| } |
| else { |
| index--; |
| } |
| } |
| outChars.append((char)v); |
| break; |
| |
| case'u': |
| // uuuuu1234 is valid too |
| while (index != chars.length() && chars.charAt(index) == 'u') { |
| index++; |
| } |
| if (index + 4 <= chars.length()) { |
| try { |
| int code = Integer.parseInt(chars.substring(index, index + 4), 16); |
| //line separators are invalid here |
| if (code == 0x000a || code == 0x000d) return false; |
| c = chars.charAt(index); |
| if (c == '+' || c == '-') return false; |
| outChars.append((char)code); |
| index += 4; |
| } |
| catch (Exception e) { |
| return false; |
| } |
| } |
| else { |
| return false; |
| } |
| break; |
| default: |
| return false; |
| } |
| if (sourceOffsets != null) { |
| sourceOffsets[outChars.length()-outOffset] = index; |
| } |
| } |
| return true; |
| } |
| |
| public static GrLiteral createStringFromRegex(@NotNull GrLiteral regex) { |
| final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(regex.getProject()); |
| |
| |
| if (regex instanceof GrRegex) { |
| StringBuilder builder = new StringBuilder(); |
| String quote = regex.getText().indexOf('\n') >= 0 ? TRIPLE_DOUBLE_QUOTES : DOUBLE_QUOTES; |
| builder.append(quote); |
| for (PsiElement child = regex.getFirstChild(); child!=null; child = child.getNextSibling()) { |
| final IElementType type = child.getNode().getElementType(); |
| if (type == GroovyTokenTypes.mREGEX_CONTENT || type == GroovyElementTypes.GSTRING_CONTENT) { |
| builder.append(escapeSymbolsForGString(unescapeSlashyString(child.getText()), quote.equals(DOUBLE_QUOTES), true)); |
| } |
| else if (type == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_CONTENT) { |
| builder.append(escapeSymbolsForGString(unescapeDollarSlashyString(child.getText()), quote.equals(DOUBLE_QUOTES), true)); |
| } |
| else if (type == GroovyElementTypes.GSTRING_INJECTION) { |
| builder.append(child.getText()); |
| } |
| } |
| builder.append(quote); |
| return (GrLiteral)factory.createExpressionFromText(builder.toString()); |
| } |
| else { |
| Object value = regex.getValue(); |
| LOG.assertTrue(value==null || value instanceof String); |
| if (value == null) { |
| value = removeQuotes(regex.getText()); |
| } |
| return factory.createLiteralFromValue(value); |
| } |
| } |
| |
| public static boolean isWellEndedString(PsiElement element) { |
| final String text = element.getText(); |
| |
| if (!(text.endsWith("'") || |
| text.endsWith("\"") || |
| text.endsWith("'''") || |
| text.endsWith("\"\"\"") || |
| text.endsWith("/") || |
| text.endsWith("/$"))) { |
| return false; |
| } |
| |
| |
| final IElementType type = element.getNode().getElementType(); |
| if (TokenSets.STRING_LITERAL_SET.contains(type)) return true; |
| |
| final PsiElement lastChild = element.getLastChild(); |
| if (lastChild == null) return false; |
| |
| final IElementType lastType = lastChild.getNode().getElementType(); |
| if (type == GroovyElementTypes.GSTRING) return lastType == GroovyTokenTypes.mGSTRING_END; |
| if (type == GroovyElementTypes.REGEX) return lastType == GroovyTokenTypes.mREGEX_END || lastType == |
| GroovyTokenTypes.mDOLLAR_SLASH_REGEX_END; |
| |
| return false; |
| } |
| |
| public static void fixAllTripleQuotes(StringBuilder builder, int position) { |
| for (int i = builder.indexOf("'''", position); i >= 0; i = builder.indexOf("'''", i)) { |
| builder.replace(i + 2, i + 3, "\\'"); |
| } |
| } |
| |
| public static void fixAllTripleDoubleQuotes(StringBuilder builder, int position) { |
| for (int i = builder.indexOf("\"\"\"", position); i >= 0; i = builder.indexOf("\"\"\"", i)) { |
| builder.replace(i + 2, i + 3, "\\\""); |
| } |
| } |
| |
| public static boolean isPlainStringLiteral(ASTNode node) { |
| String text = node.getText(); |
| return text.length() < 3 && text.equals("''") || text.length() >= 3 && !text.startsWith("'''"); |
| } |
| |
| public static boolean isMultilineStringLiteral(GrLiteral literal) { |
| String quote = getStartQuote(literal.getText()); |
| return TRIPLE_QUOTES.equals(quote) || TRIPLE_DOUBLE_QUOTES.equals(quote) || SLASH.equals(quote) || DOLLAR_SLASH.equals(quote); |
| } |
| |
| public static boolean isSinglelineStringLiteral(GrLiteral literal) { |
| String quote = getStartQuote(literal.getText()); |
| return QUOTE.equals(quote) || DOUBLE_QUOTES.equals(quote); |
| } |
| |
| public static StringBuilder getLiteralTextByValue(String value) { |
| StringBuilder buffer = new StringBuilder(); |
| if (value.indexOf('\n') >= 0) { |
| buffer.append("'''"); |
| escapeStringCharacters(value.length(), value, "", false, true, buffer); |
| buffer.append("'''"); |
| } |
| else { |
| buffer.append("'"); |
| escapeStringCharacters(value.length(), value, "'", false, true, buffer); |
| buffer.append("'"); |
| } |
| return buffer; |
| } |
| |
| public static PsiElement findContainingLiteral(PsiElement token) { |
| |
| PsiElement parent = token.getParent(); |
| if (parent instanceof GrStringContent) parent = parent.getParent(); |
| |
| return parent; |
| } |
| |
| /** |
| * Checks whether a literal is a string literal of any kind |
| */ |
| public static boolean isStringLiteral(GrLiteral literal) { |
| if (literal instanceof GrString) return true; |
| |
| if (literal instanceof GrLiteralImpl) { |
| IElementType type = GrLiteralImpl.getLiteralType(literal); |
| return TokenSets.STRING_LITERAL_SET.contains(type); |
| } |
| |
| return false; |
| } |
| |
| public static boolean isRegex(GrLiteral literal) { |
| if (literal instanceof GrRegex) return true; |
| |
| String quote = getStartQuote(literal.getText()); |
| return SLASH.equals(quote) || DOLLAR_SLASH.equals(quote); |
| } |
| |
| public static boolean isSlashyString(GrLiteral literal) { |
| return SLASH.equals(getStartQuote(literal.getText())); |
| } |
| |
| public static boolean isDollarSlashyString(GrLiteral literal) { |
| return DOLLAR_SLASH.equals(getStartQuote(literal.getText())); |
| } |
| |
| public static boolean isSingleQuoteString(GrLiteral literal) { |
| return QUOTE.equals(getStartQuote(literal.getText())); |
| } |
| |
| public static boolean isDoubleQuoteString(GrLiteral literal) { |
| return DOUBLE_QUOTES.equals(getStartQuote(literal.getText())); |
| } |
| |
| public static boolean isTripleQuoteString(GrLiteral literal) { |
| return TRIPLE_QUOTES.equals(getStartQuote(literal.getText())); |
| } |
| |
| public static boolean isTripleDoubleQuoteString(GrLiteral literal) { |
| return TRIPLE_DOUBLE_QUOTES.equals(getStartQuote(literal.getText())); |
| } |
| } |