2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.util.text;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.ProcessCanceledException;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.util.TextRange;
22 import com.intellij.util.*;
23 import com.intellij.util.containers.ContainerUtil;
24 import com.intellij.util.text.CharArrayCharSequence;
25 import com.intellij.util.text.CharArrayUtil;
26 import com.intellij.util.text.CharSequenceSubSequence;
27 import com.intellij.util.text.StringFactory;
28 import org.jetbrains.annotations.Contract;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
33 import java.beans.Introspector;
34 import java.io.IOException;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
39 //TeamCity inherits StringUtil: do not add private constructors!!!
40 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "MethodOverridesStaticMethodOfSuperclass"})
41 public class StringUtil extends StringUtilRt {
42 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.text.StringUtil");
44 @NonNls private static final String VOWELS = "aeiouy";
45 @NonNls private static final Pattern EOL_SPLIT_KEEP_SEPARATORS = Pattern.compile("(?<=(\r\n|\n))|(?<=\r)(?=[^\n])");
46 @NonNls private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r|\n|\r\n)+ *");
47 @NonNls private static final Pattern EOL_SPLIT_PATTERN_WITH_EMPTY = Pattern.compile(" *(\r|\n|\r\n) *");
48 @NonNls private static final Pattern EOL_SPLIT_DONT_TRIM_PATTERN = Pattern.compile("(\r|\n|\r\n)+");
50 public static final NotNullFunction<String, String> QUOTER = new NotNullFunction<String, String>() {
53 public String fun(String s) {
54 return "\"" + s + "\"";
58 public static final NotNullFunction<String, String> SINGLE_QUOTER = new NotNullFunction<String, String>() {
61 public String fun(String s) {
67 @Contract(pure = true)
68 public static List<String> getWordsInStringLongestFirst(@NotNull String find) {
69 List<String> words = getWordsIn(find);
70 // hope long words are rare
71 Collections.sort(words, new Comparator<String>() {
73 public int compare(@NotNull final String o1, @NotNull final String o2) {
74 return o2.length() - o1.length();
81 @Contract(pure = true)
82 public static String escapePattern(@NotNull final String text) {
83 return replace(replace(text, "'", "''"), "{", "'{'");
87 @Contract(pure = true)
88 public static <T> Function<T, String> createToStringFunction(@NotNull Class<T> cls) {
89 return new Function<T, String>() {
91 public String fun(@NotNull T o) {
98 public static Function<String, String> TRIMMER = new Function<String, String>() {
101 public String fun(@Nullable String s) {
107 @Contract(pure = true)
108 public static String replace(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
109 return replace(text, oldS, newS, false);
113 @Contract(pure = true)
114 public static String replaceIgnoreCase(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
115 return replace(text, oldS, newS, true);
118 public static void replaceChar(@NotNull char[] buffer, char oldChar, char newChar, int start, int end) {
119 for (int i = start; i < end; i++) {
128 @Contract(pure = true)
129 public static String replaceChar(@NotNull String buffer, char oldChar, char newChar) {
130 StringBuilder newBuffer = null;
131 for (int i = 0; i < buffer.length(); i++) {
132 char c = buffer.charAt(i);
134 if (newBuffer == null) {
135 newBuffer = new StringBuilder(buffer.length());
136 newBuffer.append(buffer, 0, i);
139 newBuffer.append(newChar);
141 else if (newBuffer != null) {
145 return newBuffer == null ? buffer : newBuffer.toString();
148 @Contract(pure = true)
149 public static String replace(@NonNls @NotNull final String text, @NonNls @NotNull final String oldS, @NonNls @NotNull final String newS, final boolean ignoreCase) {
150 if (text.length() < oldS.length()) return text;
152 StringBuilder newText = null;
155 while (i < text.length()) {
156 final int index = ignoreCase? indexOfIgnoreCase(text, oldS, i) : text.indexOf(oldS, i);
162 newText.append(text, i, text.length());
166 if (newText == null) {
167 if (text.length() == oldS.length()) {
170 newText = new StringBuilder(text.length() - i);
173 newText.append(text, i, index);
174 newText.append(newS);
175 i = index + oldS.length();
178 return newText != null ? newText.toString() : "";
182 * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
184 @Contract(pure = true)
185 public static int indexOfIgnoreCase(@NotNull String where, @NotNull String what, int fromIndex) {
186 int targetCount = what.length();
187 int sourceCount = where.length();
189 if (fromIndex >= sourceCount) {
190 return targetCount == 0 ? sourceCount : -1;
197 if (targetCount == 0) {
201 char first = what.charAt(0);
202 int max = sourceCount - targetCount;
204 for (int i = fromIndex; i <= max; i++) {
205 /* Look for first character. */
206 if (!charsEqualIgnoreCase(where.charAt(i), first)) {
207 while (++i <= max && !charsEqualIgnoreCase(where.charAt(i), first)) ;
210 /* Found first character, now look at the rest of v2 */
213 int end = j + targetCount - 1;
214 for (int k = 1; j < end && charsEqualIgnoreCase(where.charAt(j), what.charAt(k)); j++, k++) ;
217 /* Found whole string. */
226 @Contract(pure = true)
227 public static int indexOfIgnoreCase(@NotNull String where, char what, int fromIndex) {
228 int sourceCount = where.length();
230 if (fromIndex >= sourceCount) {
238 for (int i = fromIndex; i < sourceCount; i++) {
239 if (charsEqualIgnoreCase(where.charAt(i), what)) {
247 @Contract(pure = true)
248 public static boolean containsIgnoreCase(@NotNull String where, @NotNull String what) {
249 return indexOfIgnoreCase(where, what, 0) >= 0;
252 @Contract(pure = true)
253 public static boolean endsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String suffix) {
254 return StringUtilRt.endsWithIgnoreCase(str, suffix);
257 @Contract(pure = true)
258 public static boolean startsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String prefix) {
259 return StringUtilRt.startsWithIgnoreCase(str, prefix);
262 @Contract(pure = true)
264 public static String stripHtml(@NotNull String html, boolean convertBreaks) {
266 html = html.replaceAll("<br/?>", "\n\n");
269 return html.replaceAll("<(.|\n)*?>", "");
272 @Contract(value = "null -> null; !null -> !null", pure = true)
273 public static String toLowerCase(@Nullable final String str) {
274 //noinspection ConstantConditions
275 return str == null ? null : str.toLowerCase();
279 @Contract(pure = true)
280 public static String getPackageName(@NotNull String fqName) {
281 return getPackageName(fqName, '.');
285 @Contract(pure = true)
286 public static String getPackageName(@NotNull String fqName, char separator) {
287 int lastPointIdx = fqName.lastIndexOf(separator);
288 if (lastPointIdx >= 0) {
289 return fqName.substring(0, lastPointIdx);
294 @Contract(pure = true)
295 public static int getLineBreakCount(@NotNull CharSequence text) {
297 for (int i = 0; i < text.length(); i++) {
298 char c = text.charAt(i);
302 else if (c == '\r') {
303 if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
304 //noinspection AssignmentToForLoopParameter
316 @Contract(pure = true)
317 public static boolean containsLineBreak(@NotNull CharSequence text) {
318 for (int i = 0; i < text.length(); i++) {
319 char c = text.charAt(i);
320 if (isLineBreak(c)) return true;
325 @Contract(pure = true)
326 public static boolean isLineBreak(char c) {
327 return c == '\n' || c == '\r';
331 @Contract(pure = true)
332 public static String escapeLineBreak(@NotNull String text) {
333 StringBuilder buffer = new StringBuilder(text.length());
334 for (int i = 0; i < text.length(); i++) {
335 char c = text.charAt(i);
338 buffer.append("\\n");
341 buffer.append("\\r");
347 return buffer.toString();
350 @Contract(pure = true)
351 public static boolean endsWithLineBreak(@NotNull CharSequence text) {
352 int len = text.length();
353 return len > 0 && isLineBreak(text.charAt(len - 1));
356 @Contract(pure = true)
357 public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
360 while (line != curLine) {
361 if (offset == text.length()) return -1;
362 char c = text.charAt(offset);
366 else if (c == '\r') {
368 if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
377 @Contract(pure = true)
378 public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
381 while (curOffset < offset) {
382 if (curOffset == text.length()) return -1;
383 char c = text.charAt(curOffset);
387 else if (c == '\r') {
389 if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
399 * Classic dynamic programming algorithm for string differences.
401 @Contract(pure = true)
402 public static int difference(@NotNull String s1, @NotNull String s2) {
403 int[][] a = new int[s1.length()][s2.length()];
405 for (int i = 0; i < s1.length(); i++) {
409 for (int j = 0; j < s2.length(); j++) {
413 for (int i = 1; i < s1.length(); i++) {
414 for (int j = 1; j < s2.length(); j++) {
416 a[i][j] = Math.min(Math.min(a[i - 1][j - 1] + (s1.charAt(i) == s2.charAt(j) ? 0 : 1), a[i - 1][j] + 1), a[i][j - 1] + 1);
420 return a[s1.length() - 1][s2.length() - 1];
424 @Contract(pure = true)
425 public static String wordsToBeginFromUpperCase(@NotNull String s) {
426 return fixCapitalization(s, ourPrepositions, true);
430 @Contract(pure = true)
431 public static String wordsToBeginFromLowerCase(@NotNull String s) {
432 return fixCapitalization(s, ourPrepositions, false);
436 @Contract(pure = true)
437 public static String toTitleCase(@NotNull String s) {
438 return fixCapitalization(s, ArrayUtil.EMPTY_STRING_ARRAY, true);
442 private static String fixCapitalization(@NotNull String s, @NotNull String[] prepositions, boolean title) {
443 StringBuilder buffer = null;
444 for (int i = 0; i < s.length(); i++) {
445 char prevChar = i == 0 ? ' ' : s.charAt(i - 1);
446 char currChar = s.charAt(i);
447 if (!Character.isLetterOrDigit(prevChar) && prevChar != '\'') {
448 if (Character.isLetterOrDigit(currChar)) {
449 if (title || Character.isUpperCase(currChar)) {
451 for (; j < s.length(); j++) {
452 if (!Character.isLetterOrDigit(s.charAt(j))) {
456 if (!isPreposition(s, i, j - 1, prepositions)) {
457 if (buffer == null) {
458 buffer = new StringBuilder(s);
460 buffer.setCharAt(i, title ? toUpperCase(currChar) : toLowerCase(currChar));
466 if (buffer == null) {
470 return buffer.toString();
474 @NonNls private static final String[] ourPrepositions = {
475 "a", "an", "and", "as", "at", "but", "by", "down", "for", "from", "if", "in", "into", "not", "of", "on", "onto", "or", "out", "over",
476 "per", "nor", "the", "to", "up", "upon", "via", "with"
479 @Contract(pure = true)
480 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar) {
481 return isPreposition(s, firstChar, lastChar, ourPrepositions);
484 @Contract(pure = true)
485 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar, @NotNull String[] prepositions) {
486 for (String preposition : prepositions) {
487 boolean found = false;
488 if (lastChar - firstChar + 1 == preposition.length()) {
490 for (int j = 0; j < preposition.length(); j++) {
491 if (!(toLowerCase(s.charAt(firstChar + j)) == preposition.charAt(j))) {
504 @Contract(pure = true)
505 public static NotNullFunction<String, String> escaper(final boolean escapeSlash, @Nullable final String additionalChars) {
506 return new NotNullFunction<String, String>() {
509 public String fun(@NotNull String dom) {
510 final StringBuilder builder = new StringBuilder(dom.length());
511 escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder);
512 return builder.toString();
518 public static void escapeStringCharacters(int length, @NotNull String str, @NotNull @NonNls StringBuilder buffer) {
519 escapeStringCharacters(length, str, "\"", buffer);
523 public static StringBuilder escapeStringCharacters(int length,
525 @Nullable String additionalChars,
526 @NotNull @NonNls StringBuilder buffer) {
527 return escapeStringCharacters(length, str, additionalChars, true, buffer);
531 public static StringBuilder escapeStringCharacters(int length,
533 @Nullable String additionalChars,
535 @NotNull @NonNls StringBuilder buffer) {
537 for (int idx = 0; idx < length; idx++) {
538 char ch = str.charAt(idx);
541 buffer.append("\\b");
545 buffer.append("\\t");
549 buffer.append("\\n");
553 buffer.append("\\f");
557 buffer.append("\\r");
561 if (escapeSlash && ch == '\\') {
562 buffer.append("\\\\");
564 else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
565 buffer.append("\\").append(ch);
567 else if (!isPrintableUnicode(ch)) {
568 String hexCode = StringUtilRt.toUpperCase(Integer.toHexString(ch));
569 buffer.append("\\u");
570 int paddingCount = 4 - hexCode.length();
571 while (paddingCount-- > 0) {
574 buffer.append(hexCode);
585 @Contract(pure = true)
586 private static boolean isPrintableUnicode(char c) {
587 int t = Character.getType(c);
588 return t != Character.UNASSIGNED && t != Character.LINE_SEPARATOR && t != Character.PARAGRAPH_SEPARATOR &&
589 t != Character.CONTROL && t != Character.FORMAT && t != Character.PRIVATE_USE && t != Character.SURROGATE;
593 @Contract(pure = true)
594 public static String escapeStringCharacters(@NotNull String s) {
595 StringBuilder buffer = new StringBuilder(s.length());
596 escapeStringCharacters(s.length(), s, "\"", buffer);
597 return buffer.toString();
601 @Contract(pure = true)
602 public static String escapeCharCharacters(@NotNull String s) {
603 StringBuilder buffer = new StringBuilder(s.length());
604 escapeStringCharacters(s.length(), s, "\'", buffer);
605 return buffer.toString();
609 @Contract(pure = true)
610 public static String unescapeStringCharacters(@NotNull String s) {
611 StringBuilder buffer = new StringBuilder(s.length());
612 unescapeStringCharacters(s.length(), s, buffer);
613 return buffer.toString();
616 private static boolean isQuoteAt(@NotNull String s, int ind) {
617 char ch = s.charAt(ind);
618 return ch == '\'' || ch == '\"';
621 @Contract(pure = true)
622 public static boolean isQuotedString(@NotNull String s) {
623 return s.length() > 1 && isQuoteAt(s, 0) && s.charAt(0) == s.charAt(s.length() - 1);
627 @Contract(pure = true)
628 public static String unquoteString(@NotNull String s) {
629 if (isQuotedString(s)) {
630 return s.substring(1, s.length() - 1);
636 @Contract(pure = true)
637 public static String unquoteString(@NotNull String s, char quotationChar) {
638 if (s.length() > 1 && quotationChar == s.charAt(0) && quotationChar == s.charAt(s.length() - 1)) {
639 return s.substring(1, s.length() - 1);
645 * This is just an optimized version of Matcher.quoteReplacement
648 @Contract(pure = true)
649 public static String quoteReplacement(@NotNull String s) {
650 boolean needReplacements = false;
652 for (int i = 0; i < s.length(); i++) {
653 char c = s.charAt(i);
654 if (c == '\\' || c == '$') {
655 needReplacements = true;
660 if (!needReplacements) return s;
662 StringBuilder sb = new StringBuilder(s.length() * 6 / 5);
663 for (int i = 0; i < s.length(); i++) {
664 char c = s.charAt(i);
677 return sb.toString();
680 private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
681 boolean escaped = false;
682 for (int idx = 0; idx < length; idx++) {
683 char ch = s.charAt(idx);
727 if (idx + 4 < length) {
729 int code = Integer.parseInt(s.substring(idx + 1, idx + 5), 16);
731 buffer.append((char)code);
733 catch (NumberFormatException e) {
734 buffer.append("\\u");
738 buffer.append("\\u");
750 if (escaped) buffer.append('\\');
753 @SuppressWarnings({"HardCodedStringLiteral"})
755 @Contract(pure = true)
756 public static String pluralize(@NotNull String suggestion) {
757 if (suggestion.endsWith("Child") || suggestion.endsWith("child")) {
758 return suggestion + "ren";
761 if (suggestion.equals("this")) {
764 if (suggestion.equals("This")) {
768 if (endsWithIgnoreCase(suggestion, "es")) {
772 if (endsWithIgnoreCase(suggestion, "s") || endsWithIgnoreCase(suggestion, "x") || endsWithIgnoreCase(suggestion, "ch")) {
773 return suggestion + "es";
776 int len = suggestion.length();
777 if (endsWithIgnoreCase(suggestion, "y") && len > 1 && !isVowel(toLowerCase(suggestion.charAt(len - 2)))) {
778 return suggestion.substring(0, len - 1) + "ies";
781 return suggestion + "s";
785 @Contract(pure = true)
786 public static String capitalizeWords(@NotNull String text,
788 return capitalizeWords(text, " \t\n\r\f", allWords, false);
792 @Contract(pure = true)
793 public static String capitalizeWords(@NotNull String text,
794 @NotNull String tokenizerDelim,
796 boolean leaveOriginalDelims) {
797 final StringTokenizer tokenizer = new StringTokenizer(text, tokenizerDelim, leaveOriginalDelims);
798 final StringBuilder out = new StringBuilder(text.length());
799 boolean toCapitalize = true;
800 while (tokenizer.hasMoreTokens()) {
801 final String word = tokenizer.nextToken();
802 if (!leaveOriginalDelims && out.length() > 0) {
805 out.append(toCapitalize ? capitalize(word) : word);
807 toCapitalize = false;
810 return out.toString();
813 @Contract(pure = true)
814 public static String decapitalize(String s) {
815 return Introspector.decapitalize(s);
818 @Contract(pure = true)
819 public static boolean isVowel(char c) {
820 return VOWELS.indexOf(c) >= 0;
824 @Contract(pure = true)
825 public static String capitalize(@NotNull String s) {
826 if (s.isEmpty()) return s;
827 if (s.length() == 1) return StringUtilRt.toUpperCase(s);
830 if (Character.isUpperCase(s.charAt(0))) return s;
831 return toUpperCase(s.charAt(0)) + s.substring(1);
834 @Contract(value = "null -> false", pure = true)
835 public static boolean isCapitalized(@Nullable String s) {
836 return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0));
840 @Contract(pure = true)
841 public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
842 if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
845 return capitalize(s);
848 @Contract(pure = true)
849 public static int stringHashCode(@NotNull CharSequence chars) {
850 if (chars instanceof String) return chars.hashCode();
851 if (chars instanceof CharSequenceWithStringHash) return chars.hashCode();
852 if (chars instanceof CharArrayCharSequence) return chars.hashCode();
854 return stringHashCode(chars, 0, chars.length());
857 @Contract(pure = true)
858 public static int stringHashCode(@NotNull CharSequence chars, int from, int to) {
860 for (int off = from; off < to; off++) {
861 h = 31 * h + chars.charAt(off);
866 @Contract(pure = true)
867 public static int stringHashCode(char[] chars, int from, int to) {
869 for (int off = from; off < to; off++) {
870 h = 31 * h + chars[off];
875 @Contract(pure = true)
876 public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) {
878 for (int off = from; off < to; off++) {
879 h = 31 * h + toLowerCase(chars[off]);
884 @Contract(pure = true)
885 public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
887 for (int off = from; off < to; off++) {
888 h = 31 * h + toLowerCase(chars.charAt(off));
893 @Contract(pure = true)
894 public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
895 return stringHashCodeInsensitive(chars, 0, chars.length());
898 @Contract(pure = true)
899 public static int stringHashCodeIgnoreWhitespaces(char[] chars, int from, int to) {
901 for (int off = from; off < to; off++) {
903 if (!isWhiteSpace(c)) {
910 @Contract(pure = true)
911 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars, int from, int to) {
913 for (int off = from; off < to; off++) {
914 char c = chars.charAt(off);
915 if (!isWhiteSpace(c)) {
922 @Contract(pure = true)
923 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars) {
924 return stringHashCodeIgnoreWhitespaces(chars, 0, chars.length());
928 * Equivalent to string.startsWith(prefixes[0] + prefixes[1] + ...) but avoids creating an object for concatenation.
930 @Contract(pure = true)
931 public static boolean startsWithConcatenation(@NotNull String string, @NotNull String... prefixes) {
933 for (String prefix : prefixes) {
934 int prefixLen = prefix.length();
935 if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
944 * @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14).
946 @SuppressWarnings("UnusedDeclaration")
947 @Contract(pure = true)
948 public static boolean startsWithConcatenationOf(@NotNull String string, @NotNull String firstPrefix, @NotNull String secondPrefix) {
949 return startsWithConcatenation(string, firstPrefix, secondPrefix);
953 * @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14).
955 @SuppressWarnings("UnusedDeclaration")
956 @Contract(pure = true)
957 public static boolean startsWithConcatenationOf(@NotNull String string,
958 @NotNull String firstPrefix,
959 @NotNull String secondPrefix,
960 @NotNull String thirdPrefix) {
961 return startsWithConcatenation(string, firstPrefix, secondPrefix, thirdPrefix);
964 @Contract(value = "null -> null; !null -> !null", pure = true)
965 public static String trim(@Nullable String s) {
966 return s == null ? null : s.trim();
970 @Contract(pure = true)
971 public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix) {
972 if (s.endsWith(suffix)) {
973 return s.substring(0, s.length() - suffix.length());
979 @Contract(pure = true)
980 public static String trimLog(@NotNull final String text, final int limit) {
981 if (limit > 5 && text.length() > limit) {
982 return text.substring(0, limit - 5) + " ...\n";
988 @Contract(pure = true)
989 public static String trimLeading(@NotNull String string) {
990 return trimLeading((CharSequence)string).toString();
993 @Contract(pure = true)
994 public static CharSequence trimLeading(@NotNull CharSequence string) {
996 while (index < string.length() && Character.isWhitespace(string.charAt(index))) index++;
997 return string.subSequence(index, string.length());
1001 @Contract(pure = true)
1002 public static String trimLeading(@NotNull String string, char symbol) {
1004 while (index < string.length() && string.charAt(index) == symbol) index++;
1005 return string.substring(index);
1009 @Contract(pure = true)
1010 public static String trimTrailing(@NotNull String string) {
1011 return trimTrailing((CharSequence)string).toString();
1015 @Contract(pure = true)
1016 public static CharSequence trimTrailing(@NotNull CharSequence string) {
1017 int index = string.length() - 1;
1018 while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
1019 return string.subSequence(0, index + 1);
1022 @Contract(pure = true)
1023 public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
1024 return s != null && s.length() != 0 && s.charAt(0) == prefix;
1027 @Contract(pure = true)
1028 public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
1029 return StringUtilRt.endsWithChar(s, suffix);
1033 @Contract(pure = true)
1034 public static String trimStart(@NotNull String s, @NonNls @NotNull String prefix) {
1035 if (s.startsWith(prefix)) {
1036 return s.substring(prefix.length());
1042 @Contract(pure = true)
1043 public static String pluralize(@NotNull String base, int n) {
1044 if (n == 1) return base;
1045 return pluralize(base);
1048 public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
1049 assert times >= 0 : times;
1051 for (int i = 0; i < times; i++) {
1052 buffer.append(symbol);
1055 catch (IOException e) {
1060 @Contract(pure = true)
1061 public static String defaultIfEmpty(@Nullable String value, String defaultValue) {
1062 return isEmpty(value) ? defaultValue : value;
1065 @Contract(value = "null -> false", pure = true)
1066 public static boolean isNotEmpty(@Nullable String s) {
1067 return s != null && !s.isEmpty();
1070 @Contract(value = "null -> true", pure=true)
1071 public static boolean isEmpty(@Nullable String s) {
1072 return s == null || s.isEmpty();
1075 @Contract(value = "null -> true",pure = true)
1076 public static boolean isEmpty(@Nullable CharSequence cs) {
1077 return cs == null || cs.length() == 0;
1080 @Contract(pure = true)
1081 public static int length(@Nullable CharSequence cs) {
1082 return cs == null ? 0 : cs.length();
1086 @Contract(pure = true)
1087 public static String notNullize(@Nullable final String s) {
1088 return notNullize(s, "");
1092 @Contract(pure = true)
1093 public static String notNullize(@Nullable final String s, @NotNull String defaultValue) {
1094 return s == null ? defaultValue : s;
1098 @Contract(pure = true)
1099 public static String nullize(@Nullable final String s) {
1100 return nullize(s, false);
1104 @Contract(pure = true)
1105 public static String nullize(@Nullable final String s, boolean nullizeSpaces) {
1106 if (nullizeSpaces) {
1107 if (isEmptyOrSpaces(s)) return null;
1110 if (isEmpty(s)) return null;
1115 @Contract(value = "null -> true",pure = true)
1116 // we need to keep this method to preserve backward compatibility
1117 public static boolean isEmptyOrSpaces(@Nullable String s) {
1118 return isEmptyOrSpaces(((CharSequence)s));
1121 @Contract(value = "null -> true", pure = true)
1122 public static boolean isEmptyOrSpaces(@Nullable CharSequence s) {
1126 for (int i = 0; i < s.length(); i++) {
1127 if (s.charAt(i) > ' ') {
1135 * Allows to answer if given symbol is white space, tabulation or line feed.
1137 * @param c symbol to check
1138 * @return <code>true</code> if given symbol is white space, tabulation or line feed; <code>false</code> otherwise
1140 @Contract(pure = true)
1141 public static boolean isWhiteSpace(char c) {
1142 return c == '\n' || c == '\t' || c == ' ';
1146 @Contract(pure = true)
1147 public static String getThrowableText(@NotNull Throwable aThrowable) {
1148 return ExceptionUtil.getThrowableText(aThrowable);
1152 @Contract(pure = true)
1153 public static String getThrowableText(@NotNull Throwable aThrowable, @NonNls @NotNull final String stackFrameSkipPattern) {
1154 return ExceptionUtil.getThrowableText(aThrowable, stackFrameSkipPattern);
1158 @Contract(pure = true)
1159 public static String getMessage(@NotNull Throwable e) {
1160 return ExceptionUtil.getMessage(e);
1164 @Contract(pure = true)
1165 public static String repeatSymbol(final char aChar, final int count) {
1166 char[] buffer = new char[count];
1167 Arrays.fill(buffer, aChar);
1168 return StringFactory.createShared(buffer);
1172 @Contract(pure = true)
1173 public static String repeat(@NotNull String s, int count) {
1174 assert count >= 0 : count;
1175 StringBuilder sb = new StringBuilder(s.length() * count);
1176 for (int i = 0; i < count; i++) {
1179 return sb.toString();
1183 @Contract(pure = true)
1184 public static List<String> splitHonorQuotes(@NotNull String s, char separator) {
1185 final List<String> result = new ArrayList<String>();
1186 final StringBuilder builder = new StringBuilder(s.length());
1187 boolean inQuotes = false;
1188 for (int i = 0; i < s.length(); i++) {
1189 final char c = s.charAt(i);
1190 if (c == separator && !inQuotes) {
1191 if (builder.length() > 0) {
1192 result.add(builder.toString());
1193 builder.setLength(0);
1198 if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
1199 inQuotes = !inQuotes;
1204 if (builder.length() > 0) {
1205 result.add(builder.toString());
1212 @Contract(pure = true)
1213 public static List<String> split(@NotNull String s, @NotNull String separator) {
1214 return split(s, separator, true);
1217 @Contract(pure = true)
1218 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator) {
1219 return split(s, separator, true, true);
1223 @Contract(pure = true)
1224 public static List<String> split(@NotNull String s, @NotNull String separator,
1225 boolean excludeSeparator) {
1226 return split(s, separator, excludeSeparator, true);
1230 @Contract(pure = true)
1231 public static List<String> split(@NotNull String s, @NotNull String separator,
1232 boolean excludeSeparator, boolean excludeEmptyStrings) {
1233 return (List)split((CharSequence)s,separator,excludeSeparator,excludeEmptyStrings);
1236 @Contract(pure = true)
1237 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator,
1238 boolean excludeSeparator, boolean excludeEmptyStrings) {
1239 if (separator.length() == 0) {
1240 return Collections.singletonList(s);
1242 List<CharSequence> result = new ArrayList<CharSequence>();
1245 int index = indexOf(s,separator, pos);
1246 if (index == -1) break;
1247 final int nextPos = index + separator.length();
1248 CharSequence token = s.subSequence(pos, excludeSeparator ? index : nextPos);
1249 if (token.length() != 0 || !excludeEmptyStrings) {
1254 if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
1255 result.add(s.subSequence(pos, s.length()));
1261 @Contract(pure = true)
1262 public static Iterable<String> tokenize(@NotNull String s, @NotNull String separators) {
1263 final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators);
1264 return new Iterable<String>() {
1267 public Iterator<String> iterator() {
1268 return new Iterator<String>() {
1270 public boolean hasNext() {
1271 return tokenizer.hasMoreTokens();
1275 public String next() {
1276 return tokenizer.nextToken();
1280 public void remove() {
1281 throw new UnsupportedOperationException();
1289 @Contract(pure = true)
1290 public static Iterable<String> tokenize(@NotNull final StringTokenizer tokenizer) {
1291 return new Iterable<String>() {
1294 public Iterator<String> iterator() {
1295 return new Iterator<String>() {
1297 public boolean hasNext() {
1298 return tokenizer.hasMoreTokens();
1302 public String next() {
1303 return tokenizer.nextToken();
1307 public void remove() {
1308 throw new UnsupportedOperationException();
1316 * @return list containing all words in {@code text}, or {@link ContainerUtil#emptyList()} if there are none.
1317 * The <b>word</b> here means the maximum sub-string consisting entirely of characters which are <code>Character.isJavaIdentifierPart(c)</code>.
1320 @Contract(pure = true)
1321 public static List<String> getWordsIn(@NotNull String text) {
1322 List<String> result = null;
1324 for (int i = 0; i < text.length(); i++) {
1325 char c = text.charAt(i);
1326 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1327 if (isIdentifierPart && start == -1) {
1330 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1331 if (result == null) {
1332 result = new SmartList<String>();
1334 result.add(text.substring(start, i + 1));
1336 else if (!isIdentifierPart && start != -1) {
1337 if (result == null) {
1338 result = new SmartList<String>();
1340 result.add(text.substring(start, i));
1344 if (result == null) {
1345 return ContainerUtil.emptyList();
1351 @Contract(pure = true)
1352 public static List<TextRange> getWordIndicesIn(@NotNull String text) {
1353 List<TextRange> result = new SmartList<TextRange>();
1355 for (int i = 0; i < text.length(); i++) {
1356 char c = text.charAt(i);
1357 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1358 if (isIdentifierPart && start == -1) {
1361 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1362 result.add(new TextRange(start, i + 1));
1364 else if (!isIdentifierPart && start != -1) {
1365 result.add(new TextRange(start, i));
1373 @Contract(pure = true)
1374 public static String join(@NotNull final String[] strings, @NotNull final String separator) {
1375 return join(strings, 0, strings.length, separator);
1379 @Contract(pure = true)
1380 public static String join(@NotNull final String[] strings, int startIndex, int endIndex, @NotNull final String separator) {
1381 final StringBuilder result = new StringBuilder();
1382 for (int i = startIndex; i < endIndex; i++) {
1383 if (i > startIndex) result.append(separator);
1384 result.append(strings[i]);
1386 return result.toString();
1390 @Contract(pure = true)
1391 public static String[] zip(@NotNull String[] strings1, @NotNull String[] strings2, String separator) {
1392 if (strings1.length != strings2.length) throw new IllegalArgumentException();
1394 String[] result = ArrayUtil.newStringArray(strings1.length);
1395 for (int i = 0; i < result.length; i++) {
1396 result[i] = strings1[i] + separator + strings2[i];
1403 @Contract(pure = true)
1404 public static String[] surround(@NotNull String[] strings1, String prefix, String suffix) {
1405 String[] result = ArrayUtil.newStringArray(strings1.length);
1406 for (int i = 0; i < result.length; i++) {
1407 result[i] = prefix + strings1[i] + suffix;
1414 @Contract(pure = true)
1415 public static <T> String join(@NotNull T[] items, @NotNull Function<T, String> f, @NotNull @NonNls String separator) {
1416 return join(Arrays.asList(items), f, separator);
1420 @Contract(pure = true)
1421 public static <T> String join(@NotNull Collection<? extends T> items,
1422 @NotNull Function<? super T, String> f,
1423 @NotNull @NonNls String separator) {
1424 if (items.isEmpty()) return "";
1425 return join((Iterable<? extends T>)items, f, separator);
1428 @Contract(pure = true)
1429 public static String join(@NotNull Iterable<?> items, @NotNull @NonNls String separator) {
1430 StringBuilder result = new StringBuilder();
1431 for (Object item : items) {
1432 result.append(item).append(separator);
1434 if (result.length() > 0) {
1435 result.setLength(result.length() - separator.length());
1437 return result.toString();
1441 @Contract(pure = true)
1442 public static <T> String join(@NotNull Iterable<? extends T> items,
1443 @NotNull Function<? super T, String> f,
1444 @NotNull @NonNls String separator) {
1445 final StringBuilder result = new StringBuilder();
1446 for (T item : items) {
1447 String string = f.fun(item);
1448 if (string != null && !string.isEmpty()) {
1449 if (result.length() != 0) result.append(separator);
1450 result.append(string);
1453 return result.toString();
1457 @Contract(pure = true)
1458 public static String join(@NotNull Collection<String> strings, @NotNull String separator) {
1459 if (strings.size() <= 1) {
1460 return notNullize(ContainerUtil.getFirstItem(strings));
1462 StringBuilder result = new StringBuilder();
1463 join(strings, separator, result);
1464 return result.toString();
1467 public static void join(@NotNull Collection<String> strings, @NotNull String separator, @NotNull StringBuilder result) {
1468 boolean isFirst = true;
1469 for (String string : strings) {
1470 if (string != null) {
1475 result.append(separator);
1477 result.append(string);
1483 @Contract(pure = true)
1484 public static String join(@NotNull final int[] strings, @NotNull final String separator) {
1485 final StringBuilder result = new StringBuilder();
1486 for (int i = 0; i < strings.length; i++) {
1487 if (i > 0) result.append(separator);
1488 result.append(strings[i]);
1490 return result.toString();
1494 @Contract(pure = true)
1495 public static String join(@Nullable final String... strings) {
1496 if (strings == null || strings.length == 0) return "";
1498 final StringBuilder builder = new StringBuilder();
1499 for (final String string : strings) {
1500 builder.append(string);
1502 return builder.toString();
1506 * Consider using {@link StringUtil#unquoteString(String)} instead.
1507 * Note: this method has an odd behavior:
1508 * Quotes are removed even if leading and trailing quotes are different or
1509 * if there is only one quote (leading or trailing).
1512 @Contract(pure = true)
1513 public static String stripQuotesAroundValue(@NotNull String text) {
1514 final int len = text.length();
1516 final int from = isQuoteAt(text, 0) ? 1 : 0;
1517 final int to = len > 1 && isQuoteAt(text, len - 1) ? len - 1 : len;
1518 if (from > 0 || to < len) {
1519 return text.substring(from, to);
1526 * Formats the specified file size as a string.
1528 * @param fileSize the size to format.
1529 * @return the size formatted as a string.
1533 @Contract(pure = true)
1534 public static String formatFileSize(long fileSize) {
1535 return formatValue(fileSize, null,
1536 new String[]{"B", "K", "M", "G", "T", "P", "E"},
1537 new long[]{1000, 1000, 1000, 1000, 1000, 1000});
1541 @Contract(pure = true)
1542 public static String formatDuration(long duration) {
1543 return formatValue(duration, " ",
1544 new String[]{"ms", "s", "m", "h", "d", "w", "mo", "yr", "c", "ml", "ep"},
1545 new long[]{1000, 60, 60, 24, 7, 4, 12, 100, 10, 10000});
1549 private static String formatValue(long value, String partSeparator, String[] units, long[] multipliers) {
1550 StringBuilder sb = new StringBuilder();
1554 for (; i < units.length; i++) {
1555 long multiplier = i < multipliers.length ? multipliers[i] : -1;
1556 if (multiplier == -1 || count < multiplier) break;
1557 remainder = count % multiplier;
1558 count /= multiplier;
1559 if (partSeparator != null && (remainder != 0 || sb.length() > 0)) {
1560 sb.insert(0, units[i]).insert(0, remainder).insert(0, partSeparator);
1563 if (partSeparator != null || remainder == 0) {
1564 sb.insert(0, units[i]).insert(0, count);
1566 else if (remainder > 0) {
1567 sb.append(String.format(Locale.US, "%.2f", count + (double)remainder / multipliers[i - 1])).append(units[i]);
1569 return sb.toString();
1573 * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1574 * Returns <code>null</code> if failed to match appropriate heuristic.
1576 * @param name english word in plural form
1577 * @return name in singular form or <code>null</code> if failed to find one.
1579 @SuppressWarnings({"HardCodedStringLiteral"})
1581 @Contract(pure = true)
1582 public static String unpluralize(@NotNull final String name) {
1583 if (name.endsWith("sses") || name.endsWith("shes") || name.endsWith("ches") || name.endsWith("xes")) { //?
1584 return name.substring(0, name.length() - 2);
1587 if (name.endsWith("ses")) {
1588 return name.substring(0, name.length() - 1);
1591 if (name.endsWith("ies")) {
1592 if (name.endsWith("cookies") || name.endsWith("Cookies")) {
1593 return name.substring(0, name.length() - "ookies".length()) + "ookie";
1596 return name.substring(0, name.length() - 3) + "y";
1599 if (name.endsWith("leaves") || name.endsWith("Leaves")) {
1600 return name.substring(0, name.length() - "eaves".length()) + "eaf";
1603 String result = stripEnding(name, "s");
1604 if (result != null) {
1608 if (name.endsWith("children")) {
1609 return name.substring(0, name.length() - "children".length()) + "child";
1612 if (name.endsWith("Children") && name.length() > "Children".length()) {
1613 return name.substring(0, name.length() - "Children".length()) + "Child";
1621 @Contract(pure = true)
1622 private static String stripEnding(@NotNull String name, @NotNull String ending) {
1623 if (name.endsWith(ending)) {
1624 if (name.equals(ending)) return name; // do not return empty string
1625 return name.substring(0, name.length() - 1);
1630 @Contract(pure = true)
1631 public static boolean containsAlphaCharacters(@NotNull String value) {
1632 for (int i = 0; i < value.length(); i++) {
1633 if (Character.isLetter(value.charAt(i))) return true;
1638 @Contract(pure = true)
1639 public static boolean containsAnyChar(@NotNull final String value, @NotNull final String chars) {
1640 if (chars.length() > value.length()) {
1641 return containsAnyChar(value, chars, 0, value.length());
1644 return containsAnyChar(chars, value, 0, chars.length());
1648 @Contract(pure = true)
1649 public static boolean containsAnyChar(@NotNull final String value,
1650 @NotNull final String chars,
1651 final int start, final int end) {
1652 for (int i = start; i < end; i++) {
1653 if (chars.indexOf(value.charAt(i)) >= 0) {
1661 @Contract(pure = true)
1662 public static boolean containsChar(@NotNull final String value, final char ch) {
1663 return value.indexOf(ch) >= 0;
1667 * @deprecated use #capitalize(String)
1669 @Contract(value = "null -> null; !null -> !null", pure = true)
1670 public static String firstLetterToUpperCase(@Nullable final String displayString) {
1671 if (displayString == null || displayString.isEmpty()) return displayString;
1672 char firstChar = displayString.charAt(0);
1673 char uppedFirstChar = toUpperCase(firstChar);
1675 if (uppedFirstChar == firstChar) return displayString;
1677 char[] buffer = displayString.toCharArray();
1678 buffer[0] = uppedFirstChar;
1679 return StringFactory.createShared(buffer);
1683 * Strip out all characters not accepted by given filter
1685 * @param s e.g. "/n my string "
1686 * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1687 * @return stripped string e.g. "mystring"
1690 @Contract(pure = true)
1691 public static String strip(@NotNull final String s, @NotNull final CharFilter filter) {
1692 final StringBuilder result = new StringBuilder(s.length());
1693 for (int i = 0; i < s.length(); i++) {
1694 char ch = s.charAt(i);
1695 if (filter.accept(ch)) {
1699 return result.toString();
1703 @Contract(pure = true)
1704 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern) {
1705 return findMatches(s, pattern, 1);
1709 @Contract(pure = true)
1710 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) {
1711 List<String> result = new SmartList<String>();
1712 Matcher m = pattern.matcher(s);
1714 String group = m.group(groupIndex);
1715 if (group != null) {
1723 * Find position of the first character accepted by given filter.
1725 * @param s the string to search
1726 * @param filter search filter
1727 * @return position of the first character accepted or -1 if not found
1729 @Contract(pure = true)
1730 public static int findFirst(@NotNull final CharSequence s, @NotNull CharFilter filter) {
1731 for (int i = 0; i < s.length(); i++) {
1732 char ch = s.charAt(i);
1733 if (filter.accept(ch)) {
1741 @Contract(pure = true)
1742 public static String replaceSubstring(@NotNull String string, @NotNull TextRange range, @NotNull String replacement) {
1743 return range.replace(string, replacement);
1746 @Contract(pure = true)
1747 public static boolean startsWithWhitespace(@NotNull String text) {
1748 return !text.isEmpty() && Character.isWhitespace(text.charAt(0));
1751 @Contract(pure = true)
1752 public static boolean isChar(CharSequence seq, int index, char c) {
1753 return index >= 0 && index < seq.length() && seq.charAt(index) == c;
1756 @Contract(pure = true)
1757 public static boolean startsWith(@NotNull CharSequence text, @NotNull CharSequence prefix) {
1758 int l1 = text.length();
1759 int l2 = prefix.length();
1760 if (l1 < l2) return false;
1762 for (int i = 0; i < l2; i++) {
1763 if (text.charAt(i) != prefix.charAt(i)) return false;
1769 @Contract(pure = true)
1770 public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
1771 int l1 = text.length() - startIndex;
1772 int l2 = prefix.length();
1773 if (l1 < l2) return false;
1775 for (int i = 0; i < l2; i++) {
1776 if (text.charAt(i + startIndex) != prefix.charAt(i)) return false;
1782 @Contract(pure = true)
1783 public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
1784 int l1 = text.length();
1785 int l2 = suffix.length();
1786 if (l1 < l2) return false;
1788 for (int i = l1 - 1; i >= l1 - l2; i--) {
1789 if (text.charAt(i) != suffix.charAt(i + l2 - l1)) return false;
1796 @Contract(pure = true)
1797 public static String commonPrefix(@NotNull String s1, @NotNull String s2) {
1798 return s1.substring(0, commonPrefixLength(s1, s2));
1801 @Contract(pure = true)
1802 public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1804 int minLength = Math.min(s1.length(), s2.length());
1805 for (i = 0; i < minLength; i++) {
1806 if (s1.charAt(i) != s2.charAt(i)) {
1814 @Contract(pure = true)
1815 public static String commonSuffix(@NotNull String s1, @NotNull String s2) {
1816 return s1.substring(s1.length() - commonSuffixLength(s1, s2));
1819 @Contract(pure = true)
1820 public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1821 int s1Length = s1.length();
1822 int s2Length = s2.length();
1823 if (s1Length == 0 || s2Length == 0) return 0;
1825 for (i = 0; i < s1Length && i < s2Length; i++) {
1826 if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
1834 * Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> interval.
1836 * @param s target char sequence to check
1837 * @param start start offset to use within the given char sequence (inclusive)
1838 * @param end end offset to use within the given char sequence (exclusive)
1839 * @param c target symbol to check
1840 * @return <code>true</code> if given symbol is contained at the target range of the given char sequence;
1841 * <code>false</code> otherwise
1843 @Contract(pure = true)
1844 public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
1845 return indexOf(s, c, start, end) >= 0;
1848 @Contract(pure = true)
1849 public static boolean containsWhitespaces(@Nullable CharSequence s) {
1850 if (s == null) return false;
1852 for (int i = 0; i < s.length(); i++) {
1853 if (Character.isWhitespace(s.charAt(i))) return true;
1858 @Contract(pure = true)
1859 public static int indexOf(@NotNull CharSequence s, char c) {
1860 return indexOf(s, c, 0, s.length());
1863 @Contract(pure = true)
1864 public static int indexOf(@NotNull CharSequence s, char c, int start) {
1865 return indexOf(s, c, start, s.length());
1868 @Contract(pure = true)
1869 public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
1870 for (int i = start; i < end; i++) {
1871 if (s.charAt(i) == c) return i;
1876 @Contract(pure = true)
1877 public static boolean contains(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1878 return indexOf(sequence, infix) >= 0;
1881 @Contract(pure = true)
1882 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1883 return indexOf(sequence, infix, 0);
1886 @Contract(pure = true)
1887 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start) {
1888 for (int i = start; i <= sequence.length() - infix.length(); i++) {
1889 if (startsWith(sequence, i, infix)) {
1896 @Contract(pure = true)
1897 public static int indexOf(@NotNull CharSequence s, char c, int start, int end, boolean caseSensitive) {
1898 for (int i = start; i < end; i++) {
1899 if (charsMatch(s.charAt(i), c, !caseSensitive)) return i;
1904 @Contract(pure = true)
1905 public static int indexOf(@NotNull char[] s, char c, int start, int end, boolean caseSensitive) {
1906 for (int i = start; i < end; i++) {
1907 if (charsMatch(s[i], c, !caseSensitive)) return i;
1912 @Contract(pure = true)
1913 public static int indexOfSubstringEnd(@NotNull String text, @NotNull String subString) {
1914 int i = text.indexOf(subString);
1915 if (i == -1) return -1;
1916 return i + subString.length();
1919 @Contract(pure = true)
1920 public static int indexOfAny(@NotNull final String s, @NotNull final String chars) {
1921 return indexOfAny(s, chars, 0, s.length());
1924 @Contract(pure = true)
1925 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars) {
1926 return indexOfAny(s, chars, 0, s.length());
1929 @Contract(pure = true)
1930 public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) {
1931 return indexOfAny((CharSequence)s, chars, start, end);
1934 @Contract(pure = true)
1935 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars, final int start, final int end) {
1936 for (int i = start; i < end; i++) {
1937 if (containsChar(chars, s.charAt(i))) return i;
1943 @Contract(pure = true)
1944 public static String substringBefore(@NotNull String text, @NotNull String subString) {
1945 int i = text.indexOf(subString);
1946 if (i == -1) return null;
1947 return text.substring(0, i);
1951 @Contract(pure = true)
1952 public static String substringAfter(@NotNull String text, @NotNull String subString) {
1953 int i = text.indexOf(subString);
1954 if (i == -1) return null;
1955 return text.substring(i + subString.length());
1959 * Allows to retrieve index of last occurrence of the given symbols at <code>[start; end)</code> sub-sequence of the given text.
1961 * @param s target text
1962 * @param c target symbol which last occurrence we want to check
1963 * @param start start offset of the target text (inclusive)
1964 * @param end end offset of the target text (exclusive)
1965 * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
1966 * <code>-1</code> otherwise
1968 @Contract(pure = true)
1969 public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
1970 return StringUtilRt.lastIndexOf(s, c, start, end);
1974 @Contract(pure = true)
1975 public static String first(@NotNull String text, final int maxLength, final boolean appendEllipsis) {
1976 return text.length() > maxLength ? text.substring(0, maxLength) + (appendEllipsis ? "..." : "") : text;
1980 @Contract(pure = true)
1981 public static CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
1982 return text.length() > length ? text.subSequence(0, length) + (appendEllipsis ? "..." : "") : text;
1986 @Contract(pure = true)
1987 public static CharSequence last(@NotNull CharSequence text, final int length, boolean prependEllipsis) {
1988 return text.length() > length ? (prependEllipsis ? "..." : "") + text.subSequence(text.length() - length, text.length()) : text;
1992 @Contract(pure = true)
1993 public static String escapeChar(@NotNull final String str, final char character) {
1994 return escapeChars(str, character);
1998 @Contract(pure = true)
1999 public static String escapeChars(@NotNull final String str, final char... character) {
2000 final StringBuilder buf = new StringBuilder(str);
2001 for (char c : character) {
2004 return buf.toString();
2007 private static void escapeChar(@NotNull final StringBuilder buf, final char character) {
2009 while ((idx = indexOf(buf, character, idx)) >= 0) {
2010 buf.insert(idx, "\\");
2016 @Contract(pure = true)
2017 public static String escapeQuotes(@NotNull final String str) {
2018 return escapeChar(str, '"');
2021 public static void escapeQuotes(@NotNull final StringBuilder buf) {
2022 escapeChar(buf, '"');
2026 @Contract(pure = true)
2027 public static String escapeSlashes(@NotNull final String str) {
2028 return escapeChar(str, '/');
2032 @Contract(pure = true)
2033 public static String escapeBackSlashes(@NotNull final String str) {
2034 return escapeChar(str, '\\');
2037 public static void escapeSlashes(@NotNull final StringBuilder buf) {
2038 escapeChar(buf, '/');
2042 @Contract(pure = true)
2043 public static String unescapeSlashes(@NotNull final String str) {
2044 final StringBuilder buf = new StringBuilder(str.length());
2045 unescapeChar(buf, str, '/');
2046 return buf.toString();
2050 @Contract(pure = true)
2051 public static String unescapeBackSlashes(@NotNull final String str) {
2052 final StringBuilder buf = new StringBuilder(str.length());
2053 unescapeChar(buf, str, '\\');
2054 return buf.toString();
2058 @Contract(pure = true)
2059 public static String unescapeChar(@NotNull final String str, char unescapeChar) {
2060 final StringBuilder buf = new StringBuilder(str.length());
2061 unescapeChar(buf, str, unescapeChar);
2062 return buf.toString();
2065 private static void unescapeChar(@NotNull StringBuilder buf, @NotNull String str, char unescapeChar) {
2066 final int length = str.length();
2067 final int last = length - 1;
2068 for (int i = 0; i < length; i++) {
2069 char ch = str.charAt(i);
2070 if (ch == '\\' && i != last) {
2073 if (ch != unescapeChar) buf.append('\\');
2080 public static void quote(@NotNull final StringBuilder builder) {
2081 quote(builder, '\"');
2084 public static void quote(@NotNull final StringBuilder builder, final char quotingChar) {
2085 builder.insert(0, quotingChar);
2086 builder.append(quotingChar);
2090 @Contract(pure = true)
2091 public static String wrapWithDoubleQuote(@NotNull String str) {
2092 return '\"' + str + "\"";
2095 @NonNls private static final String[] REPLACES_REFS = {"<", ">", "&", "'", """};
2096 @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""};
2098 @Contract(value = "null -> null; !null -> !null",pure = true)
2099 public static String unescapeXml(@Nullable final String text) {
2100 if (text == null) return null;
2101 return replace(text, REPLACES_REFS, REPLACES_DISP);
2104 @Contract(value = "null -> null; !null -> !null",pure = true)
2105 public static String escapeXml(@Nullable final String text) {
2106 if (text == null) return null;
2107 return replace(text, REPLACES_DISP, REPLACES_REFS);
2111 @Contract(pure = true)
2112 public static String htmlEmphasize(@NotNull String text) {
2113 return "<b><code>" + escapeXml(text) + "</code></b>";
2118 @Contract(pure = true)
2119 public static String escapeToRegexp(@NotNull String text) {
2120 final StringBuilder result = new StringBuilder(text.length());
2121 return escapeToRegexp(text, result).toString();
2125 public static StringBuilder escapeToRegexp(@NotNull CharSequence text, @NotNull StringBuilder builder) {
2126 for (int i = 0; i < text.length(); i++) {
2127 final char c = text.charAt(i);
2128 if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_') {
2131 else if (c == '\n') {
2132 builder.append("\\n");
2135 builder.append('\\').append(c);
2142 @Contract(pure = true)
2143 public static boolean isNotEscapedBackslash(@NotNull char[] chars, int startOffset, int backslashOffset) {
2144 if (chars[backslashOffset] != '\\') {
2147 boolean escaped = false;
2148 for (int i = startOffset; i < backslashOffset; i++) {
2149 if (chars[i] == '\\') {
2159 @Contract(pure = true)
2160 public static boolean isNotEscapedBackslash(@NotNull CharSequence text, int startOffset, int backslashOffset) {
2161 if (text.charAt(backslashOffset) != '\\') {
2164 boolean escaped = false;
2165 for (int i = startOffset; i < backslashOffset; i++) {
2166 if (text.charAt(i) == '\\') {
2177 @Contract(pure = true)
2178 public static String replace(@NotNull String text, @NotNull String[] from, @NotNull String[] to) {
2179 final StringBuilder result = new StringBuilder(text.length());
2181 for (int i = 0; i < text.length(); i++) {
2182 for (int j = 0; j < from.length; j += 1) {
2183 String toReplace = from[j];
2184 String replaceWith = to[j];
2186 final int len = toReplace.length();
2187 if (text.regionMatches(i, toReplace, 0, len)) {
2188 result.append(replaceWith);
2193 result.append(text.charAt(i));
2195 return result.toString();
2199 @Contract(pure = true)
2200 public static String[] filterEmptyStrings(@NotNull String[] strings) {
2202 for (String string : strings) {
2203 if (string == null || string.isEmpty()) emptyCount++;
2205 if (emptyCount == 0) return strings;
2207 String[] result = ArrayUtil.newStringArray(strings.length - emptyCount);
2209 for (String string : strings) {
2210 if (string == null || string.isEmpty()) continue;
2211 result[count++] = string;
2217 @Contract(pure = true)
2218 public static int countNewLines(@NotNull CharSequence text) {
2219 return countChars(text, '\n');
2222 @Contract(pure = true)
2223 public static int countChars(@NotNull CharSequence text, char c) {
2224 return countChars(text, c, 0, false);
2227 @Contract(pure = true)
2228 public static int countChars(@NotNull CharSequence text, char c, int offset, boolean continuous) {
2230 for (int i = offset; i < text.length(); ++i) {
2231 if (text.charAt(i) == c) {
2234 else if (continuous) {
2242 @Contract(pure = true)
2243 public static String capitalsOnly(@NotNull String s) {
2244 StringBuilder b = new StringBuilder();
2245 for (int i = 0; i < s.length(); i++) {
2246 if (Character.isUpperCase(s.charAt(i))) {
2247 b.append(s.charAt(i));
2251 return b.toString();
2255 * @param args Strings to join.
2256 * @return {@code null} if any of given Strings is {@code null}.
2259 @Contract(pure = true)
2260 public static String joinOrNull(@NotNull String... args) {
2261 StringBuilder r = new StringBuilder();
2262 for (String arg : args) {
2263 if (arg == null) return null;
2266 return r.toString();
2270 @Contract(pure = true)
2271 public static String getPropertyName(@NonNls @NotNull String methodName) {
2272 if (methodName.startsWith("get")) {
2273 return Introspector.decapitalize(methodName.substring(3));
2275 else if (methodName.startsWith("is")) {
2276 return Introspector.decapitalize(methodName.substring(2));
2278 else if (methodName.startsWith("set")) {
2279 return Introspector.decapitalize(methodName.substring(3));
2286 @Contract(pure = true)
2287 public static boolean isJavaIdentifierStart(char c) {
2288 return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierStart(c);
2291 @Contract(pure = true)
2292 public static boolean isJavaIdentifierPart(char c) {
2293 return c >= '0' && c <= '9' || isJavaIdentifierStart(c);
2296 @Contract(pure = true)
2297 public static boolean isJavaIdentifier(@NotNull String text) {
2298 int len = text.length();
2299 if (len == 0) return false;
2301 if (!isJavaIdentifierStart(text.charAt(0))) return false;
2303 for (int i = 1; i < len; i++) {
2304 if (!isJavaIdentifierPart(text.charAt(i))) return false;
2311 * Escape property name or key in property file. Unicode characters are escaped as well.
2313 * @param input an input to escape
2314 * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case.
2315 * @return an escaped string
2318 @Contract(pure = true)
2319 public static String escapeProperty(@NotNull String input, final boolean isKey) {
2320 final StringBuilder escaped = new StringBuilder(input.length());
2321 for (int i = 0; i < input.length(); i++) {
2322 final char ch = input.charAt(i);
2325 if (isKey && i == 0) {
2326 // only the leading space has to be escaped
2327 escaped.append('\\');
2329 escaped.append(' ');
2332 escaped.append("\\t");
2335 escaped.append("\\r");
2338 escaped.append("\\n");
2341 escaped.append("\\f");
2348 escaped.append('\\');
2352 if (20 < ch && ch < 0x7F) {
2356 escaped.append("\\u");
2357 escaped.append(Character.forDigit((ch >> 12) & 0xF, 16));
2358 escaped.append(Character.forDigit((ch >> 8) & 0xF, 16));
2359 escaped.append(Character.forDigit((ch >> 4) & 0xF, 16));
2360 escaped.append(Character.forDigit((ch) & 0xF, 16));
2365 return escaped.toString();
2368 @Contract(pure = true)
2369 public static String getQualifiedName(@Nullable String packageName, String className) {
2370 if (packageName == null || packageName.isEmpty()) {
2373 return packageName + '.' + className;
2376 @Contract(pure = true)
2377 public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
2378 // todo duplicates com.intellij.util.text.VersionComparatorUtil.compare
2379 // todo please refactor next time you make changes here
2380 if (v1 == null && v2 == null) {
2390 String[] part1 = v1.split("[\\.\\_\\-]");
2391 String[] part2 = v2.split("[\\.\\_\\-]");
2394 for (; idx < part1.length && idx < part2.length; idx++) {
2395 String p1 = part1[idx];
2396 String p2 = part2[idx];
2399 if (p1.matches("\\d+") && p2.matches("\\d+")) {
2400 cmp = new Integer(p1).compareTo(new Integer(p2));
2403 cmp = part1[idx].compareTo(part2[idx]);
2405 if (cmp != 0) return cmp;
2408 if (part1.length == part2.length) {
2412 boolean left = part1.length > idx;
2413 String[] parts = left ? part1 : part2;
2415 for (; idx < parts.length; idx++) {
2416 String p = parts[idx];
2418 if (p.matches("\\d+")) {
2419 cmp = new Integer(p).compareTo(0);
2424 if (cmp != 0) return left ? cmp : -cmp;
2430 @Contract(pure = true)
2431 public static int getOccurrenceCount(@NotNull String text, final char c) {
2434 while (i < text.length()) {
2435 i = text.indexOf(c, i);
2447 @Contract(pure = true)
2448 public static int getOccurrenceCount(@NotNull String text, @NotNull String s) {
2451 while (i < text.length()) {
2452 i = text.indexOf(s, i);
2465 @Contract(pure = true)
2466 public static String fixVariableNameDerivedFromPropertyName(@NotNull String name) {
2467 if (isEmptyOrSpaces(name)) return name;
2468 char c = name.charAt(0);
2470 return "an" + Character.toUpperCase(c) + name.substring(1);
2472 return "a" + Character.toUpperCase(c) + name.substring(1);
2476 @Contract(pure = true)
2477 public static String sanitizeJavaIdentifier(@NotNull String name) {
2478 final StringBuilder result = new StringBuilder(name.length());
2480 for (int i = 0; i < name.length(); i++) {
2481 final char ch = name.charAt(i);
2482 if (Character.isJavaIdentifierPart(ch)) {
2483 if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
2490 return result.toString();
2493 public static void assertValidSeparators(@NotNull CharSequence s) {
2494 char[] chars = CharArrayUtil.fromSequenceWithoutCopying(s);
2495 int slashRIndex = -1;
2497 if (chars != null) {
2498 for (int i = 0, len = s.length(); i < len; ++i) {
2499 if (chars[i] == '\r') {
2506 for (int i = 0, len = s.length(); i < len; i++) {
2507 if (s.charAt(i) == '\r') {
2514 if (slashRIndex != -1) {
2516 String.valueOf(last(s.subSequence(0, slashRIndex), 10, true)) + first(s.subSequence(slashRIndex, s.length()), 10, true);
2517 context = escapeStringCharacters(context);
2518 LOG.error("Wrong line separators: '" + context + "' at offset " + slashRIndex);
2523 @Contract(pure = true)
2524 public static String tail(@NotNull String s, final int idx) {
2525 return idx >= s.length() ? "" : s.substring(idx, s.length());
2529 * Splits string by lines.
2531 * @param string String to split
2532 * @return array of strings
2535 @Contract(pure = true)
2536 public static String[] splitByLines(@NotNull String string) {
2537 return splitByLines(string, true);
2541 * Splits string by lines. If several line separators are in a row corresponding empty lines
2542 * are also added to result if {@code excludeEmptyStrings} is {@code false}.
2544 * @param string String to split
2545 * @return array of strings
2548 @Contract(pure = true)
2549 public static String[] splitByLines(@NotNull String string, boolean excludeEmptyStrings) {
2550 return (excludeEmptyStrings ? EOL_SPLIT_PATTERN : EOL_SPLIT_PATTERN_WITH_EMPTY).split(string);
2554 @Contract(pure = true)
2555 public static String[] splitByLinesDontTrim(@NotNull String string) {
2556 return EOL_SPLIT_DONT_TRIM_PATTERN.split(string);
2560 * Splits string by lines, keeping all line separators at the line ends and in the empty lines.
2561 * <br> E.g. splitting text
2570 * will return the following array: foo\r\n, \n, bar\n, \r\n, baz\r, \r
2574 @Contract(pure = true)
2575 public static String[] splitByLinesKeepSeparators(@NotNull String string) {
2576 return EOL_SPLIT_KEEP_SEPARATORS.split(string);
2580 @Contract(pure = true)
2581 public static List<Pair<String, Integer>> getWordsWithOffset(@NotNull String s) {
2582 List<Pair<String, Integer>> res = ContainerUtil.newArrayList();
2584 StringBuilder name = new StringBuilder();
2586 for (int i = 0; i < s.length(); i++) {
2587 if (Character.isWhitespace(s.charAt(i))) {
2588 if (name.length() > 0) {
2589 res.add(Pair.create(name.toString(), startInd));
2595 if (startInd == -1) {
2598 name.append(s.charAt(i));
2605 * Implementation of "Sorting for Humans: Natural Sort Order":
2606 * http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
2608 @Contract(pure = true)
2609 public static int naturalCompare(@Nullable String string1, @Nullable String string2) {
2610 return naturalCompare(string1, string2, false);
2613 @Contract(pure = true)
2614 private static int naturalCompare(@Nullable String string1, @Nullable String string2, boolean caseSensitive) {
2615 //noinspection StringEquality
2616 if (string1 == string2) {
2619 if (string1 == null) {
2622 if (string2 == null) {
2626 final int string1Length = string1.length();
2627 final int string2Length = string2.length();
2630 for (; i < string1Length && j < string2Length; i++, j++) {
2631 char ch1 = string1.charAt(i);
2632 char ch2 = string2.charAt(j);
2633 if ((isDecimalDigit(ch1) || ch1 == ' ') && (isDecimalDigit(ch2) || ch2 == ' ')) {
2635 while (ch1 == ' ' || ch1 == '0') { // skip leading spaces and zeros
2637 if (startNum1 >= string1Length) break;
2638 ch1 = string1.charAt(startNum1);
2641 while (ch2 == ' ' || ch2 == '0') { // skip leading spaces and zeros
2643 if (startNum2 >= string2Length) break;
2644 ch2 = string2.charAt(startNum2);
2648 // find end index of number
2649 while (i < string1Length && isDecimalDigit(string1.charAt(i))) i++;
2650 while (j < string2Length && isDecimalDigit(string2.charAt(j))) j++;
2651 final int lengthDiff = (i - startNum1) - (j - startNum2);
2652 if (lengthDiff != 0) {
2653 // numbers with more digits are always greater than shorter numbers
2656 for (; startNum1 < i; startNum1++, startNum2++) {
2657 // compare numbers with equal digit count
2658 final int diff = string1.charAt(startNum1) - string2.charAt(startNum2);
2667 if (caseSensitive) {
2671 // similar logic to charsMatch() below
2673 final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
2675 final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
2684 // After the loop the end of one of the strings might not have been reached, if the other
2685 // string ends with a number and the strings are equal until the end of that number. When
2686 // there are more characters in the string, then it is greater.
2687 if (i < string1Length) {
2690 else if (j < string2Length) {
2693 if (!caseSensitive && string1Length == string2Length) {
2694 // do case sensitive compare if case insensitive strings are equal
2695 return naturalCompare(string1, string2, true);
2697 return string1Length - string2Length;
2700 @Contract(pure = true)
2701 public static boolean isDecimalDigit(char c) {
2702 return c >= '0' && c <= '9';
2705 @Contract(pure = true)
2706 public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) {
2707 //noinspection StringEquality
2708 if (s1 == s2) return 0;
2709 if (s1 == null) return -1;
2710 if (s2 == null) return 1;
2711 return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
2714 @Contract(pure = true)
2715 public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
2716 final int compare = compare(s1, s2, ignoreCase);
2717 return compare != 0 ? compare : compare(t1, t2, ignoreCase);
2720 @Contract(pure = true)
2721 public static int hashCode(@NotNull CharSequence s) {
2722 return stringHashCode(s);
2725 @Contract(pure = true)
2726 public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2727 if (s1 == null ^ s2 == null) {
2735 if (s1.length() != s2.length()) {
2738 for (int i = 0; i < s1.length(); i++) {
2739 if (s1.charAt(i) != s2.charAt(i)) {
2746 @Contract(pure = true)
2747 public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2748 if (s1 == null ^ s2 == null) {
2756 if (s1.length() != s2.length()) {
2759 for (int i = 0; i < s1.length(); i++) {
2760 if (!charsMatch(s1.charAt(i), s2.charAt(i), true)) {
2767 @Contract(pure = true)
2768 public static boolean equalsIgnoreWhitespaces(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2769 if (s1 == null ^ s2 == null) {
2777 int len1 = s1.length();
2778 int len2 = s2.length();
2782 while (index1 < len1 && index2 < len2) {
2783 if (s1.charAt(index1) == s2.charAt(index2)) {
2789 boolean skipped = false;
2790 while (index1 != len1 && isWhiteSpace(s1.charAt(index1))) {
2794 while (index2 != len2 && isWhiteSpace(s2.charAt(index2))) {
2799 if (!skipped) return false;
2802 for (; index1 != len1; index1++) {
2803 if (!isWhiteSpace(s1.charAt(index1))) return false;
2805 for (; index2 != len2; index2++) {
2806 if (!isWhiteSpace(s2.charAt(index2))) return false;
2812 @Contract(pure = true)
2813 public static boolean equalsTrimWhitespaces(@NotNull CharSequence s1, @NotNull CharSequence s2) {
2815 int end1 = s1.length();
2817 int end2 = s2.length();
2819 while (start1 < end1) {
2820 char c = s1.charAt(start1);
2821 if (!isWhiteSpace(c)) break;
2825 while (start1 < end1) {
2826 char c = s1.charAt(end1 - 1);
2827 if (!isWhiteSpace(c)) break;
2831 while (start2 < end2) {
2832 char c = s2.charAt(start2);
2833 if (!isWhiteSpace(c)) break;
2837 while (start2 < end2) {
2838 char c = s2.charAt(end2 - 1);
2839 if (!isWhiteSpace(c)) break;
2843 CharSequence ts1 = new CharSequenceSubSequence(s1, start1, end1);
2844 CharSequence ts2 = new CharSequenceSubSequence(s2, start2, end2);
2846 return equals(ts1, ts2);
2849 @Contract(pure = true)
2850 public static int compare(char c1, char c2, boolean ignoreCase) {
2851 // duplicating String.equalsIgnoreCase logic
2853 if (d == 0 || !ignoreCase) {
2856 // If characters don't match but case may be ignored,
2857 // try converting both characters to uppercase.
2858 // If the results match, then the comparison scan should
2860 char u1 = StringUtilRt.toUpperCase(c1);
2861 char u2 = StringUtilRt.toUpperCase(c2);
2864 // Unfortunately, conversion to uppercase does not work properly
2865 // for the Georgian alphabet, which has strange rules about case
2866 // conversion. So we need to make one last check before
2868 d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
2873 @Contract(pure = true)
2874 public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
2875 return compare(c1, c2, ignoreCase) == 0;
2879 @Contract(pure = true)
2880 public static String formatLinks(@NotNull String message) {
2881 Pattern linkPattern = Pattern.compile("http://[a-zA-Z0-9\\./\\-\\+]+");
2882 StringBuffer result = new StringBuffer();
2883 Matcher m = linkPattern.matcher(message);
2885 m.appendReplacement(result, "<a href=\"" + m.group() + "\">" + m.group() + "</a>");
2887 m.appendTail(result);
2888 return result.toString();
2891 @Contract(pure = true)
2892 public static boolean isHexDigit(char c) {
2893 return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
2896 @Contract(pure = true)
2897 public static boolean isOctalDigit(char c) {
2898 return '0' <= c && c <= '7';
2902 @Contract(pure = true)
2903 public static String shortenTextWithEllipsis(@NotNull final String text, final int maxLength, final int suffixLength) {
2904 return shortenTextWithEllipsis(text, maxLength, suffixLength, false);
2908 @Contract(pure = true)
2909 public static String trimMiddle(@NotNull String text, int maxLength) {
2910 return shortenTextWithEllipsis(text, maxLength, maxLength >> 1, true);
2914 @Contract(pure = true)
2915 public static String shortenTextWithEllipsis(@NotNull final String text,
2916 final int maxLength,
2917 final int suffixLength,
2918 @NotNull String symbol) {
2919 final int textLength = text.length();
2920 if (textLength > maxLength) {
2921 final int prefixLength = maxLength - suffixLength - symbol.length();
2922 assert prefixLength > 0;
2923 return text.substring(0, prefixLength) + symbol + text.substring(textLength - suffixLength);
2931 @Contract(pure = true)
2932 public static String shortenTextWithEllipsis(@NotNull final String text,
2933 final int maxLength,
2934 final int suffixLength,
2935 boolean useEllipsisSymbol) {
2936 String symbol = useEllipsisSymbol ? "\u2026" : "...";
2937 return shortenTextWithEllipsis(text, maxLength, suffixLength, symbol);
2941 @Contract(pure = true)
2942 public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength, boolean useEllipsisSymbol) {
2943 return shortenTextWithEllipsis(path, maxLength, (int)(maxLength * 0.7), useEllipsisSymbol);
2947 @Contract(pure = true)
2948 public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength) {
2949 return shortenPathWithEllipsis(path, maxLength, false);
2952 @Contract(pure = true)
2953 public static boolean charsEqual(char a, char b, boolean ignoreCase) {
2954 return ignoreCase ? charsEqualIgnoreCase(a, b) : a == b;
2957 @Contract(pure = true)
2958 public static boolean charsEqualIgnoreCase(char a, char b) {
2959 return StringUtilRt.charsEqualIgnoreCase(a, b);
2962 @Contract(pure = true)
2963 public static char toUpperCase(char a) {
2964 return StringUtilRt.toUpperCase(a);
2968 @Contract(pure = true)
2969 public static String toUpperCase(@NotNull String a) {
2970 return StringUtilRt.toUpperCase(a);
2973 @Contract(pure = true)
2974 public static char toLowerCase(final char a) {
2975 return StringUtilRt.toLowerCase(a);
2979 public static LineSeparator detectSeparators(@NotNull CharSequence text) {
2980 int index = indexOfAny(text, "\n\r");
2981 if (index == -1) return null;
2982 if (startsWith(text, index, "\r\n")) return LineSeparator.CRLF;
2983 if (text.charAt(index) == '\r') return LineSeparator.CR;
2984 if (text.charAt(index) == '\n') return LineSeparator.LF;
2985 throw new IllegalStateException();
2989 @Contract(pure = true)
2990 public static String convertLineSeparators(@NotNull String text) {
2991 return StringUtilRt.convertLineSeparators(text);
2995 @Contract(pure = true)
2996 public static String convertLineSeparators(@NotNull String text, boolean keepCarriageReturn) {
2997 return StringUtilRt.convertLineSeparators(text, keepCarriageReturn);