2 * Copyright 2000-2016 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.CharArrayUtil;
25 import com.intellij.util.text.CharSequenceSubSequence;
26 import com.intellij.util.text.StringFactory;
27 import org.jetbrains.annotations.Contract;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
32 import javax.swing.text.MutableAttributeSet;
33 import javax.swing.text.html.HTML;
34 import javax.swing.text.html.HTMLEditorKit;
35 import javax.swing.text.html.parser.ParserDelegator;
36 import java.beans.Introspector;
37 import java.io.IOException;
38 import java.io.Reader;
39 import java.io.StringReader;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
44 import static java.lang.Math.max;
45 import static java.lang.Math.min;
47 //TeamCity inherits StringUtil: do not add private constructors!!!
48 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "MethodOverridesStaticMethodOfSuperclass"})
49 public class StringUtil extends StringUtilRt {
50 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.text.StringUtil");
52 @NonNls private static final String VOWELS = "aeiouy";
53 @NonNls private static final Pattern EOL_SPLIT_KEEP_SEPARATORS = Pattern.compile("(?<=(\r\n|\n))|(?<=\r)(?=[^\n])");
54 @NonNls private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r|\n|\r\n)+ *");
55 @NonNls private static final Pattern EOL_SPLIT_PATTERN_WITH_EMPTY = Pattern.compile(" *(\r|\n|\r\n) *");
56 @NonNls private static final Pattern EOL_SPLIT_DONT_TRIM_PATTERN = Pattern.compile("(\r|\n|\r\n)+");
58 private static class MyHtml2Text extends HTMLEditorKit.ParserCallback {
59 @NotNull private final StringBuilder myBuffer = new StringBuilder();
61 public void parse(Reader in) throws IOException {
62 myBuffer.setLength(0);
63 new ParserDelegator().parse(in, this, Boolean.TRUE);
67 public void handleText(char[] text, int pos) {
68 myBuffer.append(text);
72 public void handleStartTag(HTML.Tag tag, MutableAttributeSet set, int i) {
77 public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet set, int i) {
81 private void handleTag(HTML.Tag tag) {
82 if (tag.breaksFlow() && myBuffer.length() > 0) {
83 myBuffer.append(SystemProperties.getLineSeparator());
87 public String getText() {
88 return myBuffer.toString();
92 private static final MyHtml2Text html2TextParser = new MyHtml2Text();
94 public static final NotNullFunction<String, String> QUOTER = new NotNullFunction<String, String>() {
97 public String fun(String s) {
98 return "\"" + s + "\"";
102 public static final NotNullFunction<String, String> SINGLE_QUOTER = new NotNullFunction<String, String>() {
105 public String fun(String s) {
106 return "'" + s + "'";
111 @Contract(pure = true)
112 public static List<String> getWordsInStringLongestFirst(@NotNull String find) {
113 List<String> words = getWordsIn(find);
114 // hope long words are rare
115 Collections.sort(words, new Comparator<String>() {
117 public int compare(@NotNull final String o1, @NotNull final String o2) {
118 return o2.length() - o1.length();
125 @Contract(pure = true)
126 public static String escapePattern(@NotNull final String text) {
127 return replace(replace(text, "'", "''"), "{", "'{'");
131 @Contract(pure = true)
132 public static <T> Function<T, String> createToStringFunction(@NotNull Class<T> cls) {
133 return new Function<T, String>() {
135 public String fun(@NotNull T o) {
142 public static Function<String, String> TRIMMER = new Function<String, String>() {
145 public String fun(@Nullable String s) {
151 @Contract(pure = true)
152 public static String replace(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
153 return replace(text, oldS, newS, false);
157 @Contract(pure = true)
158 public static String replaceIgnoreCase(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
159 return replace(text, oldS, newS, true);
162 public static void replaceChar(@NotNull char[] buffer, char oldChar, char newChar, int start, int end) {
163 for (int i = start; i < end; i++) {
172 @Contract(pure = true)
173 public static String replaceChar(@NotNull String buffer, char oldChar, char newChar) {
174 StringBuilder newBuffer = null;
175 for (int i = 0; i < buffer.length(); i++) {
176 char c = buffer.charAt(i);
178 if (newBuffer == null) {
179 newBuffer = new StringBuilder(buffer.length());
180 newBuffer.append(buffer, 0, i);
183 newBuffer.append(newChar);
185 else if (newBuffer != null) {
189 return newBuffer == null ? buffer : newBuffer.toString();
192 @Contract(pure = true)
193 public static String replace(@NonNls @NotNull final String text, @NonNls @NotNull final String oldS, @NonNls @NotNull final String newS, final boolean ignoreCase) {
194 if (text.length() < oldS.length()) return text;
196 StringBuilder newText = null;
199 while (i < text.length()) {
200 final int index = ignoreCase? indexOfIgnoreCase(text, oldS, i) : text.indexOf(oldS, i);
206 newText.append(text, i, text.length());
210 if (newText == null) {
211 if (text.length() == oldS.length()) {
214 newText = new StringBuilder(text.length() - i);
217 newText.append(text, i, index);
218 newText.append(newS);
219 i = index + oldS.length();
222 return newText != null ? newText.toString() : "";
226 * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
228 @Contract(pure = true)
229 public static int indexOfIgnoreCase(@NotNull String where, @NotNull String what, int fromIndex) {
230 int targetCount = what.length();
231 int sourceCount = where.length();
233 if (fromIndex >= sourceCount) {
234 return targetCount == 0 ? sourceCount : -1;
241 if (targetCount == 0) {
245 char first = what.charAt(0);
246 int max = sourceCount - targetCount;
248 for (int i = fromIndex; i <= max; i++) {
249 /* Look for first character. */
250 if (!charsEqualIgnoreCase(where.charAt(i), first)) {
251 while (++i <= max && !charsEqualIgnoreCase(where.charAt(i), first)) ;
254 /* Found first character, now look at the rest of v2 */
257 int end = j + targetCount - 1;
258 for (int k = 1; j < end && charsEqualIgnoreCase(where.charAt(j), what.charAt(k)); j++, k++) ;
261 /* Found whole string. */
270 @Contract(pure = true)
271 public static int indexOfIgnoreCase(@NotNull String where, char what, int fromIndex) {
272 int sourceCount = where.length();
273 for (int i = max(fromIndex, 0); i < sourceCount; i++) {
274 if (charsEqualIgnoreCase(where.charAt(i), what)) {
282 @Contract(pure = true)
283 public static boolean containsIgnoreCase(@NotNull String where, @NotNull String what) {
284 return indexOfIgnoreCase(where, what, 0) >= 0;
287 @Contract(pure = true)
288 public static boolean endsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String suffix) {
289 return StringUtilRt.endsWithIgnoreCase(str, suffix);
292 @Contract(pure = true)
293 public static boolean startsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String prefix) {
294 return StringUtilRt.startsWithIgnoreCase(str, prefix);
297 @Contract(pure = true)
299 public static String stripHtml(@NotNull String html, boolean convertBreaks) {
301 html = html.replaceAll("<br/?>", "\n\n");
304 return html.replaceAll("<(.|\n)*?>", "");
307 @Contract(value = "null -> null; !null -> !null", pure = true)
308 public static String toLowerCase(@Nullable final String str) {
309 //noinspection ConstantConditions
310 return str == null ? null : str.toLowerCase();
314 @Contract(pure = true)
315 public static String getPackageName(@NotNull String fqName) {
316 return getPackageName(fqName, '.');
320 * Given a fqName returns the package name for the type or the containing type.
323 * <li><code>java.lang.String</code> -> <code>java.lang</code></li>
324 * <li><code>java.util.Map.Entry</code> -> <code>java.util.Map</code></li>
327 * @param fqName a fully qualified type name. Not supposed to contain any type arguments
328 * @param separator the separator to use. Typically '.'
329 * @return the package name of the type or the declarator of the type. The empty string if the given fqName is unqualified
332 @Contract(pure = true)
333 public static String getPackageName(@NotNull String fqName, char separator) {
334 int lastPointIdx = fqName.lastIndexOf(separator);
335 if (lastPointIdx >= 0) {
336 return fqName.substring(0, lastPointIdx);
341 @Contract(pure = true)
342 public static int getLineBreakCount(@NotNull CharSequence text) {
344 for (int i = 0; i < text.length(); i++) {
345 char c = text.charAt(i);
349 else if (c == '\r') {
350 if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
351 //noinspection AssignmentToForLoopParameter
363 @Contract(pure = true)
364 public static boolean containsLineBreak(@NotNull CharSequence text) {
365 for (int i = 0; i < text.length(); i++) {
366 char c = text.charAt(i);
367 if (isLineBreak(c)) return true;
372 @Contract(pure = true)
373 public static boolean isLineBreak(char c) {
374 return c == '\n' || c == '\r';
378 @Contract(pure = true)
379 public static String escapeLineBreak(@NotNull String text) {
380 StringBuilder buffer = new StringBuilder(text.length());
381 for (int i = 0; i < text.length(); i++) {
382 char c = text.charAt(i);
385 buffer.append("\\n");
388 buffer.append("\\r");
394 return buffer.toString();
397 @Contract(pure = true)
398 public static boolean endsWithLineBreak(@NotNull CharSequence text) {
399 int len = text.length();
400 return len > 0 && isLineBreak(text.charAt(len - 1));
403 @Contract(pure = true)
404 public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
407 while (line != curLine) {
408 if (offset == text.length()) return -1;
409 char c = text.charAt(offset);
413 else if (c == '\r') {
415 if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
424 @Contract(pure = true)
425 public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
428 while (curOffset < offset) {
429 if (curOffset == text.length()) return -1;
430 char c = text.charAt(curOffset);
434 else if (c == '\r') {
436 if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
446 * Classic dynamic programming algorithm for string differences.
448 @Contract(pure = true)
449 public static int difference(@NotNull String s1, @NotNull String s2) {
450 int[][] a = new int[s1.length()][s2.length()];
452 for (int i = 0; i < s1.length(); i++) {
456 for (int j = 0; j < s2.length(); j++) {
460 for (int i = 1; i < s1.length(); i++) {
461 for (int j = 1; j < s2.length(); j++) {
463 a[i][j] = min(min(a[i - 1][j - 1] + (s1.charAt(i) == s2.charAt(j) ? 0 : 1), a[i - 1][j] + 1), a[i][j - 1] + 1);
467 return a[s1.length() - 1][s2.length() - 1];
471 @Contract(pure = true)
472 public static String wordsToBeginFromUpperCase(@NotNull String s) {
473 return fixCapitalization(s, ourPrepositions, true);
477 @Contract(pure = true)
478 public static String wordsToBeginFromLowerCase(@NotNull String s) {
479 return fixCapitalization(s, ourPrepositions, false);
483 @Contract(pure = true)
484 public static String toTitleCase(@NotNull String s) {
485 return fixCapitalization(s, ArrayUtil.EMPTY_STRING_ARRAY, true);
489 private static String fixCapitalization(@NotNull String s, @NotNull String[] prepositions, boolean title) {
490 StringBuilder buffer = null;
491 for (int i = 0; i < s.length(); i++) {
492 char prevChar = i == 0 ? ' ' : s.charAt(i - 1);
493 char currChar = s.charAt(i);
494 if (!Character.isLetterOrDigit(prevChar) && prevChar != '\'') {
495 if (Character.isLetterOrDigit(currChar)) {
496 if (title || Character.isUpperCase(currChar)) {
498 for (; j < s.length(); j++) {
499 if (!Character.isLetterOrDigit(s.charAt(j))) {
503 if (!title && j > i + 1 && !Character.isLowerCase(s.charAt(i + 1))) {
504 // filter out abbreviations like I18n, SQL and CSS
507 if (!isPreposition(s, i, j - 1, prepositions)) {
508 if (buffer == null) {
509 buffer = new StringBuilder(s);
511 buffer.setCharAt(i, title ? toUpperCase(currChar) : toLowerCase(currChar));
517 return buffer == null ? s : buffer.toString();
520 @NonNls private static final String[] ourPrepositions = {
521 "a", "an", "and", "as", "at", "but", "by", "down", "for", "from", "if", "in", "into", "not", "of", "on", "onto", "or", "out", "over",
522 "per", "nor", "the", "to", "up", "upon", "via", "with"
525 @Contract(pure = true)
526 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar) {
527 return isPreposition(s, firstChar, lastChar, ourPrepositions);
530 @Contract(pure = true)
531 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar, @NotNull String[] prepositions) {
532 for (String preposition : prepositions) {
533 boolean found = false;
534 if (lastChar - firstChar + 1 == preposition.length()) {
536 for (int j = 0; j < preposition.length(); j++) {
537 if (toLowerCase(s.charAt(firstChar + j)) != preposition.charAt(j)) {
550 @Contract(pure = true)
551 public static NotNullFunction<String, String> escaper(final boolean escapeSlash, @Nullable final String additionalChars) {
552 return new NotNullFunction<String, String>() {
555 public String fun(@NotNull String dom) {
556 final StringBuilder builder = new StringBuilder(dom.length());
557 escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder);
558 return builder.toString();
564 public static void escapeStringCharacters(int length, @NotNull String str, @NotNull @NonNls StringBuilder buffer) {
565 escapeStringCharacters(length, str, "\"", buffer);
569 public static StringBuilder escapeStringCharacters(int length,
571 @Nullable String additionalChars,
572 @NotNull @NonNls StringBuilder buffer) {
573 return escapeStringCharacters(length, str, additionalChars, true, buffer);
577 public static StringBuilder escapeStringCharacters(int length,
579 @Nullable String additionalChars,
581 @NotNull @NonNls StringBuilder buffer) {
582 return escapeStringCharacters(length, str, additionalChars, escapeSlash, true, buffer);
586 public static StringBuilder escapeStringCharacters(int length,
588 @Nullable String additionalChars,
590 boolean escapeUnicode,
591 @NotNull @NonNls StringBuilder buffer) {
593 for (int idx = 0; idx < length; idx++) {
594 char ch = str.charAt(idx);
597 buffer.append("\\b");
601 buffer.append("\\t");
605 buffer.append("\\n");
609 buffer.append("\\f");
613 buffer.append("\\r");
617 if (escapeSlash && ch == '\\') {
618 buffer.append("\\\\");
620 else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
621 buffer.append("\\").append(ch);
623 else if (escapeUnicode && !isPrintableUnicode(ch)) {
624 CharSequence hexCode = StringUtilRt.toUpperCase(Integer.toHexString(ch));
625 buffer.append("\\u");
626 int paddingCount = 4 - hexCode.length();
627 while (paddingCount-- > 0) {
630 buffer.append(hexCode);
641 @Contract(pure = true)
642 public static boolean isPrintableUnicode(char c) {
643 int t = Character.getType(c);
644 return t != Character.UNASSIGNED && t != Character.LINE_SEPARATOR && t != Character.PARAGRAPH_SEPARATOR &&
645 t != Character.CONTROL && t != Character.FORMAT && t != Character.PRIVATE_USE && t != Character.SURROGATE;
649 @Contract(pure = true)
650 public static String escapeStringCharacters(@NotNull String s) {
651 StringBuilder buffer = new StringBuilder(s.length());
652 escapeStringCharacters(s.length(), s, "\"", buffer);
653 return buffer.toString();
657 @Contract(pure = true)
658 public static String escapeCharCharacters(@NotNull String s) {
659 StringBuilder buffer = new StringBuilder(s.length());
660 escapeStringCharacters(s.length(), s, "\'", buffer);
661 return buffer.toString();
665 @Contract(pure = true)
666 public static String unescapeStringCharacters(@NotNull String s) {
667 StringBuilder buffer = new StringBuilder(s.length());
668 unescapeStringCharacters(s.length(), s, buffer);
669 return buffer.toString();
672 private static boolean isQuoteAt(@NotNull String s, int ind) {
673 char ch = s.charAt(ind);
674 return ch == '\'' || ch == '\"';
677 @Contract(pure = true)
678 public static boolean isQuotedString(@NotNull String s) {
679 return s.length() > 1 && isQuoteAt(s, 0) && s.charAt(0) == s.charAt(s.length() - 1);
683 @Contract(pure = true)
684 public static String unquoteString(@NotNull String s) {
685 if (isQuotedString(s)) {
686 return s.substring(1, s.length() - 1);
692 @Contract(pure = true)
693 public static String unquoteString(@NotNull String s, char quotationChar) {
694 if (s.length() > 1 && quotationChar == s.charAt(0) && quotationChar == s.charAt(s.length() - 1)) {
695 return s.substring(1, s.length() - 1);
701 * This is just an optimized version of Matcher.quoteReplacement
704 @Contract(pure = true)
705 public static String quoteReplacement(@NotNull String s) {
706 boolean needReplacements = false;
708 for (int i = 0; i < s.length(); i++) {
709 char c = s.charAt(i);
710 if (c == '\\' || c == '$') {
711 needReplacements = true;
716 if (!needReplacements) return s;
718 StringBuilder sb = new StringBuilder(s.length() * 6 / 5);
719 for (int i = 0; i < s.length(); i++) {
720 char c = s.charAt(i);
733 return sb.toString();
736 private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
737 boolean escaped = false;
738 for (int idx = 0; idx < length; idx++) {
739 char ch = s.charAt(idx);
749 int octalEscapeMaxLength = 2;
784 if (idx + 4 < length) {
786 int code = Integer.parseInt(s.substring(idx + 1, idx + 5), 16);
787 //noinspection AssignmentToForLoopParameter
789 buffer.append((char)code);
791 catch (NumberFormatException e) {
792 buffer.append("\\u");
796 buffer.append("\\u");
804 octalEscapeMaxLength = 3;
809 int escapeEnd = idx + 1;
810 while (escapeEnd < length && escapeEnd < idx + octalEscapeMaxLength && isOctalDigit(s.charAt(escapeEnd))) escapeEnd++;
812 buffer.append((char)Integer.parseInt(s.substring(idx, escapeEnd), 8));
814 catch (NumberFormatException e) {
815 throw new RuntimeException("Couldn't parse " + s.substring(idx, escapeEnd), e); // shouldn't happen
817 //noinspection AssignmentToForLoopParameter
829 if (escaped) buffer.append('\\');
832 @SuppressWarnings("HardCodedStringLiteral")
834 @Contract(pure = true)
835 public static String pluralize(@NotNull String suggestion) {
836 if (suggestion.endsWith("Child") || suggestion.endsWith("child")) {
837 return suggestion + "ren";
840 if (suggestion.equals("this")) {
843 if (suggestion.equals("This")) {
846 if (suggestion.equals("fix") || suggestion.equals("Fix")) {
847 return suggestion + "es";
850 if (endsWithIgnoreCase(suggestion, "es")) {
854 int len = suggestion.length();
855 if (endsWithIgnoreCase(suggestion, "ex") || endsWithIgnoreCase(suggestion, "ix")) {
856 return suggestion.substring(0, len - 2) + "ices";
858 if (endsWithIgnoreCase(suggestion, "um")) {
859 return suggestion.substring(0, len - 2) + "a";
861 if (endsWithIgnoreCase(suggestion, "an")) {
862 return suggestion.substring(0, len - 2) + "en";
865 if (endsWithIgnoreCase(suggestion, "s") || endsWithIgnoreCase(suggestion, "x") ||
866 endsWithIgnoreCase(suggestion, "ch") || endsWithIgnoreCase(suggestion, "sh")) {
867 return suggestion + "es";
870 if (endsWithIgnoreCase(suggestion, "y") && len > 1 && !isVowel(toLowerCase(suggestion.charAt(len - 2)))) {
871 return suggestion.substring(0, len - 1) + "ies";
874 return suggestion + "s";
878 @Contract(pure = true)
879 public static String capitalizeWords(@NotNull String text,
881 return capitalizeWords(text, " \t\n\r\f", allWords, false);
885 @Contract(pure = true)
886 public static String capitalizeWords(@NotNull String text,
887 @NotNull String tokenizerDelim,
889 boolean leaveOriginalDelims) {
890 final StringTokenizer tokenizer = new StringTokenizer(text, tokenizerDelim, leaveOriginalDelims);
891 final StringBuilder out = new StringBuilder(text.length());
892 boolean toCapitalize = true;
893 while (tokenizer.hasMoreTokens()) {
894 final String word = tokenizer.nextToken();
895 if (!leaveOriginalDelims && out.length() > 0) {
898 out.append(toCapitalize ? capitalize(word) : word);
900 toCapitalize = false;
903 return out.toString();
906 @Contract(pure = true)
907 public static String decapitalize(String s) {
908 return Introspector.decapitalize(s);
911 @Contract(pure = true)
912 public static boolean isVowel(char c) {
913 return VOWELS.indexOf(c) >= 0;
917 @Contract(pure = true)
918 public static String capitalize(@NotNull String s) {
919 if (s.isEmpty()) return s;
920 if (s.length() == 1) return StringUtilRt.toUpperCase(s).toString();
923 if (Character.isUpperCase(s.charAt(0))) return s;
924 return toUpperCase(s.charAt(0)) + s.substring(1);
927 @Contract(value = "null -> false", pure = true)
928 public static boolean isCapitalized(@Nullable String s) {
929 return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0));
933 @Contract(pure = true)
934 public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
935 if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
938 return capitalize(s);
941 @Contract(pure = true)
942 public static int stringHashCode(@NotNull CharSequence chars) {
943 if (chars instanceof String || chars instanceof CharSequenceWithStringHash) {
944 // we know for sure these classes have conformant (and maybe faster) hashCode()
945 return chars.hashCode();
948 return stringHashCode(chars, 0, chars.length());
951 @Contract(pure = true)
952 public static int stringHashCode(@NotNull CharSequence chars, int from, int to) {
954 for (int off = from; off < to; off++) {
955 h = 31 * h + chars.charAt(off);
960 @Contract(pure = true)
961 public static int stringHashCode(char[] chars, int from, int to) {
963 for (int off = from; off < to; off++) {
964 h = 31 * h + chars[off];
969 @Contract(pure = true)
970 public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) {
972 for (int off = from; off < to; off++) {
973 h = 31 * h + toLowerCase(chars[off]);
978 @Contract(pure = true)
979 public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
981 for (int off = from; off < to; off++) {
982 h = 31 * h + toLowerCase(chars.charAt(off));
987 @Contract(pure = true)
988 public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
989 return stringHashCodeInsensitive(chars, 0, chars.length());
992 @Contract(pure = true)
993 public static int stringHashCodeIgnoreWhitespaces(char[] chars, int from, int to) {
995 for (int off = from; off < to; off++) {
997 if (!isWhiteSpace(c)) {
1004 @Contract(pure = true)
1005 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars, int from, int to) {
1007 for (int off = from; off < to; off++) {
1008 char c = chars.charAt(off);
1009 if (!isWhiteSpace(c)) {
1016 @Contract(pure = true)
1017 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars) {
1018 return stringHashCodeIgnoreWhitespaces(chars, 0, chars.length());
1022 * Equivalent to string.startsWith(prefixes[0] + prefixes[1] + ...) but avoids creating an object for concatenation.
1024 @Contract(pure = true)
1025 public static boolean startsWithConcatenation(@NotNull String string, @NotNull String... prefixes) {
1027 for (String prefix : prefixes) {
1028 int prefixLen = prefix.length();
1029 if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
1032 offset += prefixLen;
1037 @Contract(value = "null -> null; !null -> !null", pure = true)
1038 public static String trim(@Nullable String s) {
1039 return s == null ? null : s.trim();
1043 @Contract(pure = true)
1044 public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix) {
1045 return trimEnd(s, suffix, false);
1049 @Contract(pure = true)
1050 public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix, boolean ignoreCase) {
1051 boolean endsWith = ignoreCase ? endsWithIgnoreCase(s, suffix) : s.endsWith(suffix);
1053 return s.substring(0, s.length() - suffix.length());
1059 @Contract(pure = true)
1060 public static String trimEnd(@NotNull String s, char suffix) {
1061 if (endsWithChar(s, suffix)) {
1062 return s.substring(0, s.length() - 1);
1068 @Contract(pure = true)
1069 public static String trimLog(@NotNull final String text, final int limit) {
1070 if (limit > 5 && text.length() > limit) {
1071 return text.substring(0, limit - 5) + " ...\n";
1077 @Contract(pure = true)
1078 public static String trimLeading(@NotNull String string) {
1079 return trimLeading((CharSequence)string).toString();
1082 @Contract(pure = true)
1083 public static CharSequence trimLeading(@NotNull CharSequence string) {
1085 while (index < string.length() && Character.isWhitespace(string.charAt(index))) index++;
1086 return string.subSequence(index, string.length());
1090 @Contract(pure = true)
1091 public static String trimLeading(@NotNull String string, char symbol) {
1093 while (index < string.length() && string.charAt(index) == symbol) index++;
1094 return string.substring(index);
1098 @Contract(pure = true)
1099 public static String trimTrailing(@NotNull String string) {
1100 return trimTrailing((CharSequence)string).toString();
1104 @Contract(pure = true)
1105 public static CharSequence trimTrailing(@NotNull CharSequence string) {
1106 int index = string.length() - 1;
1107 while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
1108 return string.subSequence(0, index + 1);
1112 @Contract(pure = true)
1113 public static String trimTrailing(@NotNull String string, char symbol) {
1114 int index = string.length() - 1;
1115 while (index >= 0 && string.charAt(index) == symbol) index--;
1116 return string.substring(0, index + 1);
1119 @Contract(pure = true)
1120 public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
1121 return s != null && s.length() != 0 && s.charAt(0) == prefix;
1124 @Contract(pure = true)
1125 public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
1126 return StringUtilRt.endsWithChar(s, suffix);
1130 @Contract(pure = true)
1131 public static String trimStart(@NotNull String s, @NonNls @NotNull String prefix) {
1132 if (s.startsWith(prefix)) {
1133 return s.substring(prefix.length());
1139 @Contract(pure = true)
1140 public static String trimExtension(@NotNull String name) {
1141 int index = name.lastIndexOf('.');
1142 return index < 0 ? name : name.substring(0, index);
1146 @Contract(pure = true)
1147 public static String trimExtensions(@NotNull String name) {
1148 int index = name.indexOf('.');
1149 return index < 0 ? name : name.substring(0, index);
1153 @Contract(pure = true)
1154 public static String pluralize(@NotNull String base, int n) {
1155 if (n == 1) return base;
1156 return pluralize(base);
1159 public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
1160 assert times >= 0 : times;
1162 for (int i = 0; i < times; i++) {
1163 buffer.append(symbol);
1166 catch (IOException e) {
1171 @Contract(pure = true)
1172 public static String defaultIfEmpty(@Nullable String value, String defaultValue) {
1173 return isEmpty(value) ? defaultValue : value;
1176 @Contract(value = "null -> false", pure = true)
1177 public static boolean isNotEmpty(@Nullable String s) {
1178 return s != null && !s.isEmpty();
1181 @Contract(value = "null -> true", pure=true)
1182 public static boolean isEmpty(@Nullable String s) {
1183 return s == null || s.isEmpty();
1186 @Contract(value = "null -> true",pure = true)
1187 public static boolean isEmpty(@Nullable CharSequence cs) {
1188 return cs == null || cs.length() == 0;
1191 @Contract(pure = true)
1192 public static int length(@Nullable CharSequence cs) {
1193 return cs == null ? 0 : cs.length();
1197 @Contract(pure = true)
1198 public static String notNullize(@Nullable final String s) {
1199 return notNullize(s, "");
1203 @Contract(pure = true)
1204 public static String notNullize(@Nullable final String s, @NotNull String defaultValue) {
1205 return s == null ? defaultValue : s;
1209 @Contract(pure = true)
1210 public static String nullize(@Nullable final String s) {
1211 return nullize(s, false);
1215 @Contract(pure = true)
1216 public static String nullize(@Nullable final String s, boolean nullizeSpaces) {
1217 if (nullizeSpaces) {
1218 if (isEmptyOrSpaces(s)) return null;
1221 if (isEmpty(s)) return null;
1226 @Contract(value = "null -> true",pure = true)
1227 // we need to keep this method to preserve backward compatibility
1228 public static boolean isEmptyOrSpaces(@Nullable String s) {
1229 return isEmptyOrSpaces((CharSequence)s);
1232 @Contract(value = "null -> true", pure = true)
1233 public static boolean isEmptyOrSpaces(@Nullable CharSequence s) {
1237 for (int i = 0; i < s.length(); i++) {
1238 if (s.charAt(i) > ' ') {
1246 * Allows to answer if given symbol is white space, tabulation or line feed.
1248 * @param c symbol to check
1249 * @return <code>true</code> if given symbol is white space, tabulation or line feed; <code>false</code> otherwise
1251 @Contract(pure = true)
1252 public static boolean isWhiteSpace(char c) {
1253 return c == '\n' || c == '\t' || c == ' ';
1257 @Contract(pure = true)
1258 public static String getThrowableText(@NotNull Throwable aThrowable) {
1259 return ExceptionUtil.getThrowableText(aThrowable);
1263 @Contract(pure = true)
1264 public static String getThrowableText(@NotNull Throwable aThrowable, @NonNls @NotNull final String stackFrameSkipPattern) {
1265 return ExceptionUtil.getThrowableText(aThrowable, stackFrameSkipPattern);
1269 @Contract(pure = true)
1270 public static String getMessage(@NotNull Throwable e) {
1271 return ExceptionUtil.getMessage(e);
1275 @Contract(pure = true)
1276 public static String repeatSymbol(final char aChar, final int count) {
1277 char[] buffer = new char[count];
1278 Arrays.fill(buffer, aChar);
1279 return StringFactory.createShared(buffer);
1283 @Contract(pure = true)
1284 public static String repeat(@NotNull String s, int count) {
1285 assert count >= 0 : count;
1286 StringBuilder sb = new StringBuilder(s.length() * count);
1287 for (int i = 0; i < count; i++) {
1290 return sb.toString();
1294 @Contract(pure = true)
1295 public static List<String> splitHonorQuotes(@NotNull String s, char separator) {
1296 final List<String> result = new ArrayList<String>();
1297 final StringBuilder builder = new StringBuilder(s.length());
1298 boolean inQuotes = false;
1299 for (int i = 0; i < s.length(); i++) {
1300 final char c = s.charAt(i);
1301 if (c == separator && !inQuotes) {
1302 if (builder.length() > 0) {
1303 result.add(builder.toString());
1304 builder.setLength(0);
1309 if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
1310 inQuotes = !inQuotes;
1315 if (builder.length() > 0) {
1316 result.add(builder.toString());
1323 @Contract(pure = true)
1324 public static List<String> split(@NotNull String s, @NotNull String separator) {
1325 return split(s, separator, true);
1328 @Contract(pure = true)
1329 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator) {
1330 return split(s, separator, true, true);
1334 @Contract(pure = true)
1335 public static List<String> split(@NotNull String s, @NotNull String separator,
1336 boolean excludeSeparator) {
1337 return split(s, separator, excludeSeparator, true);
1341 @Contract(pure = true)
1342 public static List<String> split(@NotNull String s, @NotNull String separator,
1343 boolean excludeSeparator, boolean excludeEmptyStrings) {
1344 return (List)split((CharSequence)s,separator,excludeSeparator,excludeEmptyStrings);
1347 @Contract(pure = true)
1348 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator,
1349 boolean excludeSeparator, boolean excludeEmptyStrings) {
1350 if (separator.length() == 0) {
1351 return Collections.singletonList(s);
1353 List<CharSequence> result = new ArrayList<CharSequence>();
1356 int index = indexOf(s,separator, pos);
1357 if (index == -1) break;
1358 final int nextPos = index + separator.length();
1359 CharSequence token = s.subSequence(pos, excludeSeparator ? index : nextPos);
1360 if (token.length() != 0 || !excludeEmptyStrings) {
1365 if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
1366 result.add(s.subSequence(pos, s.length()));
1372 @Contract(pure = true)
1373 public static Iterable<String> tokenize(@NotNull String s, @NotNull String separators) {
1374 final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators);
1375 return new Iterable<String>() {
1378 public Iterator<String> iterator() {
1379 return new Iterator<String>() {
1381 public boolean hasNext() {
1382 return tokenizer.hasMoreTokens();
1386 public String next() {
1387 return tokenizer.nextToken();
1391 public void remove() {
1392 throw new UnsupportedOperationException();
1400 @Contract(pure = true)
1401 public static Iterable<String> tokenize(@NotNull final StringTokenizer tokenizer) {
1402 return new Iterable<String>() {
1405 public Iterator<String> iterator() {
1406 return new Iterator<String>() {
1408 public boolean hasNext() {
1409 return tokenizer.hasMoreTokens();
1413 public String next() {
1414 return tokenizer.nextToken();
1418 public void remove() {
1419 throw new UnsupportedOperationException();
1427 * @return list containing all words in {@code text}, or {@link ContainerUtil#emptyList()} if there are none.
1428 * The <b>word</b> here means the maximum sub-string consisting entirely of characters which are <code>Character.isJavaIdentifierPart(c)</code>.
1431 @Contract(pure = true)
1432 public static List<String> getWordsIn(@NotNull String text) {
1433 List<String> result = null;
1435 for (int i = 0; i < text.length(); i++) {
1436 char c = text.charAt(i);
1437 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1438 if (isIdentifierPart && start == -1) {
1441 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1442 if (result == null) {
1443 result = new SmartList<String>();
1445 result.add(text.substring(start, i + 1));
1447 else if (!isIdentifierPart && start != -1) {
1448 if (result == null) {
1449 result = new SmartList<String>();
1451 result.add(text.substring(start, i));
1455 if (result == null) {
1456 return ContainerUtil.emptyList();
1462 @Contract(pure = true)
1463 public static List<TextRange> getWordIndicesIn(@NotNull String text) {
1464 List<TextRange> result = new SmartList<TextRange>();
1466 for (int i = 0; i < text.length(); i++) {
1467 char c = text.charAt(i);
1468 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1469 if (isIdentifierPart && start == -1) {
1472 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1473 result.add(new TextRange(start, i + 1));
1475 else if (!isIdentifierPart && start != -1) {
1476 result.add(new TextRange(start, i));
1484 @Contract(pure = true)
1485 public static String join(@NotNull final String[] strings, @NotNull final String separator) {
1486 return join(strings, 0, strings.length, separator);
1490 @Contract(pure = true)
1491 public static String join(@NotNull final String[] strings, int startIndex, int endIndex, @NotNull final String separator) {
1492 final StringBuilder result = new StringBuilder();
1493 for (int i = startIndex; i < endIndex; i++) {
1494 if (i > startIndex) result.append(separator);
1495 result.append(strings[i]);
1497 return result.toString();
1501 @Contract(pure = true)
1502 public static String[] zip(@NotNull String[] strings1, @NotNull String[] strings2, String separator) {
1503 if (strings1.length != strings2.length) throw new IllegalArgumentException();
1505 String[] result = ArrayUtil.newStringArray(strings1.length);
1506 for (int i = 0; i < result.length; i++) {
1507 result[i] = strings1[i] + separator + strings2[i];
1514 @Contract(pure = true)
1515 public static String[] surround(@NotNull String[] strings1, String prefix, String suffix) {
1516 String[] result = ArrayUtil.newStringArray(strings1.length);
1517 for (int i = 0; i < result.length; i++) {
1518 result[i] = prefix + strings1[i] + suffix;
1525 @Contract(pure = true)
1526 public static <T> String join(@NotNull T[] items, @NotNull Function<T, String> f, @NotNull @NonNls String separator) {
1527 return join(Arrays.asList(items), f, separator);
1531 @Contract(pure = true)
1532 public static <T> String join(@NotNull Collection<? extends T> items,
1533 @NotNull Function<? super T, String> f,
1534 @NotNull @NonNls String separator) {
1535 if (items.isEmpty()) return "";
1536 return join((Iterable<? extends T>)items, f, separator);
1539 @Contract(pure = true)
1540 public static String join(@NotNull Iterable<?> items, @NotNull @NonNls String separator) {
1541 StringBuilder result = new StringBuilder();
1542 for (Object item : items) {
1543 result.append(item).append(separator);
1545 if (result.length() > 0) {
1546 result.setLength(result.length() - separator.length());
1548 return result.toString();
1552 @Contract(pure = true)
1553 public static <T> String join(@NotNull Iterable<? extends T> items,
1554 @NotNull Function<? super T, String> f,
1555 @NotNull @NonNls String separator) {
1556 final StringBuilder result = new StringBuilder();
1557 for (T item : items) {
1558 String string = f.fun(item);
1559 if (string != null && !string.isEmpty()) {
1560 if (result.length() != 0) result.append(separator);
1561 result.append(string);
1564 return result.toString();
1568 @Contract(pure = true)
1569 public static String join(@NotNull Collection<String> strings, @NotNull String separator) {
1570 if (strings.size() <= 1) {
1571 return notNullize(ContainerUtil.getFirstItem(strings));
1573 StringBuilder result = new StringBuilder();
1574 join(strings, separator, result);
1575 return result.toString();
1578 public static void join(@NotNull Collection<String> strings, @NotNull String separator, @NotNull StringBuilder result) {
1579 boolean isFirst = true;
1580 for (String string : strings) {
1581 if (string != null) {
1586 result.append(separator);
1588 result.append(string);
1594 @Contract(pure = true)
1595 public static String join(@NotNull final int[] strings, @NotNull final String separator) {
1596 final StringBuilder result = new StringBuilder();
1597 for (int i = 0; i < strings.length; i++) {
1598 if (i > 0) result.append(separator);
1599 result.append(strings[i]);
1601 return result.toString();
1605 @Contract(pure = true)
1606 public static String join(@Nullable final String... strings) {
1607 if (strings == null || strings.length == 0) return "";
1609 final StringBuilder builder = new StringBuilder();
1610 for (final String string : strings) {
1611 builder.append(string);
1613 return builder.toString();
1617 * Consider using {@link StringUtil#unquoteString(String)} instead.
1618 * Note: this method has an odd behavior:
1619 * Quotes are removed even if leading and trailing quotes are different or
1620 * if there is only one quote (leading or trailing).
1623 @Contract(pure = true)
1624 public static String stripQuotesAroundValue(@NotNull String text) {
1625 final int len = text.length();
1627 final int from = isQuoteAt(text, 0) ? 1 : 0;
1628 final int to = len > 1 && isQuoteAt(text, len - 1) ? len - 1 : len;
1629 if (from > 0 || to < len) {
1630 return text.substring(from, to);
1637 * Formats the specified file size as a string.
1639 * @param fileSize the size to format.
1640 * @return the size formatted as a string.
1644 @Contract(pure = true)
1645 public static String formatFileSize(long fileSize) {
1646 return formatValue(fileSize, null,
1647 new String[]{"B", "K", "M", "G", "T", "P", "E"},
1648 new long[]{1000, 1000, 1000, 1000, 1000, 1000});
1652 @Contract(pure = true)
1653 public static String formatDuration(long duration) {
1654 return formatValue(duration, " ",
1655 new String[]{"ms", "s", "m", "h", "d", "w", "mo", "yr", "c", "ml", "ep"},
1656 new long[]{1000, 60, 60, 24, 7, 4, 12, 100, 10, 10000});
1660 private static String formatValue(long value, String partSeparator, String[] units, long[] multipliers) {
1661 StringBuilder sb = new StringBuilder();
1665 for (; i < units.length; i++) {
1666 long multiplier = i < multipliers.length ? multipliers[i] : -1;
1667 if (multiplier == -1 || count < multiplier) break;
1668 remainder = count % multiplier;
1669 count /= multiplier;
1670 if (partSeparator != null && (remainder != 0 || sb.length() > 0)) {
1671 sb.insert(0, units[i]).insert(0, remainder).insert(0, partSeparator);
1674 if (partSeparator != null || remainder == 0) {
1675 sb.insert(0, units[i]).insert(0, count);
1677 else if (remainder > 0) {
1678 sb.append(String.format(Locale.US, "%.2f", count + (double)remainder / multipliers[i - 1])).append(units[i]);
1680 return sb.toString();
1684 * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1685 * Returns <code>null</code> if failed to match appropriate heuristic.
1687 * @param name english word in plural form
1688 * @return name in singular form or <code>null</code> if failed to find one.
1690 @SuppressWarnings("HardCodedStringLiteral")
1692 @Contract(pure = true)
1693 public static String unpluralize(@NotNull final String name) {
1694 if (name.endsWith("sses") || name.endsWith("shes") || name.endsWith("ches") || name.endsWith("xes")) { //?
1695 return name.substring(0, name.length() - 2);
1698 if (name.endsWith("ses")) {
1699 return name.substring(0, name.length() - 1);
1702 if (name.endsWith("ies")) {
1703 if (name.endsWith("cookies") || name.endsWith("Cookies")) {
1704 return name.substring(0, name.length() - "ookies".length()) + "ookie";
1707 return name.substring(0, name.length() - 3) + "y";
1710 if (name.endsWith("leaves") || name.endsWith("Leaves")) {
1711 return name.substring(0, name.length() - "eaves".length()) + "eaf";
1714 String result = stripEnding(name, "s");
1715 if (result != null) {
1719 if (name.endsWith("children")) {
1720 return name.substring(0, name.length() - "children".length()) + "child";
1723 if (name.endsWith("Children") && name.length() > "Children".length()) {
1724 return name.substring(0, name.length() - "Children".length()) + "Child";
1732 @Contract(pure = true)
1733 private static String stripEnding(@NotNull String name, @NotNull String ending) {
1734 if (name.endsWith(ending)) {
1735 if (name.equals(ending)) return name; // do not return empty string
1736 return name.substring(0, name.length() - 1);
1741 @Contract(pure = true)
1742 public static boolean containsAlphaCharacters(@NotNull String value) {
1743 for (int i = 0; i < value.length(); i++) {
1744 if (Character.isLetter(value.charAt(i))) return true;
1749 @Contract(pure = true)
1750 public static boolean containsAnyChar(@NotNull final String value, @NotNull final String chars) {
1751 if (chars.length() > value.length()) {
1752 return containsAnyChar(value, chars, 0, value.length());
1755 return containsAnyChar(chars, value, 0, chars.length());
1759 @Contract(pure = true)
1760 public static boolean containsAnyChar(@NotNull final String value,
1761 @NotNull final String chars,
1762 final int start, final int end) {
1763 for (int i = start; i < end; i++) {
1764 if (chars.indexOf(value.charAt(i)) >= 0) {
1772 @Contract(pure = true)
1773 public static boolean containsChar(@NotNull final String value, final char ch) {
1774 return value.indexOf(ch) >= 0;
1778 * @deprecated use #capitalize(String)
1780 @Contract(value = "null -> null; !null -> !null", pure = true)
1781 public static String firstLetterToUpperCase(@Nullable final String displayString) {
1782 if (displayString == null || displayString.isEmpty()) return displayString;
1783 char firstChar = displayString.charAt(0);
1784 char uppedFirstChar = toUpperCase(firstChar);
1786 if (uppedFirstChar == firstChar) return displayString;
1788 char[] buffer = displayString.toCharArray();
1789 buffer[0] = uppedFirstChar;
1790 return StringFactory.createShared(buffer);
1794 * Strip out all characters not accepted by given filter
1796 * @param s e.g. "/n my string "
1797 * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1798 * @return stripped string e.g. "mystring"
1801 @Contract(pure = true)
1802 public static String strip(@NotNull final String s, @NotNull final CharFilter filter) {
1803 final StringBuilder result = new StringBuilder(s.length());
1804 for (int i = 0; i < s.length(); i++) {
1805 char ch = s.charAt(i);
1806 if (filter.accept(ch)) {
1810 return result.toString();
1814 @Contract(pure = true)
1815 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern) {
1816 return findMatches(s, pattern, 1);
1820 @Contract(pure = true)
1821 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) {
1822 List<String> result = new SmartList<String>();
1823 Matcher m = pattern.matcher(s);
1825 String group = m.group(groupIndex);
1826 if (group != null) {
1834 * Find position of the first character accepted by given filter.
1836 * @param s the string to search
1837 * @param filter search filter
1838 * @return position of the first character accepted or -1 if not found
1840 @Contract(pure = true)
1841 public static int findFirst(@NotNull final CharSequence s, @NotNull CharFilter filter) {
1842 for (int i = 0; i < s.length(); i++) {
1843 char ch = s.charAt(i);
1844 if (filter.accept(ch)) {
1852 @Contract(pure = true)
1853 public static String replaceSubstring(@NotNull String string, @NotNull TextRange range, @NotNull String replacement) {
1854 return range.replace(string, replacement);
1857 @Contract(pure = true)
1858 public static boolean startsWithWhitespace(@NotNull String text) {
1859 return !text.isEmpty() && Character.isWhitespace(text.charAt(0));
1862 @Contract(pure = true)
1863 public static boolean isChar(CharSequence seq, int index, char c) {
1864 return index >= 0 && index < seq.length() && seq.charAt(index) == c;
1867 @Contract(pure = true)
1868 public static boolean startsWith(@NotNull CharSequence text, @NotNull CharSequence prefix) {
1869 int l1 = text.length();
1870 int l2 = prefix.length();
1871 if (l1 < l2) return false;
1873 for (int i = 0; i < l2; i++) {
1874 if (text.charAt(i) != prefix.charAt(i)) return false;
1880 @Contract(pure = true)
1881 public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
1882 int l1 = text.length() - startIndex;
1883 int l2 = prefix.length();
1884 if (l1 < l2) return false;
1886 for (int i = 0; i < l2; i++) {
1887 if (text.charAt(i + startIndex) != prefix.charAt(i)) return false;
1893 @Contract(pure = true)
1894 public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
1895 int l1 = text.length();
1896 int l2 = suffix.length();
1897 if (l1 < l2) return false;
1899 for (int i = l1 - 1; i >= l1 - l2; i--) {
1900 if (text.charAt(i) != suffix.charAt(i + l2 - l1)) return false;
1907 @Contract(pure = true)
1908 public static String commonPrefix(@NotNull String s1, @NotNull String s2) {
1909 return s1.substring(0, commonPrefixLength(s1, s2));
1912 @Contract(pure = true)
1913 public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1915 int minLength = min(s1.length(), s2.length());
1916 for (i = 0; i < minLength; i++) {
1917 if (s1.charAt(i) != s2.charAt(i)) {
1925 @Contract(pure = true)
1926 public static String commonSuffix(@NotNull String s1, @NotNull String s2) {
1927 return s1.substring(s1.length() - commonSuffixLength(s1, s2));
1930 @Contract(pure = true)
1931 public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1932 int s1Length = s1.length();
1933 int s2Length = s2.length();
1934 if (s1Length == 0 || s2Length == 0) return 0;
1936 for (i = 0; i < s1Length && i < s2Length; i++) {
1937 if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
1945 * Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> interval.
1947 * @param s target char sequence to check
1948 * @param start start offset to use within the given char sequence (inclusive)
1949 * @param end end offset to use within the given char sequence (exclusive)
1950 * @param c target symbol to check
1951 * @return <code>true</code> if given symbol is contained at the target range of the given char sequence;
1952 * <code>false</code> otherwise
1954 @Contract(pure = true)
1955 public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
1956 return indexOf(s, c, start, end) >= 0;
1959 @Contract(pure = true)
1960 public static boolean containsWhitespaces(@Nullable CharSequence s) {
1961 if (s == null) return false;
1963 for (int i = 0; i < s.length(); i++) {
1964 if (Character.isWhitespace(s.charAt(i))) return true;
1969 @Contract(pure = true)
1970 public static int indexOf(@NotNull CharSequence s, char c) {
1971 return indexOf(s, c, 0, s.length());
1974 @Contract(pure = true)
1975 public static int indexOf(@NotNull CharSequence s, char c, int start) {
1976 return indexOf(s, c, start, s.length());
1979 @Contract(pure = true)
1980 public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
1981 end = min(end, s.length());
1982 for (int i = max(start, 0); i < end; i++) {
1983 if (s.charAt(i) == c) return i;
1988 @Contract(pure = true)
1989 public static boolean contains(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1990 return indexOf(sequence, infix) >= 0;
1993 @Contract(pure = true)
1994 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1995 return indexOf(sequence, infix, 0);
1998 @Contract(pure = true)
1999 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start) {
2000 for (int i = start; i <= sequence.length() - infix.length(); i++) {
2001 if (startsWith(sequence, i, infix)) {
2008 @Contract(pure = true)
2009 public static int indexOf(@NotNull CharSequence s, char c, int start, int end, boolean caseSensitive) {
2010 end = min(end, s.length());
2011 for (int i = max(start, 0); i < end; i++) {
2012 if (charsMatch(s.charAt(i), c, !caseSensitive)) return i;
2017 @Contract(pure = true)
2018 public static int indexOf(@NotNull char[] s, char c, int start, int end, boolean caseSensitive) {
2019 end = min(end, s.length);
2020 for (int i = max(start, 0); i < end; i++) {
2021 if (charsMatch(s[i], c, !caseSensitive)) return i;
2026 @Contract(pure = true)
2027 public static int indexOfSubstringEnd(@NotNull String text, @NotNull String subString) {
2028 int i = text.indexOf(subString);
2029 if (i == -1) return -1;
2030 return i + subString.length();
2033 @Contract(pure = true)
2034 public static int indexOfAny(@NotNull final String s, @NotNull final String chars) {
2035 return indexOfAny(s, chars, 0, s.length());
2038 @Contract(pure = true)
2039 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars) {
2040 return indexOfAny(s, chars, 0, s.length());
2043 @Contract(pure = true)
2044 public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) {
2045 return indexOfAny((CharSequence)s, chars, start, end);
2048 @Contract(pure = true)
2049 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars, final int start, int end) {
2050 end = min(end, s.length());
2051 for (int i = max(start, 0); i < end; i++) {
2052 if (containsChar(chars, s.charAt(i))) return i;
2057 @Contract(pure = true)
2058 public static int lastIndexOfAny(@NotNull CharSequence s, @NotNull final String chars) {
2059 for (int i = s.length() - 1; i >= 0; i--) {
2060 if (containsChar(chars, s.charAt(i))) return i;
2066 @Contract(pure = true)
2067 public static String substringBefore(@NotNull String text, @NotNull String subString) {
2068 int i = text.indexOf(subString);
2069 if (i == -1) return null;
2070 return text.substring(0, i);
2074 @Contract(pure = true)
2075 public static String substringAfter(@NotNull String text, @NotNull String subString) {
2076 int i = text.indexOf(subString);
2077 if (i == -1) return null;
2078 return text.substring(i + subString.length());
2082 * Allows to retrieve index of last occurrence of the given symbols at <code>[start; end)</code> sub-sequence of the given text.
2084 * @param s target text
2085 * @param c target symbol which last occurrence we want to check
2086 * @param start start offset of the target text (inclusive)
2087 * @param end end offset of the target text (exclusive)
2088 * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
2089 * <code>-1</code> otherwise
2091 @Contract(pure = true)
2092 public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
2093 return StringUtilRt.lastIndexOf(s, c, start, end);
2097 @Contract(pure = true)
2098 public static String first(@NotNull String text, final int maxLength, final boolean appendEllipsis) {
2099 return text.length() > maxLength ? text.substring(0, maxLength) + (appendEllipsis ? "..." : "") : text;
2103 @Contract(pure = true)
2104 public static CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
2105 return text.length() > length ? text.subSequence(0, length) + (appendEllipsis ? "..." : "") : text;
2109 @Contract(pure = true)
2110 public static CharSequence last(@NotNull CharSequence text, final int length, boolean prependEllipsis) {
2111 return text.length() > length ? (prependEllipsis ? "..." : "") + text.subSequence(text.length() - length, text.length()) : text;
2115 @Contract(pure = true)
2116 public static String firstLast(@NotNull String text, int length) {
2117 return text.length() > length
2118 ? text.subSequence(0, length / 2) + "\u2026" + text.subSequence(text.length() - length / 2 - 1, text.length())
2123 @Contract(pure = true)
2124 public static String escapeChar(@NotNull final String str, final char character) {
2125 return escapeChars(str, character);
2129 @Contract(pure = true)
2130 public static String escapeChars(@NotNull final String str, final char... character) {
2131 final StringBuilder buf = new StringBuilder(str);
2132 for (char c : character) {
2135 return buf.toString();
2138 public static void escapeChar(@NotNull final StringBuilder buf, final char character) {
2140 while ((idx = indexOf(buf, character, idx)) >= 0) {
2141 buf.insert(idx, "\\");
2147 @Contract(pure = true)
2148 public static String escapeQuotes(@NotNull final String str) {
2149 return escapeChar(str, '"');
2152 public static void escapeQuotes(@NotNull final StringBuilder buf) {
2153 escapeChar(buf, '"');
2157 @Contract(pure = true)
2158 public static String escapeSlashes(@NotNull final String str) {
2159 return escapeChar(str, '/');
2163 @Contract(pure = true)
2164 public static String escapeBackSlashes(@NotNull final String str) {
2165 return escapeChar(str, '\\');
2168 public static void escapeSlashes(@NotNull final StringBuilder buf) {
2169 escapeChar(buf, '/');
2173 @Contract(pure = true)
2174 public static String unescapeSlashes(@NotNull final String str) {
2175 final StringBuilder buf = new StringBuilder(str.length());
2176 unescapeChar(buf, str, '/');
2177 return buf.toString();
2181 @Contract(pure = true)
2182 public static String unescapeBackSlashes(@NotNull final String str) {
2183 final StringBuilder buf = new StringBuilder(str.length());
2184 unescapeChar(buf, str, '\\');
2185 return buf.toString();
2189 @Contract(pure = true)
2190 public static String unescapeChar(@NotNull final String str, char unescapeChar) {
2191 final StringBuilder buf = new StringBuilder(str.length());
2192 unescapeChar(buf, str, unescapeChar);
2193 return buf.toString();
2196 private static void unescapeChar(@NotNull StringBuilder buf, @NotNull String str, char unescapeChar) {
2197 final int length = str.length();
2198 final int last = length - 1;
2199 for (int i = 0; i < length; i++) {
2200 char ch = str.charAt(i);
2201 if (ch == '\\' && i != last) {
2204 if (ch != unescapeChar) buf.append('\\');
2211 public static void quote(@NotNull final StringBuilder builder) {
2212 quote(builder, '\"');
2215 public static void quote(@NotNull final StringBuilder builder, final char quotingChar) {
2216 builder.insert(0, quotingChar);
2217 builder.append(quotingChar);
2221 @Contract(pure = true)
2222 public static String wrapWithDoubleQuote(@NotNull String str) {
2223 return '\"' + str + "\"";
2226 @NonNls private static final String[] REPLACES_REFS = {"<", ">", "&", "'", """};
2227 @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""};
2229 @Contract(value = "null -> null; !null -> !null",pure = true)
2230 public static String unescapeXml(@Nullable final String text) {
2231 if (text == null) return null;
2232 return replace(text, REPLACES_REFS, REPLACES_DISP);
2235 @Contract(value = "null -> null; !null -> !null",pure = true)
2236 public static String escapeXml(@Nullable final String text) {
2237 if (text == null) return null;
2238 return replace(text, REPLACES_DISP, REPLACES_REFS);
2241 public static String removeHtmlTags (@Nullable String htmlString) {
2242 if (isEmpty(htmlString)) return htmlString;
2244 html2TextParser.parse(new StringReader(htmlString));
2246 catch (IOException e) {
2249 return html2TextParser.getText();
2252 @NonNls private static final String[] MN_QUOTED = {"&&", "__"};
2253 @NonNls private static final String[] MN_CHARS = {"&", "_"};
2255 @Contract(value = "null -> null; !null -> !null", pure = true)
2256 public static String escapeMnemonics(@Nullable String text) {
2257 if (text == null) return null;
2258 return replace(text, MN_CHARS, MN_QUOTED);
2262 @Contract(pure = true)
2263 public static String htmlEmphasize(@NotNull String text) {
2264 return "<b><code>" + escapeXml(text) + "</code></b>";
2269 @Contract(pure = true)
2270 public static String escapeToRegexp(@NotNull String text) {
2271 final StringBuilder result = new StringBuilder(text.length());
2272 return escapeToRegexp(text, result).toString();
2276 public static StringBuilder escapeToRegexp(@NotNull CharSequence text, @NotNull StringBuilder builder) {
2277 for (int i = 0; i < text.length(); i++) {
2278 final char c = text.charAt(i);
2279 if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_') {
2282 else if (c == '\n') {
2283 builder.append("\\n");
2286 builder.append('\\').append(c);
2293 @Contract(pure = true)
2294 public static boolean isEscapedBackslash(@NotNull char[] chars, int startOffset, int backslashOffset) {
2295 if (chars[backslashOffset] != '\\') {
2298 boolean escaped = false;
2299 for (int i = startOffset; i < backslashOffset; i++) {
2300 if (chars[i] == '\\') {
2310 @Contract(pure = true)
2311 public static boolean isEscapedBackslash(@NotNull CharSequence text, int startOffset, int backslashOffset) {
2312 if (text.charAt(backslashOffset) != '\\') {
2315 boolean escaped = false;
2316 for (int i = startOffset; i < backslashOffset; i++) {
2317 if (text.charAt(i) == '\\') {
2328 @Contract(pure = true)
2329 public static String replace(@NotNull String text, @NotNull String[] from, @NotNull String[] to) {
2330 return replace(text, Arrays.asList(from), Arrays.asList(to));
2334 @Contract(pure = true)
2335 public static String replace(@NotNull String text, @NotNull List<String> from, @NotNull List<String> to) {
2336 assert from.size() == to.size();
2337 final StringBuilder result = new StringBuilder(text.length());
2339 for (int i = 0; i < text.length(); i++) {
2340 for (int j = 0; j < from.size(); j += 1) {
2341 String toReplace = from.get(j);
2342 String replaceWith = to.get(j);
2344 final int len = toReplace.length();
2345 if (text.regionMatches(i, toReplace, 0, len)) {
2346 result.append(replaceWith);
2351 result.append(text.charAt(i));
2353 return result.toString();
2357 @Contract(pure = true)
2358 public static String[] filterEmptyStrings(@NotNull String[] strings) {
2360 for (String string : strings) {
2361 if (string == null || string.isEmpty()) emptyCount++;
2363 if (emptyCount == 0) return strings;
2365 String[] result = ArrayUtil.newStringArray(strings.length - emptyCount);
2367 for (String string : strings) {
2368 if (string == null || string.isEmpty()) continue;
2369 result[count++] = string;
2375 @Contract(pure = true)
2376 public static int countNewLines(@NotNull CharSequence text) {
2377 return countChars(text, '\n');
2380 @Contract(pure = true)
2381 public static int countChars(@NotNull CharSequence text, char c) {
2382 return countChars(text, c, 0, false);
2385 @Contract(pure = true)
2386 public static int countChars(@NotNull CharSequence text, char c, int offset, boolean stopAtOtherChar) {
2387 return countChars(text, c, offset, text.length(), stopAtOtherChar);
2390 @Contract(pure = true)
2391 public static int countChars(@NotNull CharSequence text, char c, int start, int end, boolean stopAtOtherChar) {
2393 for (int i = start, len = min(text.length(), end); i < len; ++i) {
2394 if (text.charAt(i) == c) {
2397 else if (stopAtOtherChar) {
2405 @Contract(pure = true)
2406 public static String capitalsOnly(@NotNull String s) {
2407 StringBuilder b = new StringBuilder();
2408 for (int i = 0; i < s.length(); i++) {
2409 if (Character.isUpperCase(s.charAt(i))) {
2410 b.append(s.charAt(i));
2414 return b.toString();
2418 * @param args Strings to join.
2419 * @return {@code null} if any of given Strings is {@code null}.
2422 @Contract(pure = true)
2423 public static String joinOrNull(@NotNull String... args) {
2424 StringBuilder r = new StringBuilder();
2425 for (String arg : args) {
2426 if (arg == null) return null;
2429 return r.toString();
2433 @Contract(pure = true)
2434 public static String getPropertyName(@NonNls @NotNull String methodName) {
2435 if (methodName.startsWith("get")) {
2436 return Introspector.decapitalize(methodName.substring(3));
2438 if (methodName.startsWith("is")) {
2439 return Introspector.decapitalize(methodName.substring(2));
2441 if (methodName.startsWith("set")) {
2442 return Introspector.decapitalize(methodName.substring(3));
2447 @Contract(pure = true)
2448 public static boolean isJavaIdentifierStart(char c) {
2449 return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierStart(c);
2452 @Contract(pure = true)
2453 public static boolean isJavaIdentifierPart(char c) {
2454 return c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierPart(c);
2457 @Contract(pure = true)
2458 public static boolean isJavaIdentifier(@NotNull String text) {
2459 int len = text.length();
2460 if (len == 0) return false;
2462 if (!isJavaIdentifierStart(text.charAt(0))) return false;
2464 for (int i = 1; i < len; i++) {
2465 if (!isJavaIdentifierPart(text.charAt(i))) return false;
2472 * Escape property name or key in property file. Unicode characters are escaped as well.
2474 * @param input an input to escape
2475 * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case.
2476 * @return an escaped string
2479 @Contract(pure = true)
2480 public static String escapeProperty(@NotNull String input, final boolean isKey) {
2481 final StringBuilder escaped = new StringBuilder(input.length());
2482 for (int i = 0; i < input.length(); i++) {
2483 final char ch = input.charAt(i);
2486 if (isKey && i == 0) {
2487 // only the leading space has to be escaped
2488 escaped.append('\\');
2490 escaped.append(' ');
2493 escaped.append("\\t");
2496 escaped.append("\\r");
2499 escaped.append("\\n");
2502 escaped.append("\\f");
2509 escaped.append('\\');
2513 if (20 < ch && ch < 0x7F) {
2517 escaped.append("\\u");
2518 escaped.append(Character.forDigit((ch >> 12) & 0xF, 16));
2519 escaped.append(Character.forDigit((ch >> 8) & 0xF, 16));
2520 escaped.append(Character.forDigit((ch >> 4) & 0xF, 16));
2521 escaped.append(Character.forDigit((ch) & 0xF, 16));
2526 return escaped.toString();
2529 @Contract(pure = true)
2530 public static String getQualifiedName(@Nullable String packageName, String className) {
2531 if (packageName == null || packageName.isEmpty()) {
2534 return packageName + '.' + className;
2537 @Contract(pure = true)
2538 public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
2539 // todo duplicates com.intellij.util.text.VersionComparatorUtil.compare
2540 // todo please refactor next time you make changes here
2541 if (v1 == null && v2 == null) {
2551 String[] part1 = v1.split("[\\.\\_\\-]");
2552 String[] part2 = v2.split("[\\.\\_\\-]");
2555 for (; idx < part1.length && idx < part2.length; idx++) {
2556 String p1 = part1[idx];
2557 String p2 = part2[idx];
2560 if (p1.matches("\\d+") && p2.matches("\\d+")) {
2561 cmp = new Integer(p1).compareTo(new Integer(p2));
2564 cmp = part1[idx].compareTo(part2[idx]);
2566 if (cmp != 0) return cmp;
2569 if (part1.length == part2.length) {
2573 boolean left = part1.length > idx;
2574 String[] parts = left ? part1 : part2;
2576 for (; idx < parts.length; idx++) {
2577 String p = parts[idx];
2579 if (p.matches("\\d+")) {
2580 cmp = new Integer(p).compareTo(0);
2585 if (cmp != 0) return left ? cmp : -cmp;
2591 @Contract(pure = true)
2592 public static int getOccurrenceCount(@NotNull String text, final char c) {
2595 while (i < text.length()) {
2596 i = text.indexOf(c, i);
2608 @Contract(pure = true)
2609 public static int getOccurrenceCount(@NotNull String text, @NotNull String s) {
2612 while (i < text.length()) {
2613 i = text.indexOf(s, i);
2626 @Contract(pure = true)
2627 public static String fixVariableNameDerivedFromPropertyName(@NotNull String name) {
2628 if (isEmptyOrSpaces(name)) return name;
2629 char c = name.charAt(0);
2631 return "an" + Character.toUpperCase(c) + name.substring(1);
2633 return "a" + Character.toUpperCase(c) + name.substring(1);
2637 @Contract(pure = true)
2638 public static String sanitizeJavaIdentifier(@NotNull String name) {
2639 final StringBuilder result = new StringBuilder(name.length());
2641 for (int i = 0; i < name.length(); i++) {
2642 final char ch = name.charAt(i);
2643 if (Character.isJavaIdentifierPart(ch)) {
2644 if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
2651 return result.toString();
2654 public static void assertValidSeparators(@NotNull CharSequence s) {
2655 char[] chars = CharArrayUtil.fromSequenceWithoutCopying(s);
2656 int slashRIndex = -1;
2658 if (chars != null) {
2659 for (int i = 0, len = s.length(); i < len; ++i) {
2660 if (chars[i] == '\r') {
2667 for (int i = 0, len = s.length(); i < len; i++) {
2668 if (s.charAt(i) == '\r') {
2675 if (slashRIndex != -1) {
2677 String.valueOf(last(s.subSequence(0, slashRIndex), 10, true)) + first(s.subSequence(slashRIndex, s.length()), 10, true);
2678 context = escapeStringCharacters(context);
2679 LOG.error("Wrong line separators: '" + context + "' at offset " + slashRIndex);
2684 @Contract(pure = true)
2685 public static String tail(@NotNull String s, final int idx) {
2686 return idx >= s.length() ? "" : s.substring(idx, s.length());
2690 * Splits string by lines.
2692 * @param string String to split
2693 * @return array of strings
2696 @Contract(pure = true)
2697 public static String[] splitByLines(@NotNull String string) {
2698 return splitByLines(string, true);
2702 * Splits string by lines. If several line separators are in a row corresponding empty lines
2703 * are also added to result if {@code excludeEmptyStrings} is {@code false}.
2705 * @param string String to split
2706 * @return array of strings
2709 @Contract(pure = true)
2710 public static String[] splitByLines(@NotNull String string, boolean excludeEmptyStrings) {
2711 return (excludeEmptyStrings ? EOL_SPLIT_PATTERN : EOL_SPLIT_PATTERN_WITH_EMPTY).split(string);
2715 @Contract(pure = true)
2716 public static String[] splitByLinesDontTrim(@NotNull String string) {
2717 return EOL_SPLIT_DONT_TRIM_PATTERN.split(string);
2721 * Splits string by lines, keeping all line separators at the line ends and in the empty lines.
2722 * <br> E.g. splitting text
2731 * will return the following array: foo\r\n, \n, bar\n, \r\n, baz\r, \r
2735 @Contract(pure = true)
2736 public static String[] splitByLinesKeepSeparators(@NotNull String string) {
2737 return EOL_SPLIT_KEEP_SEPARATORS.split(string);
2741 @Contract(pure = true)
2742 public static List<Pair<String, Integer>> getWordsWithOffset(@NotNull String s) {
2743 List<Pair<String, Integer>> res = ContainerUtil.newArrayList();
2745 StringBuilder name = new StringBuilder();
2747 for (int i = 0; i < s.length(); i++) {
2748 if (Character.isWhitespace(s.charAt(i))) {
2749 if (name.length() > 0) {
2750 res.add(Pair.create(name.toString(), startInd));
2756 if (startInd == -1) {
2759 name.append(s.charAt(i));
2766 * Implementation of <a href="http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html"/>
2767 * "Sorting for Humans: Natural Sort Order"</a>
2769 @Contract(pure = true)
2770 public static int naturalCompare(@Nullable String string1, @Nullable String string2) {
2771 return naturalCompare(string1, string2, false);
2774 @Contract(pure = true)
2775 private static int naturalCompare(@Nullable String string1, @Nullable String string2, boolean caseSensitive) {
2776 //noinspection StringEquality
2777 if (string1 == string2) {
2780 if (string1 == null) {
2783 if (string2 == null) {
2787 final int string1Length = string1.length();
2788 final int string2Length = string2.length();
2791 for (; i < string1Length && j < string2Length; i++, j++) {
2792 char ch1 = string1.charAt(i);
2793 char ch2 = string2.charAt(j);
2794 if ((isDecimalDigit(ch1) || ch1 == ' ') && (isDecimalDigit(ch2) || ch2 == ' ')) {
2796 while (ch1 == ' ' || ch1 == '0') { // skip leading spaces and zeros
2798 if (startNum1 >= string1Length) break;
2799 ch1 = string1.charAt(startNum1);
2802 while (ch2 == ' ' || ch2 == '0') { // skip leading spaces and zeros
2804 if (startNum2 >= string2Length) break;
2805 ch2 = string2.charAt(startNum2);
2809 // find end index of number
2810 while (i < string1Length && isDecimalDigit(string1.charAt(i))) i++;
2811 while (j < string2Length && isDecimalDigit(string2.charAt(j))) j++;
2812 final int lengthDiff = (i - startNum1) - (j - startNum2);
2813 if (lengthDiff != 0) {
2814 // numbers with more digits are always greater than shorter numbers
2817 for (; startNum1 < i; startNum1++, startNum2++) {
2818 // compare numbers with equal digit count
2819 final int diff = string1.charAt(startNum1) - string2.charAt(startNum2);
2828 if (caseSensitive) {
2832 // similar logic to charsMatch() below
2834 final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
2836 final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
2845 // After the loop the end of one of the strings might not have been reached, if the other
2846 // string ends with a number and the strings are equal until the end of that number. When
2847 // there are more characters in the string, then it is greater.
2848 if (i < string1Length) {
2851 if (j < string2Length) {
2854 if (!caseSensitive && string1Length == string2Length) {
2855 // do case sensitive compare if case insensitive strings are equal
2856 return naturalCompare(string1, string2, true);
2858 return string1Length - string2Length;
2861 @Contract(pure = true)
2862 public static boolean isDecimalDigit(char c) {
2863 return c >= '0' && c <= '9';
2866 @Contract(pure = true)
2867 public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) {
2868 //noinspection StringEquality
2869 if (s1 == s2) return 0;
2870 if (s1 == null) return -1;
2871 if (s2 == null) return 1;
2872 return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
2875 @Contract(pure = true)
2876 public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
2877 final int compare = compare(s1, s2, ignoreCase);
2878 return compare != 0 ? compare : compare(t1, t2, ignoreCase);
2881 @Contract(pure = true)
2882 public static int hashCode(@NotNull CharSequence s) {
2883 return stringHashCode(s);
2886 @Contract(pure = true)
2887 public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2888 if (s1 == null ^ s2 == null) {
2896 if (s1.length() != s2.length()) {
2899 for (int i = 0; i < s1.length(); i++) {
2900 if (s1.charAt(i) != s2.charAt(i)) {
2907 @Contract(pure = true)
2908 public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2909 if (s1 == null ^ s2 == null) {
2917 if (s1.length() != s2.length()) {
2920 for (int i = 0; i < s1.length(); i++) {
2921 if (!charsMatch(s1.charAt(i), s2.charAt(i), true)) {
2928 @Contract(pure = true)
2929 public static boolean equalsIgnoreWhitespaces(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2930 if (s1 == null ^ s2 == null) {
2938 int len1 = s1.length();
2939 int len2 = s2.length();
2943 while (index1 < len1 && index2 < len2) {
2944 if (s1.charAt(index1) == s2.charAt(index2)) {
2950 boolean skipped = false;
2951 while (index1 != len1 && isWhiteSpace(s1.charAt(index1))) {
2955 while (index2 != len2 && isWhiteSpace(s2.charAt(index2))) {
2960 if (!skipped) return false;
2963 for (; index1 != len1; index1++) {
2964 if (!isWhiteSpace(s1.charAt(index1))) return false;
2966 for (; index2 != len2; index2++) {
2967 if (!isWhiteSpace(s2.charAt(index2))) return false;
2973 @Contract(pure = true)
2974 public static boolean equalsTrimWhitespaces(@NotNull CharSequence s1, @NotNull CharSequence s2) {
2976 int end1 = s1.length();
2978 int end2 = s2.length();
2980 while (start1 < end1) {
2981 char c = s1.charAt(start1);
2982 if (!isWhiteSpace(c)) break;
2986 while (start1 < end1) {
2987 char c = s1.charAt(end1 - 1);
2988 if (!isWhiteSpace(c)) break;
2992 while (start2 < end2) {
2993 char c = s2.charAt(start2);
2994 if (!isWhiteSpace(c)) break;