Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.intellij.openapi.util.text.StringUtil Maven / Gradle / Ivy
package com.intellij.openapi.util.text;
import com.intellij.openapi.diagnostic.Logger;
import kala.function.CharPredicate;
import org.aya.ij.AyaModified;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class StringUtil extends StringUtilRt {
public static final String ELLIPSIS = "\u2026";
/**
* True if given symbol is white space, tabulation or line feed.
*
* @param c symbol to check
* @return {@code true} if given symbol is white space, tabulation or line feed; {@code false} otherwise
*/
@Contract(pure = true)
public static boolean isWhiteSpace(char c) {
return Strings.isWhiteSpace(c);
}
@Contract(pure = true)
public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
int curLine = 0;
int offset = 0;
while (line != curLine) {
if (offset == text.length()) return -1;
char c = text.charAt(offset);
if (c == '\n') {
curLine++;
} else if (c == '\r') {
curLine++;
if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
offset++;
}
}
offset++;
}
return offset + col;
}
@Contract(pure = true)
public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
LineColumn lineColumn = offsetToLineColumn(text, offset);
return lineColumn != null ? lineColumn.line : -1;
}
@Contract(pure = true)
public static LineColumn offsetToLineColumn(@NotNull CharSequence text, int offset) {
int curLine = 0;
int curLineStart = 0;
int curOffset = 0;
while (curOffset < offset) {
if (curOffset == text.length()) return null;
char c = text.charAt(curOffset);
if (c == '\n') {
curLine++;
curLineStart = curOffset + 1;
} else if (c == '\r') {
curLine++;
if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
curOffset++;
}
curLineStart = curOffset + 1;
}
curOffset++;
}
return LineColumn.of(curLine, offset - curLineStart);
}
public static boolean containLineBreaks(@Nullable CharSequence seq, int fromOffset, int endOffset) {
if (seq == null) return false;
for (int i = fromOffset; i < endOffset; i++) {
final char c = seq.charAt(i);
if (c == '\n' || c == '\r') return true;
}
return false;
}
@Contract(pure = true)
public static int parseInt(@Nullable String string, int defaultValue) {
if (string != null) {
try {
return Integer.parseInt(string);
} catch (NumberFormatException ignored) {
}
}
return defaultValue;
}
@NotNull
@Contract(pure = true)
public static String notNullize(@Nullable String s) {
return notNullize(s, "");
}
@NotNull
@Contract(pure = true)
public static String notNullize(@Nullable String s, @NotNull String defaultValue) {
return s == null ? defaultValue : s;
}
@Contract(value = "null -> true", pure = true)
public static boolean isEmpty(@Nullable CharSequence cs) {
return cs == null || cs.length() == 0;
}
@Contract(pure = true)
public static @NotNull CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
if (text.length() <= length) {
return text;
}
if (appendEllipsis) {
return text.subSequence(0, length) + "...";
}
return text.subSequence(0, length);
}
@Contract(value = "null -> true", pure = true)
public static boolean isEmpty(@Nullable String s) {
return s == null || s.isEmpty();
}
@Contract(value = "null -> null; !null -> !null", pure = true)
public static @Nullable CharSequence trim(@Nullable CharSequence s) {
if (s == null) return null;
int startIndex = 0;
int length = s.length();
if (length == 0) return s;
while (startIndex < length && Character.isWhitespace(s.charAt(startIndex))) startIndex++;
if (startIndex == length) {
return Strings.EMPTY_CHAR_SEQUENCE;
}
int endIndex = length - 1;
while (endIndex >= startIndex && Character.isWhitespace(s.charAt(endIndex))) endIndex--;
endIndex++;
if (startIndex > 0 || endIndex < length) {
return s.subSequence(startIndex, endIndex);
}
return s;
}
@Contract(value = "null -> null; !null -> !null", pure = true)
public static @Nullable String trim(@Nullable String s) {
return s == null ? null : s.trim();
}
public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
assert times >= 0 : times;
try {
for (int i = 0; i < times; i++) {
buffer.append(symbol);
}
} catch (IOException e) {
Logger.getInstance(StringUtil.class).error(e);
}
}
@Contract(pure = true)
public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
return equal(s1, s2, true);
}
@Contract(pure = true) @AyaModified
public static @NotNull String strip(final @NotNull String s, final @NotNull CharPredicate filter) {
final StringBuilder result = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (filter.test(ch)) {
result.append(ch);
}
}
return result.toString();
}
@Contract(pure = true)
public static int indexOfIgnoreCase(@NotNull String where, char what, int fromIndex) {
int sourceCount = where.length();
for (int i = Math.max(fromIndex, 0); i < sourceCount; i++) {
if (charsEqualIgnoreCase(where.charAt(i), what)) {
return i;
}
}
return -1;
}
@Contract(pure = true)
public static boolean charsEqualIgnoreCase(char a, char b) {
return charsMatch(a, b, true);
}
@Contract(pure = true)
public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
return compare(c1, c2, ignoreCase) == 0;
}
@Contract(pure = true)
public static int compare(char c1, char c2, boolean ignoreCase) {
// duplicating String.equalsIgnoreCase logic
int d = c1 - c2;
if (d == 0 || !ignoreCase) {
return d;
}
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = toUpperCase(c1);
char u2 = toUpperCase(c2);
d = u1 - u2;
if (d != 0) {
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
}
return d;
}
@Contract(pure = true)
public static @NotNull String trimEnd(@NotNull String s, @NotNull String suffix) {
return trimEnd(s, suffix, false);
}
@Contract(pure = true)
public static @NotNull String trimEnd(@NotNull String s, @NotNull String suffix, boolean ignoreCase) {
boolean endsWith = ignoreCase ? endsWithIgnoreCase(s, suffix) : s.endsWith(suffix);
if (endsWith) {
return s.substring(0, s.length() - suffix.length());
}
return s;
}
@Contract(pure = true)
public static boolean endsWithIgnoreCase(@NotNull CharSequence text, @NotNull CharSequence suffix) {
int l1 = text.length();
int l2 = suffix.length();
if (l1 < l2) return false;
for (int i = l1 - 1; i >= l1 - l2; i--) {
if (!charsEqualIgnoreCase(text.charAt(i), suffix.charAt(i + l2 - l1))) {
return false;
}
}
return true;
}
@Contract(pure = true)
public static @NotNull String trimTrailing(@NotNull String string) {
return trimTrailing((CharSequence) string).toString();
}
@Contract(pure = true)
public static boolean isHexDigit(char c) {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
}
@Contract(pure = true)
public static @NotNull CharSequence trimTrailing(@NotNull CharSequence string) {
int index = string.length() - 1;
while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
return string.subSequence(0, index + 1);
}
@Contract(pure = true)
public static @NotNull List split(@NotNull String s, @NotNull String separator) {
return split(s, separator, true);
}
@Contract(pure = true)
public static @NotNull List split(@NotNull CharSequence s, @NotNull CharSequence separator) {
return split(s, separator, true, true);
}
@Contract(pure = true)
public static @NotNull List split(@NotNull String s, @NotNull String separator, boolean excludeSeparator) {
return split(s, separator, excludeSeparator, true);
}
@Contract(pure = true)
public static @NotNull List split(@NotNull String s, @NotNull String separator, boolean excludeSeparator, boolean excludeEmptyStrings) {
//noinspection unchecked
return (List) split((CharSequence) s, separator, excludeSeparator, excludeEmptyStrings);
}
@Contract(pure = true)
public static @NotNull List split(@NotNull CharSequence s, @NotNull CharSequence separator, boolean excludeSeparator, boolean excludeEmptyStrings) {
if (separator.length() == 0) {
return Collections.singletonList(s);
}
List result = new ArrayList<>();
int pos = 0;
while (true) {
int index = indexOf(s, separator, pos);
if (index == -1) break;
final int nextPos = index + separator.length();
CharSequence token = s.subSequence(pos, excludeSeparator ? index : nextPos);
if (token.length() != 0 || !excludeEmptyStrings) {
result.add(token);
}
pos = nextPos;
}
if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
result.add(s.subSequence(pos, s.length()));
}
return result;
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
return indexOf(sequence, infix, 0);
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start) {
return indexOf(sequence, infix, start, sequence.length());
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start, int end) {
for (int i = start; i <= end - infix.length(); i++) {
if (startsWith(sequence, i, infix)) {
return i;
}
}
return -1;
}
@Contract(pure = true)
public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
return Strings.startsWith(text, startIndex, prefix);
}
@Contract(pure = true)
public static boolean containsIgnoreCase(@NotNull String where, @NotNull String what) {
return indexOfIgnoreCase(where, what, 0) >= 0;
}
@Contract(pure = true)
public static int indexOfIgnoreCase(@NotNull String where, @NotNull String what, int fromIndex) {
return indexOfIgnoreCase((CharSequence) where, what, fromIndex);
}
/**
* Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
*/
@Contract(pure = true)
public static int indexOfIgnoreCase(@NotNull CharSequence where, @NotNull CharSequence what, int fromIndex) {
return Strings.indexOfIgnoreCase(where, what, fromIndex);
}
@Contract(value = "null -> null; !null -> !null", pure = true)
public static String toLowerCase(@Nullable String str) {
return str == null ? null : str.toLowerCase(Locale.ENGLISH);
}
@Contract(value = "null -> null; !null -> !null", pure = true)
public static String toUpperCase(String s) {
return s == null ? null : s.toUpperCase(Locale.ENGLISH);
}
@Contract(pure = true)
public static char toLowerCase(char a) {
if (a <= 'z') {
return a >= 'A' && a <= 'Z' ? (char) (a + ('a' - 'A')) : a;
}
return Character.toLowerCase(a);
}
@Contract(pure = true)
public static boolean containsAnyChar(final @NotNull String value, final @NotNull String chars) {
return chars.length() > value.length()
? containsAnyChar(value, chars, 0, value.length())
: containsAnyChar(chars, value, 0, chars.length());
}
@Contract(pure = true)
public static boolean containsAnyChar(final @NotNull String value,
final @NotNull String chars,
final int start, final int end) {
for (int i = start; i < end; i++) {
if (chars.indexOf(value.charAt(i)) >= 0) {
return true;
}
}
return false;
}
@Contract(pure = true)
public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
// todo duplicates com.intellij.util.text.VersionComparatorUtil.compare
// todo please refactor next time you make changes here
if (v1 == null && v2 == null) {
return 0;
}
if (v1 == null) {
return -1;
}
if (v2 == null) {
return 1;
}
String[] part1 = v1.split("[._\\-]");
String[] part2 = v2.split("[._\\-]");
int idx = 0;
for (; idx < part1.length && idx < part2.length; idx++) {
String p1 = part1[idx];
String p2 = part2[idx];
int cmp;
if (p1.matches("\\d+") && p2.matches("\\d+")) {
cmp = Integer.valueOf(p1).compareTo(Integer.valueOf(p2));
} else {
cmp = part1[idx].compareTo(part2[idx]);
}
if (cmp != 0) return cmp;
}
if (part1.length != part2.length) {
boolean left = part1.length > idx;
String[] parts = left ? part1 : part2;
for (; idx < parts.length; idx++) {
String p = parts[idx];
int cmp;
if (p.matches("\\d+")) {
cmp = Integer.valueOf(p).compareTo(0);
} else {
cmp = 1;
}
if (cmp != 0) return left ? cmp : -cmp;
}
}
return 0;
}
public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
return s != null && s.length() != 0 && s.charAt(0) == prefix;
}
public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
return s != null && s.length() != 0 && s.charAt(s.length() - 1) == suffix;
}
public static int stringHashCode(@NotNull CharSequence chars) {
return stringHashCode(chars, 0, chars.length(), 0);
}
@Contract(pure = true)
public static int stringHashCode(@NotNull CharSequence chars, int from, int to, int prefixHash) {
int h = prefixHash;
for (int off = from; off < to; off++) {
h = 31 * h + chars.charAt(off);
}
return h;
}
@Contract(pure = true)
public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
return stringHashCodeInsensitive(chars, 0, chars.length());
}
@Contract(pure = true)
public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
return stringHashCodeInsensitive(chars, from, to, 0);
}
@Contract(pure = true)
public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to, int prefixHash) {
int h = prefixHash;
for (int off = from; off < to; off++) {
h = 31 * h + toLowerCase(chars.charAt(off));
}
return h;
}
@Contract(pure = true)
public static boolean containsLineBreak(@NotNull CharSequence text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (isLineBreak(c)) return true;
}
return false;
}
@Contract(pure = true)
public static boolean isLineBreak(char c) {
return c == '\n' || c == '\r';
}
@Contract(pure = true)
public static int countChars(@NotNull CharSequence text, char c) {
return Strings.countChars(text, c);
}
@Contract(pure = true)
public static int getLineBreakCount(@NotNull CharSequence text) {
int count = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n') {
count++;
} else if (c == '\r') {
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
//noinspection AssignmentToForLoopParameter
i++;
}
count++;
}
}
return count;
}
@Contract(pure = true)
public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
return StringUtilRt.endsWith(text, suffix);
}
@Contract(pure = true)
public static @NotNull String wrapWithDoubleQuote(@NotNull String str) {
return '\"' + str + "\"";
}
@Contract(pure = true)
public static boolean startsWithConcatenation(@NotNull String string, String @NotNull ... prefixes) {
int offset = 0;
for (String prefix : prefixes) {
int prefixLen = prefix.length();
if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
return false;
}
offset += prefixLen;
}
return true;
}
@Contract(pure = true)
public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
return equal(s1, s2, false);
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence s, char c) {
return indexOf(s, c, 0, s.length());
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence s, char c, int start) {
return indexOf(s, c, start, s.length());
}
@Contract(pure = true)
public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
end = Math.min(end, s.length());
for (int i = Math.max(start, 0); i < end; i++) {
if (s.charAt(i) == c) return i;
}
return -1;
}
@Contract(value = "null -> true", pure = true)
public static boolean isEmptyOrSpaces(@Nullable CharSequence s) {
return Strings.isEmptyOrSpaces(s);
}
@Contract(pure = true)
public static boolean containsChar(final @NotNull String value, final char ch) {
return Strings.containsChar(value, ch);
}
@Contract(pure = true)
public static @NotNull String shortenTextWithEllipsis(final @NotNull String text, final int maxLength, final int suffixLength) {
return shortenTextWithEllipsis(text, maxLength, suffixLength, false);
}
@Contract(pure = true)
public static @NotNull String shortenTextWithEllipsis(final @NotNull String text,
final int maxLength,
final int suffixLength,
@NotNull String symbol) {
final int textLength = text.length();
if (textLength > maxLength) {
final int prefixLength = maxLength - suffixLength - symbol.length();
assert prefixLength >= 0;
return text.subSequence(0, prefixLength) + symbol + text.subSequence(textLength - suffixLength, text.length());
} else {
return text;
}
}
@Contract(pure = true)
public static @NotNull String shortenTextWithEllipsis(final @NotNull String text,
final int maxLength,
final int suffixLength,
boolean useEllipsisSymbol) {
String symbol = useEllipsisSymbol ? ELLIPSIS : "...";
return shortenTextWithEllipsis(text, maxLength, suffixLength, symbol);
}
public static void escapeStringCharacters(int length, @NotNull String str, @NotNull StringBuilder buffer) {
escapeStringCharacters(length, str, "\"", buffer);
}
public static @NotNull StringBuilder escapeStringCharacters(int length,
@NotNull String str,
@Nullable String additionalChars,
@NotNull StringBuilder buffer) {
return escapeStringCharacters(length, str, additionalChars, true, buffer);
}
public static @NotNull StringBuilder escapeStringCharacters(int length,
@NotNull String str,
@Nullable String additionalChars,
boolean escapeSlash,
@NotNull StringBuilder buffer) {
return escapeStringCharacters(length, str, additionalChars, escapeSlash, true, buffer);
}
public static @NotNull StringBuilder escapeStringCharacters(int length,
@NotNull String str,
@Nullable String additionalChars,
boolean escapeSlash,
boolean escapeUnicode,
@NotNull StringBuilder buffer) {
char prev = 0;
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 '\n':
buffer.append("\\n");
break;
case '\f':
buffer.append("\\f");
break;
case '\r':
buffer.append("\\r");
break;
default:
if (escapeSlash && ch == '\\') {
buffer.append("\\\\");
} else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
buffer.append("\\").append(ch);
} else if (escapeUnicode && !isPrintableUnicode(ch)) {
CharSequence hexCode = toUpperCase(Integer.toHexString(ch));
buffer.append("\\u");
int paddingCount = 4 - hexCode.length();
while (paddingCount-- > 0) {
buffer.append(0);
}
buffer.append(hexCode);
} else {
buffer.append(ch);
}
}
prev = ch;
}
return buffer;
}
@Contract(pure = true)
public static boolean isPrintableUnicode(char c) {
int t = Character.getType(c);
return t != Character.UNASSIGNED && t != Character.LINE_SEPARATOR && t != Character.PARAGRAPH_SEPARATOR &&
t != Character.CONTROL && t != Character.FORMAT && t != Character.PRIVATE_USE && t != Character.SURROGATE;
}
@Contract(pure = true)
public static @NotNull String escapeStringCharacters(@NotNull String s) {
StringBuilder buffer = new StringBuilder(s.length());
escapeStringCharacters(s.length(), s, "\"", buffer);
return buffer.toString();
}
@Contract(pure = true)
public static boolean isOctalDigit(char c) {
return '0' <= c && c <= '7';
}
@Contract(pure = true)
public static @NotNull String unescapeStringCharacters(@NotNull String s) {
StringBuilder buffer = new StringBuilder(s.length());
unescapeStringCharacters(s.length(), s, buffer);
return buffer.toString();
}
private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
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 {
int octalEscapeMaxLength = 2;
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 'u':
if (idx + 4 < length) {
try {
int code = parseInt(s, idx + 1, idx + 5, 16);
//noinspection AssignmentToForLoopParameter
idx += 4;
buffer.append((char) code);
} catch (NumberFormatException e) {
buffer.append("\\u");
}
} else {
buffer.append("\\u");
}
break;
case '0':
case '1':
case '2':
case '3':
octalEscapeMaxLength = 3;
//noinspection fallthrough
case '4':
case '5':
case '6':
case '7':
int escapeEnd = idx + 1;
while (escapeEnd < length && escapeEnd < idx + octalEscapeMaxLength && isOctalDigit(s.charAt(escapeEnd)))
escapeEnd++;
try {
buffer.append((char) parseInt(s, idx, escapeEnd, 8));
} catch (NumberFormatException e) {
throw new RuntimeException("Couldn't parse " + s.subSequence(idx, escapeEnd), e); // shouldn't happen
}
//noinspection AssignmentToForLoopParameter
idx = escapeEnd - 1;
break;
default:
buffer.append(ch);
break;
}
escaped = false;
}
}
if (escaped) buffer.append('\\');
}
public static int parseInt(@NotNull CharSequence s, int x, int y, int radix) {
return Integer.parseInt(s.subSequence(x, y).toString(), radix);
}
/**
* @return true if the string starts and ends with quote (") or apostrophe (')
*/
@Contract(pure = true)
public static boolean isQuotedString(@NotNull String s) {
return s.length() > 1 && (s.charAt(0) == '\'' || s.charAt(0) == '\"') && s.charAt(0) == s.charAt(s.length() - 1);
}
@Contract(pure = true)
public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
int s1Length = s1.length();
int s2Length = s2.length();
if (s1Length == 0 || s2Length == 0) return 0;
int i;
for (i = 0; i < s1Length && i < s2Length; i++) {
if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
break;
}
}
return i;
}
@Contract(pure = true)
public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
return commonPrefixLength(s1, s2, false);
}
@Contract(pure = true)
public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2, boolean ignoreCase) {
int i;
int minLength = Math.min(s1.length(), s2.length());
for (i = 0; i < minLength; i++) {
if (!Strings.charsMatch(s1.charAt(i), s2.charAt(i), ignoreCase)) {
break;
}
}
return i;
}
/**
* C/C++ escaping cppref
*/
@NotNull
public static String unescapeAnsiStringCharacters(@NotNull String s) {
StringBuilder buffer = new StringBuilder();
int length = s.length();
int count = 0;
int radix = 0;
int suffixLen = 0;
boolean decode = false;
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 '\'' -> buffer.append((char) 0x27);
case '\"' -> buffer.append((char) 0x22);
case '?' -> buffer.append((char) 0x3f);
case '\\' -> buffer.append((char) 0x5c);
case 'a' -> buffer.append((char) 0x07);
case 'b' -> buffer.append((char) 0x08);
case 'f' -> buffer.append((char) 0x0c);
case 'n' -> buffer.append((char) 0x0a);
case 'r' -> buffer.append((char) 0x0d);
case 't' -> buffer.append((char) 0x09);
case 'v' -> buffer.append((char) 0x0b);
case '0', '1', '2', '3', '4', '5', '6', '7' -> {
count = 3;
radix = 8;
suffixLen = 0;
decode = true;
}
case 'x' -> {
count = 2;
radix = 0x10;
suffixLen = 1;
decode = true;
}
case 'u' -> {
count = 4;
radix = 0x10;
suffixLen = 1;
decode = true;
}
case 'U' -> {
count = 8;
radix = 0x10;
suffixLen = 1;
decode = true;
}
default -> buffer.append(ch);
}
if (decode) {
decode = false;
StringBuilder sb = new StringBuilder(count);
for (int pos = idx + suffixLen; pos < length && count > 0; ++pos) {
char chl = s.charAt(pos);
if (!(radix == 0x10 && StringUtil.isHexDigit(chl) || radix == 8 && StringUtil.isOctalDigit(chl))) {
break;
}
sb.append(chl);
--count;
}
if (sb.length() != 0) {
try {
long code = Long.parseLong(sb.toString(), radix);
//noinspection AssignmentToForLoopParameter
idx += sb.length() + suffixLen - 1;
// todo: implement UTF-32 support
//if (code > 0xFFFFL) {
// OCLog.LOG.warn("U32 char is not supported:" + code + ", reduced to " + (char)code);
//}
buffer.append((char) code);
} catch (NumberFormatException e) {
buffer.append('\\').append(ch);
}
} else {
buffer.append('\\').append(ch);
}
}
escaped = false;
}
}
if (escaped) buffer.append('\\');
return buffer.toString();
}
/**
* Allows to answer if target symbol is contained at given char sequence at {@code [start; end)} interval.
*
* @param s target char sequence to check
* @param start start offset to use within the given char sequence (inclusive)
* @param end end offset to use within the given char sequence (exclusive)
* @param c target symbol to check
* @return {@code true} if given symbol is contained at the target range of the given char sequence;
* {@code false} otherwise
*/
@Contract(pure = true)
public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
return Strings.contains(s, start, end, c);
}
}