All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy