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.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 (!isPreposition(s, i, j - 1, prepositions)) {
504 if (buffer == null) {
505 buffer = new StringBuilder(s);
507 buffer.setCharAt(i, title ? toUpperCase(currChar) : toLowerCase(currChar));
513 return buffer == null ? s : buffer.toString();
516 @NonNls private static final String[] ourPrepositions = {
517 "a", "an", "and", "as", "at", "but", "by", "down", "for", "from", "if", "in", "into", "not", "of", "on", "onto", "or", "out", "over",
518 "per", "nor", "the", "to", "up", "upon", "via", "with"
521 @Contract(pure = true)
522 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar) {
523 return isPreposition(s, firstChar, lastChar, ourPrepositions);
526 @Contract(pure = true)
527 public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar, @NotNull String[] prepositions) {
528 for (String preposition : prepositions) {
529 boolean found = false;
530 if (lastChar - firstChar + 1 == preposition.length()) {
532 for (int j = 0; j < preposition.length(); j++) {
533 if (toLowerCase(s.charAt(firstChar + j)) != preposition.charAt(j)) {
546 @Contract(pure = true)
547 public static NotNullFunction<String, String> escaper(final boolean escapeSlash, @Nullable final String additionalChars) {
548 return new NotNullFunction<String, String>() {
551 public String fun(@NotNull String dom) {
552 final StringBuilder builder = new StringBuilder(dom.length());
553 escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder);
554 return builder.toString();
560 public static void escapeStringCharacters(int length, @NotNull String str, @NotNull @NonNls StringBuilder buffer) {
561 escapeStringCharacters(length, str, "\"", buffer);
565 public static StringBuilder escapeStringCharacters(int length,
567 @Nullable String additionalChars,
568 @NotNull @NonNls StringBuilder buffer) {
569 return escapeStringCharacters(length, str, additionalChars, true, buffer);
573 public static StringBuilder escapeStringCharacters(int length,
575 @Nullable String additionalChars,
577 @NotNull @NonNls StringBuilder buffer) {
579 for (int idx = 0; idx < length; idx++) {
580 char ch = str.charAt(idx);
583 buffer.append("\\b");
587 buffer.append("\\t");
591 buffer.append("\\n");
595 buffer.append("\\f");
599 buffer.append("\\r");
603 if (escapeSlash && ch == '\\') {
604 buffer.append("\\\\");
606 else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
607 buffer.append("\\").append(ch);
609 else if (!isPrintableUnicode(ch)) {
610 CharSequence hexCode = StringUtilRt.toUpperCase(Integer.toHexString(ch));
611 buffer.append("\\u");
612 int paddingCount = 4 - hexCode.length();
613 while (paddingCount-- > 0) {
616 buffer.append(hexCode);
627 @Contract(pure = true)
628 private static boolean isPrintableUnicode(char c) {
629 int t = Character.getType(c);
630 return t != Character.UNASSIGNED && t != Character.LINE_SEPARATOR && t != Character.PARAGRAPH_SEPARATOR &&
631 t != Character.CONTROL && t != Character.FORMAT && t != Character.PRIVATE_USE && t != Character.SURROGATE;
635 @Contract(pure = true)
636 public static String escapeStringCharacters(@NotNull String s) {
637 StringBuilder buffer = new StringBuilder(s.length());
638 escapeStringCharacters(s.length(), s, "\"", buffer);
639 return buffer.toString();
643 @Contract(pure = true)
644 public static String escapeCharCharacters(@NotNull String s) {
645 StringBuilder buffer = new StringBuilder(s.length());
646 escapeStringCharacters(s.length(), s, "\'", buffer);
647 return buffer.toString();
651 @Contract(pure = true)
652 public static String unescapeStringCharacters(@NotNull String s) {
653 StringBuilder buffer = new StringBuilder(s.length());
654 unescapeStringCharacters(s.length(), s, buffer);
655 return buffer.toString();
658 private static boolean isQuoteAt(@NotNull String s, int ind) {
659 char ch = s.charAt(ind);
660 return ch == '\'' || ch == '\"';
663 @Contract(pure = true)
664 public static boolean isQuotedString(@NotNull String s) {
665 return s.length() > 1 && isQuoteAt(s, 0) && s.charAt(0) == s.charAt(s.length() - 1);
669 @Contract(pure = true)
670 public static String unquoteString(@NotNull String s) {
671 if (isQuotedString(s)) {
672 return s.substring(1, s.length() - 1);
678 @Contract(pure = true)
679 public static String unquoteString(@NotNull String s, char quotationChar) {
680 if (s.length() > 1 && quotationChar == s.charAt(0) && quotationChar == s.charAt(s.length() - 1)) {
681 return s.substring(1, s.length() - 1);
687 * This is just an optimized version of Matcher.quoteReplacement
690 @Contract(pure = true)
691 public static String quoteReplacement(@NotNull String s) {
692 boolean needReplacements = false;
694 for (int i = 0; i < s.length(); i++) {
695 char c = s.charAt(i);
696 if (c == '\\' || c == '$') {
697 needReplacements = true;
702 if (!needReplacements) return s;
704 StringBuilder sb = new StringBuilder(s.length() * 6 / 5);
705 for (int i = 0; i < s.length(); i++) {
706 char c = s.charAt(i);
719 return sb.toString();
722 private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
723 boolean escaped = false;
724 for (int idx = 0; idx < length; idx++) {
725 char ch = s.charAt(idx);
769 if (idx + 4 < length) {
771 int code = Integer.parseInt(s.substring(idx + 1, idx + 5), 16);
773 buffer.append((char)code);
775 catch (NumberFormatException e) {
776 buffer.append("\\u");
780 buffer.append("\\u");
792 if (escaped) buffer.append('\\');
795 @SuppressWarnings("HardCodedStringLiteral")
797 @Contract(pure = true)
798 public static String pluralize(@NotNull String suggestion) {
799 if (suggestion.endsWith("Child") || suggestion.endsWith("child")) {
800 return suggestion + "ren";
803 if (suggestion.equals("this")) {
806 if (suggestion.equals("This")) {
809 if (suggestion.equals("fix") || suggestion.equals("Fix")) {
810 return suggestion + "es";
813 if (endsWithIgnoreCase(suggestion, "es")) {
817 int len = suggestion.length();
818 if (endsWithIgnoreCase(suggestion, "ex") || endsWithIgnoreCase(suggestion, "ix")) {
819 return suggestion.substring(0, len - 2) + "ices";
821 if (endsWithIgnoreCase(suggestion, "um")) {
822 return suggestion.substring(0, len - 2) + "a";
824 if (endsWithIgnoreCase(suggestion, "an")) {
825 return suggestion.substring(0, len - 2) + "en";
828 if (endsWithIgnoreCase(suggestion, "s") || endsWithIgnoreCase(suggestion, "x") || endsWithIgnoreCase(suggestion, "ch")) {
829 return suggestion + "es";
832 if (endsWithIgnoreCase(suggestion, "y") && len > 1 && !isVowel(toLowerCase(suggestion.charAt(len - 2)))) {
833 return suggestion.substring(0, len - 1) + "ies";
836 return suggestion + "s";
840 @Contract(pure = true)
841 public static String capitalizeWords(@NotNull String text,
843 return capitalizeWords(text, " \t\n\r\f", allWords, false);
847 @Contract(pure = true)
848 public static String capitalizeWords(@NotNull String text,
849 @NotNull String tokenizerDelim,
851 boolean leaveOriginalDelims) {
852 final StringTokenizer tokenizer = new StringTokenizer(text, tokenizerDelim, leaveOriginalDelims);
853 final StringBuilder out = new StringBuilder(text.length());
854 boolean toCapitalize = true;
855 while (tokenizer.hasMoreTokens()) {
856 final String word = tokenizer.nextToken();
857 if (!leaveOriginalDelims && out.length() > 0) {
860 out.append(toCapitalize ? capitalize(word) : word);
862 toCapitalize = false;
865 return out.toString();
868 @Contract(pure = true)
869 public static String decapitalize(String s) {
870 return Introspector.decapitalize(s);
873 @Contract(pure = true)
874 public static boolean isVowel(char c) {
875 return VOWELS.indexOf(c) >= 0;
879 @Contract(pure = true)
880 public static String capitalize(@NotNull String s) {
881 if (s.isEmpty()) return s;
882 if (s.length() == 1) return StringUtilRt.toUpperCase(s).toString();
885 if (Character.isUpperCase(s.charAt(0))) return s;
886 return toUpperCase(s.charAt(0)) + s.substring(1);
889 @Contract(value = "null -> false", pure = true)
890 public static boolean isCapitalized(@Nullable String s) {
891 return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0));
895 @Contract(pure = true)
896 public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
897 if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
900 return capitalize(s);
903 @Contract(pure = true)
904 public static int stringHashCode(@NotNull CharSequence chars) {
905 if (chars instanceof String || chars instanceof CharSequenceWithStringHash) {
906 // we know for sure these classes have conformant (and maybe faster) hashCode()
907 return chars.hashCode();
910 return stringHashCode(chars, 0, chars.length());
913 @Contract(pure = true)
914 public static int stringHashCode(@NotNull CharSequence chars, int from, int to) {
916 for (int off = from; off < to; off++) {
917 h = 31 * h + chars.charAt(off);
922 @Contract(pure = true)
923 public static int stringHashCode(char[] chars, int from, int to) {
925 for (int off = from; off < to; off++) {
926 h = 31 * h + chars[off];
931 @Contract(pure = true)
932 public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) {
934 for (int off = from; off < to; off++) {
935 h = 31 * h + toLowerCase(chars[off]);
940 @Contract(pure = true)
941 public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
943 for (int off = from; off < to; off++) {
944 h = 31 * h + toLowerCase(chars.charAt(off));
949 @Contract(pure = true)
950 public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
951 return stringHashCodeInsensitive(chars, 0, chars.length());
954 @Contract(pure = true)
955 public static int stringHashCodeIgnoreWhitespaces(char[] chars, int from, int to) {
957 for (int off = from; off < to; off++) {
959 if (!isWhiteSpace(c)) {
966 @Contract(pure = true)
967 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars, int from, int to) {
969 for (int off = from; off < to; off++) {
970 char c = chars.charAt(off);
971 if (!isWhiteSpace(c)) {
978 @Contract(pure = true)
979 public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars) {
980 return stringHashCodeIgnoreWhitespaces(chars, 0, chars.length());
984 * Equivalent to string.startsWith(prefixes[0] + prefixes[1] + ...) but avoids creating an object for concatenation.
986 @Contract(pure = true)
987 public static boolean startsWithConcatenation(@NotNull String string, @NotNull String... prefixes) {
989 for (String prefix : prefixes) {
990 int prefixLen = prefix.length();
991 if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
999 @Contract(value = "null -> null; !null -> !null", pure = true)
1000 public static String trim(@Nullable String s) {
1001 return s == null ? null : s.trim();
1005 @Contract(pure = true)
1006 public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix) {
1007 return trimEnd(s, suffix, false);
1011 @Contract(pure = true)
1012 public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix, boolean ignoreCase) {
1013 boolean endsWith = ignoreCase ? endsWithIgnoreCase(s, suffix) : s.endsWith(suffix);
1015 return s.substring(0, s.length() - suffix.length());
1021 @Contract(pure = true)
1022 public static String trimEnd(@NotNull String s, char suffix) {
1023 if (endsWithChar(s, suffix)) {
1024 return s.substring(0, s.length() - 1);
1030 @Contract(pure = true)
1031 public static String trimLog(@NotNull final String text, final int limit) {
1032 if (limit > 5 && text.length() > limit) {
1033 return text.substring(0, limit - 5) + " ...\n";
1039 @Contract(pure = true)
1040 public static String trimLeading(@NotNull String string) {
1041 return trimLeading((CharSequence)string).toString();
1044 @Contract(pure = true)
1045 public static CharSequence trimLeading(@NotNull CharSequence string) {
1047 while (index < string.length() && Character.isWhitespace(string.charAt(index))) index++;
1048 return string.subSequence(index, string.length());
1052 @Contract(pure = true)
1053 public static String trimLeading(@NotNull String string, char symbol) {
1055 while (index < string.length() && string.charAt(index) == symbol) index++;
1056 return string.substring(index);
1060 @Contract(pure = true)
1061 public static String trimTrailing(@NotNull String string) {
1062 return trimTrailing((CharSequence)string).toString();
1066 @Contract(pure = true)
1067 public static CharSequence trimTrailing(@NotNull CharSequence string) {
1068 int index = string.length() - 1;
1069 while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
1070 return string.subSequence(0, index + 1);
1074 @Contract(pure = true)
1075 public static String trimTrailing(@NotNull String string, char symbol) {
1076 int index = string.length() - 1;
1077 while (index >= 0 && string.charAt(index) == symbol) index--;
1078 return string.substring(0, index + 1);
1081 @Contract(pure = true)
1082 public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
1083 return s != null && s.length() != 0 && s.charAt(0) == prefix;
1086 @Contract(pure = true)
1087 public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
1088 return StringUtilRt.endsWithChar(s, suffix);
1092 @Contract(pure = true)
1093 public static String trimStart(@NotNull String s, @NonNls @NotNull String prefix) {
1094 if (s.startsWith(prefix)) {
1095 return s.substring(prefix.length());
1101 @Contract(pure = true)
1102 public static String pluralize(@NotNull String base, int n) {
1103 if (n == 1) return base;
1104 return pluralize(base);
1107 public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
1108 assert times >= 0 : times;
1110 for (int i = 0; i < times; i++) {
1111 buffer.append(symbol);
1114 catch (IOException e) {
1119 @Contract(pure = true)
1120 public static String defaultIfEmpty(@Nullable String value, String defaultValue) {
1121 return isEmpty(value) ? defaultValue : value;
1124 @Contract(value = "null -> false", pure = true)
1125 public static boolean isNotEmpty(@Nullable String s) {
1126 return s != null && !s.isEmpty();
1129 @Contract(value = "null -> true", pure=true)
1130 public static boolean isEmpty(@Nullable String s) {
1131 return s == null || s.isEmpty();
1134 @Contract(value = "null -> true",pure = true)
1135 public static boolean isEmpty(@Nullable CharSequence cs) {
1136 return cs == null || cs.length() == 0;
1139 @Contract(pure = true)
1140 public static int length(@Nullable CharSequence cs) {
1141 return cs == null ? 0 : cs.length();
1145 @Contract(pure = true)
1146 public static String notNullize(@Nullable final String s) {
1147 return notNullize(s, "");
1151 @Contract(pure = true)
1152 public static String notNullize(@Nullable final String s, @NotNull String defaultValue) {
1153 return s == null ? defaultValue : s;
1157 @Contract(pure = true)
1158 public static String nullize(@Nullable final String s) {
1159 return nullize(s, false);
1163 @Contract(pure = true)
1164 public static String nullize(@Nullable final String s, boolean nullizeSpaces) {
1165 if (nullizeSpaces) {
1166 if (isEmptyOrSpaces(s)) return null;
1169 if (isEmpty(s)) return null;
1174 @Contract(value = "null -> true",pure = true)
1175 // we need to keep this method to preserve backward compatibility
1176 public static boolean isEmptyOrSpaces(@Nullable String s) {
1177 return isEmptyOrSpaces((CharSequence)s);
1180 @Contract(value = "null -> true", pure = true)
1181 public static boolean isEmptyOrSpaces(@Nullable CharSequence s) {
1185 for (int i = 0; i < s.length(); i++) {
1186 if (s.charAt(i) > ' ') {
1194 * Allows to answer if given symbol is white space, tabulation or line feed.
1196 * @param c symbol to check
1197 * @return <code>true</code> if given symbol is white space, tabulation or line feed; <code>false</code> otherwise
1199 @Contract(pure = true)
1200 public static boolean isWhiteSpace(char c) {
1201 return c == '\n' || c == '\t' || c == ' ';
1205 @Contract(pure = true)
1206 public static String getThrowableText(@NotNull Throwable aThrowable) {
1207 return ExceptionUtil.getThrowableText(aThrowable);
1211 @Contract(pure = true)
1212 public static String getThrowableText(@NotNull Throwable aThrowable, @NonNls @NotNull final String stackFrameSkipPattern) {
1213 return ExceptionUtil.getThrowableText(aThrowable, stackFrameSkipPattern);
1217 @Contract(pure = true)
1218 public static String getMessage(@NotNull Throwable e) {
1219 return ExceptionUtil.getMessage(e);
1223 @Contract(pure = true)
1224 public static String repeatSymbol(final char aChar, final int count) {
1225 char[] buffer = new char[count];
1226 Arrays.fill(buffer, aChar);
1227 return StringFactory.createShared(buffer);
1231 @Contract(pure = true)
1232 public static String repeat(@NotNull String s, int count) {
1233 assert count >= 0 : count;
1234 StringBuilder sb = new StringBuilder(s.length() * count);
1235 for (int i = 0; i < count; i++) {
1238 return sb.toString();
1242 @Contract(pure = true)
1243 public static List<String> splitHonorQuotes(@NotNull String s, char separator) {
1244 final List<String> result = new ArrayList<String>();
1245 final StringBuilder builder = new StringBuilder(s.length());
1246 boolean inQuotes = false;
1247 for (int i = 0; i < s.length(); i++) {
1248 final char c = s.charAt(i);
1249 if (c == separator && !inQuotes) {
1250 if (builder.length() > 0) {
1251 result.add(builder.toString());
1252 builder.setLength(0);
1257 if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
1258 inQuotes = !inQuotes;
1263 if (builder.length() > 0) {
1264 result.add(builder.toString());
1271 @Contract(pure = true)
1272 public static List<String> split(@NotNull String s, @NotNull String separator) {
1273 return split(s, separator, true);
1276 @Contract(pure = true)
1277 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator) {
1278 return split(s, separator, true, true);
1282 @Contract(pure = true)
1283 public static List<String> split(@NotNull String s, @NotNull String separator,
1284 boolean excludeSeparator) {
1285 return split(s, separator, excludeSeparator, true);
1289 @Contract(pure = true)
1290 public static List<String> split(@NotNull String s, @NotNull String separator,
1291 boolean excludeSeparator, boolean excludeEmptyStrings) {
1292 return (List)split((CharSequence)s,separator,excludeSeparator,excludeEmptyStrings);
1295 @Contract(pure = true)
1296 public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator,
1297 boolean excludeSeparator, boolean excludeEmptyStrings) {
1298 if (separator.length() == 0) {
1299 return Collections.singletonList(s);
1301 List<CharSequence> result = new ArrayList<CharSequence>();
1304 int index = indexOf(s,separator, pos);
1305 if (index == -1) break;
1306 final int nextPos = index + separator.length();
1307 CharSequence token = s.subSequence(pos, excludeSeparator ? index : nextPos);
1308 if (token.length() != 0 || !excludeEmptyStrings) {
1313 if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
1314 result.add(s.subSequence(pos, s.length()));
1320 @Contract(pure = true)
1321 public static Iterable<String> tokenize(@NotNull String s, @NotNull String separators) {
1322 final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators);
1323 return new Iterable<String>() {
1326 public Iterator<String> iterator() {
1327 return new Iterator<String>() {
1329 public boolean hasNext() {
1330 return tokenizer.hasMoreTokens();
1334 public String next() {
1335 return tokenizer.nextToken();
1339 public void remove() {
1340 throw new UnsupportedOperationException();
1348 @Contract(pure = true)
1349 public static Iterable<String> tokenize(@NotNull final StringTokenizer tokenizer) {
1350 return new Iterable<String>() {
1353 public Iterator<String> iterator() {
1354 return new Iterator<String>() {
1356 public boolean hasNext() {
1357 return tokenizer.hasMoreTokens();
1361 public String next() {
1362 return tokenizer.nextToken();
1366 public void remove() {
1367 throw new UnsupportedOperationException();
1375 * @return list containing all words in {@code text}, or {@link ContainerUtil#emptyList()} if there are none.
1376 * The <b>word</b> here means the maximum sub-string consisting entirely of characters which are <code>Character.isJavaIdentifierPart(c)</code>.
1379 @Contract(pure = true)
1380 public static List<String> getWordsIn(@NotNull String text) {
1381 List<String> result = null;
1383 for (int i = 0; i < text.length(); i++) {
1384 char c = text.charAt(i);
1385 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1386 if (isIdentifierPart && start == -1) {
1389 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1390 if (result == null) {
1391 result = new SmartList<String>();
1393 result.add(text.substring(start, i + 1));
1395 else if (!isIdentifierPart && start != -1) {
1396 if (result == null) {
1397 result = new SmartList<String>();
1399 result.add(text.substring(start, i));
1403 if (result == null) {
1404 return ContainerUtil.emptyList();
1410 @Contract(pure = true)
1411 public static List<TextRange> getWordIndicesIn(@NotNull String text) {
1412 List<TextRange> result = new SmartList<TextRange>();
1414 for (int i = 0; i < text.length(); i++) {
1415 char c = text.charAt(i);
1416 boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1417 if (isIdentifierPart && start == -1) {
1420 if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1421 result.add(new TextRange(start, i + 1));
1423 else if (!isIdentifierPart && start != -1) {
1424 result.add(new TextRange(start, i));
1432 @Contract(pure = true)
1433 public static String join(@NotNull final String[] strings, @NotNull final String separator) {
1434 return join(strings, 0, strings.length, separator);
1438 @Contract(pure = true)
1439 public static String join(@NotNull final String[] strings, int startIndex, int endIndex, @NotNull final String separator) {
1440 final StringBuilder result = new StringBuilder();
1441 for (int i = startIndex; i < endIndex; i++) {
1442 if (i > startIndex) result.append(separator);
1443 result.append(strings[i]);
1445 return result.toString();
1449 @Contract(pure = true)
1450 public static String[] zip(@NotNull String[] strings1, @NotNull String[] strings2, String separator) {
1451 if (strings1.length != strings2.length) throw new IllegalArgumentException();
1453 String[] result = ArrayUtil.newStringArray(strings1.length);
1454 for (int i = 0; i < result.length; i++) {
1455 result[i] = strings1[i] + separator + strings2[i];
1462 @Contract(pure = true)
1463 public static String[] surround(@NotNull String[] strings1, String prefix, String suffix) {
1464 String[] result = ArrayUtil.newStringArray(strings1.length);
1465 for (int i = 0; i < result.length; i++) {
1466 result[i] = prefix + strings1[i] + suffix;
1473 @Contract(pure = true)
1474 public static <T> String join(@NotNull T[] items, @NotNull Function<T, String> f, @NotNull @NonNls String separator) {
1475 return join(Arrays.asList(items), f, separator);
1479 @Contract(pure = true)
1480 public static <T> String join(@NotNull Collection<? extends T> items,
1481 @NotNull Function<? super T, String> f,
1482 @NotNull @NonNls String separator) {
1483 if (items.isEmpty()) return "";
1484 return join((Iterable<? extends T>)items, f, separator);
1487 @Contract(pure = true)
1488 public static String join(@NotNull Iterable<?> items, @NotNull @NonNls String separator) {
1489 StringBuilder result = new StringBuilder();
1490 for (Object item : items) {
1491 result.append(item).append(separator);
1493 if (result.length() > 0) {
1494 result.setLength(result.length() - separator.length());
1496 return result.toString();
1500 @Contract(pure = true)
1501 public static <T> String join(@NotNull Iterable<? extends T> items,
1502 @NotNull Function<? super T, String> f,
1503 @NotNull @NonNls String separator) {
1504 final StringBuilder result = new StringBuilder();
1505 for (T item : items) {
1506 String string = f.fun(item);
1507 if (string != null && !string.isEmpty()) {
1508 if (result.length() != 0) result.append(separator);
1509 result.append(string);
1512 return result.toString();
1516 @Contract(pure = true)
1517 public static String join(@NotNull Collection<String> strings, @NotNull String separator) {
1518 if (strings.size() <= 1) {
1519 return notNullize(ContainerUtil.getFirstItem(strings));
1521 StringBuilder result = new StringBuilder();
1522 join(strings, separator, result);
1523 return result.toString();
1526 public static void join(@NotNull Collection<String> strings, @NotNull String separator, @NotNull StringBuilder result) {
1527 boolean isFirst = true;
1528 for (String string : strings) {
1529 if (string != null) {
1534 result.append(separator);
1536 result.append(string);
1542 @Contract(pure = true)
1543 public static String join(@NotNull final int[] strings, @NotNull final String separator) {
1544 final StringBuilder result = new StringBuilder();
1545 for (int i = 0; i < strings.length; i++) {
1546 if (i > 0) result.append(separator);
1547 result.append(strings[i]);
1549 return result.toString();
1553 @Contract(pure = true)
1554 public static String join(@Nullable final String... strings) {
1555 if (strings == null || strings.length == 0) return "";
1557 final StringBuilder builder = new StringBuilder();
1558 for (final String string : strings) {
1559 builder.append(string);
1561 return builder.toString();
1565 * Consider using {@link StringUtil#unquoteString(String)} instead.
1566 * Note: this method has an odd behavior:
1567 * Quotes are removed even if leading and trailing quotes are different or
1568 * if there is only one quote (leading or trailing).
1571 @Contract(pure = true)
1572 public static String stripQuotesAroundValue(@NotNull String text) {
1573 final int len = text.length();
1575 final int from = isQuoteAt(text, 0) ? 1 : 0;
1576 final int to = len > 1 && isQuoteAt(text, len - 1) ? len - 1 : len;
1577 if (from > 0 || to < len) {
1578 return text.substring(from, to);
1585 * Formats the specified file size as a string.
1587 * @param fileSize the size to format.
1588 * @return the size formatted as a string.
1592 @Contract(pure = true)
1593 public static String formatFileSize(long fileSize) {
1594 return formatValue(fileSize, null,
1595 new String[]{"B", "K", "M", "G", "T", "P", "E"},
1596 new long[]{1000, 1000, 1000, 1000, 1000, 1000});
1600 @Contract(pure = true)
1601 public static String formatDuration(long duration) {
1602 return formatValue(duration, " ",
1603 new String[]{"ms", "s", "m", "h", "d", "w", "mo", "yr", "c", "ml", "ep"},
1604 new long[]{1000, 60, 60, 24, 7, 4, 12, 100, 10, 10000});
1608 private static String formatValue(long value, String partSeparator, String[] units, long[] multipliers) {
1609 StringBuilder sb = new StringBuilder();
1613 for (; i < units.length; i++) {
1614 long multiplier = i < multipliers.length ? multipliers[i] : -1;
1615 if (multiplier == -1 || count < multiplier) break;
1616 remainder = count % multiplier;
1617 count /= multiplier;
1618 if (partSeparator != null && (remainder != 0 || sb.length() > 0)) {
1619 sb.insert(0, units[i]).insert(0, remainder).insert(0, partSeparator);
1622 if (partSeparator != null || remainder == 0) {
1623 sb.insert(0, units[i]).insert(0, count);
1625 else if (remainder > 0) {
1626 sb.append(String.format(Locale.US, "%.2f", count + (double)remainder / multipliers[i - 1])).append(units[i]);
1628 return sb.toString();
1632 * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1633 * Returns <code>null</code> if failed to match appropriate heuristic.
1635 * @param name english word in plural form
1636 * @return name in singular form or <code>null</code> if failed to find one.
1638 @SuppressWarnings("HardCodedStringLiteral")
1640 @Contract(pure = true)
1641 public static String unpluralize(@NotNull final String name) {
1642 if (name.endsWith("sses") || name.endsWith("shes") || name.endsWith("ches") || name.endsWith("xes")) { //?
1643 return name.substring(0, name.length() - 2);
1646 if (name.endsWith("ses")) {
1647 return name.substring(0, name.length() - 1);
1650 if (name.endsWith("ies")) {
1651 if (name.endsWith("cookies") || name.endsWith("Cookies")) {
1652 return name.substring(0, name.length() - "ookies".length()) + "ookie";
1655 return name.substring(0, name.length() - 3) + "y";
1658 if (name.endsWith("leaves") || name.endsWith("Leaves")) {
1659 return name.substring(0, name.length() - "eaves".length()) + "eaf";
1662 String result = stripEnding(name, "s");
1663 if (result != null) {
1667 if (name.endsWith("children")) {
1668 return name.substring(0, name.length() - "children".length()) + "child";
1671 if (name.endsWith("Children") && name.length() > "Children".length()) {
1672 return name.substring(0, name.length() - "Children".length()) + "Child";
1680 @Contract(pure = true)
1681 private static String stripEnding(@NotNull String name, @NotNull String ending) {
1682 if (name.endsWith(ending)) {
1683 if (name.equals(ending)) return name; // do not return empty string
1684 return name.substring(0, name.length() - 1);
1689 @Contract(pure = true)
1690 public static boolean containsAlphaCharacters(@NotNull String value) {
1691 for (int i = 0; i < value.length(); i++) {
1692 if (Character.isLetter(value.charAt(i))) return true;
1697 @Contract(pure = true)
1698 public static boolean containsAnyChar(@NotNull final String value, @NotNull final String chars) {
1699 if (chars.length() > value.length()) {
1700 return containsAnyChar(value, chars, 0, value.length());
1703 return containsAnyChar(chars, value, 0, chars.length());
1707 @Contract(pure = true)
1708 public static boolean containsAnyChar(@NotNull final String value,
1709 @NotNull final String chars,
1710 final int start, final int end) {
1711 for (int i = start; i < end; i++) {
1712 if (chars.indexOf(value.charAt(i)) >= 0) {
1720 @Contract(pure = true)
1721 public static boolean containsChar(@NotNull final String value, final char ch) {
1722 return value.indexOf(ch) >= 0;
1726 * @deprecated use #capitalize(String)
1728 @Contract(value = "null -> null; !null -> !null", pure = true)
1729 public static String firstLetterToUpperCase(@Nullable final String displayString) {
1730 if (displayString == null || displayString.isEmpty()) return displayString;
1731 char firstChar = displayString.charAt(0);
1732 char uppedFirstChar = toUpperCase(firstChar);
1734 if (uppedFirstChar == firstChar) return displayString;
1736 char[] buffer = displayString.toCharArray();
1737 buffer[0] = uppedFirstChar;
1738 return StringFactory.createShared(buffer);
1742 * Strip out all characters not accepted by given filter
1744 * @param s e.g. "/n my string "
1745 * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1746 * @return stripped string e.g. "mystring"
1749 @Contract(pure = true)
1750 public static String strip(@NotNull final String s, @NotNull final CharFilter filter) {
1751 final StringBuilder result = new StringBuilder(s.length());
1752 for (int i = 0; i < s.length(); i++) {
1753 char ch = s.charAt(i);
1754 if (filter.accept(ch)) {
1758 return result.toString();
1762 @Contract(pure = true)
1763 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern) {
1764 return findMatches(s, pattern, 1);
1768 @Contract(pure = true)
1769 public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) {
1770 List<String> result = new SmartList<String>();
1771 Matcher m = pattern.matcher(s);
1773 String group = m.group(groupIndex);
1774 if (group != null) {
1782 * Find position of the first character accepted by given filter.
1784 * @param s the string to search
1785 * @param filter search filter
1786 * @return position of the first character accepted or -1 if not found
1788 @Contract(pure = true)
1789 public static int findFirst(@NotNull final CharSequence s, @NotNull CharFilter filter) {
1790 for (int i = 0; i < s.length(); i++) {
1791 char ch = s.charAt(i);
1792 if (filter.accept(ch)) {
1800 @Contract(pure = true)
1801 public static String replaceSubstring(@NotNull String string, @NotNull TextRange range, @NotNull String replacement) {
1802 return range.replace(string, replacement);
1805 @Contract(pure = true)
1806 public static boolean startsWithWhitespace(@NotNull String text) {
1807 return !text.isEmpty() && Character.isWhitespace(text.charAt(0));
1810 @Contract(pure = true)
1811 public static boolean isChar(CharSequence seq, int index, char c) {
1812 return index >= 0 && index < seq.length() && seq.charAt(index) == c;
1815 @Contract(pure = true)
1816 public static boolean startsWith(@NotNull CharSequence text, @NotNull CharSequence prefix) {
1817 int l1 = text.length();
1818 int l2 = prefix.length();
1819 if (l1 < l2) return false;
1821 for (int i = 0; i < l2; i++) {
1822 if (text.charAt(i) != prefix.charAt(i)) return false;
1828 @Contract(pure = true)
1829 public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
1830 int l1 = text.length() - startIndex;
1831 int l2 = prefix.length();
1832 if (l1 < l2) return false;
1834 for (int i = 0; i < l2; i++) {
1835 if (text.charAt(i + startIndex) != prefix.charAt(i)) return false;
1841 @Contract(pure = true)
1842 public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
1843 int l1 = text.length();
1844 int l2 = suffix.length();
1845 if (l1 < l2) return false;
1847 for (int i = l1 - 1; i >= l1 - l2; i--) {
1848 if (text.charAt(i) != suffix.charAt(i + l2 - l1)) return false;
1855 @Contract(pure = true)
1856 public static String commonPrefix(@NotNull String s1, @NotNull String s2) {
1857 return s1.substring(0, commonPrefixLength(s1, s2));
1860 @Contract(pure = true)
1861 public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1863 int minLength = min(s1.length(), s2.length());
1864 for (i = 0; i < minLength; i++) {
1865 if (s1.charAt(i) != s2.charAt(i)) {
1873 @Contract(pure = true)
1874 public static String commonSuffix(@NotNull String s1, @NotNull String s2) {
1875 return s1.substring(s1.length() - commonSuffixLength(s1, s2));
1878 @Contract(pure = true)
1879 public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1880 int s1Length = s1.length();
1881 int s2Length = s2.length();
1882 if (s1Length == 0 || s2Length == 0) return 0;
1884 for (i = 0; i < s1Length && i < s2Length; i++) {
1885 if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
1893 * Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> interval.
1895 * @param s target char sequence to check
1896 * @param start start offset to use within the given char sequence (inclusive)
1897 * @param end end offset to use within the given char sequence (exclusive)
1898 * @param c target symbol to check
1899 * @return <code>true</code> if given symbol is contained at the target range of the given char sequence;
1900 * <code>false</code> otherwise
1902 @Contract(pure = true)
1903 public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
1904 return indexOf(s, c, start, end) >= 0;
1907 @Contract(pure = true)
1908 public static boolean containsWhitespaces(@Nullable CharSequence s) {
1909 if (s == null) return false;
1911 for (int i = 0; i < s.length(); i++) {
1912 if (Character.isWhitespace(s.charAt(i))) return true;
1917 @Contract(pure = true)
1918 public static int indexOf(@NotNull CharSequence s, char c) {
1919 return indexOf(s, c, 0, s.length());
1922 @Contract(pure = true)
1923 public static int indexOf(@NotNull CharSequence s, char c, int start) {
1924 return indexOf(s, c, start, s.length());
1927 @Contract(pure = true)
1928 public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
1929 end = min(end, s.length());
1930 for (int i = max(start, 0); i < end; i++) {
1931 if (s.charAt(i) == c) return i;
1936 @Contract(pure = true)
1937 public static boolean contains(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1938 return indexOf(sequence, infix) >= 0;
1941 @Contract(pure = true)
1942 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1943 return indexOf(sequence, infix, 0);
1946 @Contract(pure = true)
1947 public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start) {
1948 for (int i = start; i <= sequence.length() - infix.length(); i++) {
1949 if (startsWith(sequence, i, infix)) {
1956 @Contract(pure = true)
1957 public static int indexOf(@NotNull CharSequence s, char c, int start, int end, boolean caseSensitive) {
1958 end = min(end, s.length());
1959 for (int i = max(start, 0); i < end; i++) {
1960 if (charsMatch(s.charAt(i), c, !caseSensitive)) return i;
1965 @Contract(pure = true)
1966 public static int indexOf(@NotNull char[] s, char c, int start, int end, boolean caseSensitive) {
1967 end = min(end, s.length);
1968 for (int i = max(start, 0); i < end; i++) {
1969 if (charsMatch(s[i], c, !caseSensitive)) return i;
1974 @Contract(pure = true)
1975 public static int indexOfSubstringEnd(@NotNull String text, @NotNull String subString) {
1976 int i = text.indexOf(subString);
1977 if (i == -1) return -1;
1978 return i + subString.length();
1981 @Contract(pure = true)
1982 public static int indexOfAny(@NotNull final String s, @NotNull final String chars) {
1983 return indexOfAny(s, chars, 0, s.length());
1986 @Contract(pure = true)
1987 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars) {
1988 return indexOfAny(s, chars, 0, s.length());
1991 @Contract(pure = true)
1992 public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) {
1993 return indexOfAny((CharSequence)s, chars, start, end);
1996 @Contract(pure = true)
1997 public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars, final int start, int end) {
1998 end = min(end, s.length());
1999 for (int i = max(start, 0); i < end; i++) {
2000 if (containsChar(chars, s.charAt(i))) return i;
2005 @Contract(pure = true)
2006 public static int lastIndexOfAny(@NotNull CharSequence s, @NotNull final String chars) {
2007 for (int i = s.length() - 1; i >= 0; i--) {
2008 if (containsChar(chars, s.charAt(i))) return i;
2014 @Contract(pure = true)
2015 public static String substringBefore(@NotNull String text, @NotNull String subString) {
2016 int i = text.indexOf(subString);
2017 if (i == -1) return null;
2018 return text.substring(0, i);
2022 @Contract(pure = true)
2023 public static String substringAfter(@NotNull String text, @NotNull String subString) {
2024 int i = text.indexOf(subString);
2025 if (i == -1) return null;
2026 return text.substring(i + subString.length());
2030 * Allows to retrieve index of last occurrence of the given symbols at <code>[start; end)</code> sub-sequence of the given text.
2032 * @param s target text
2033 * @param c target symbol which last occurrence we want to check
2034 * @param start start offset of the target text (inclusive)
2035 * @param end end offset of the target text (exclusive)
2036 * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
2037 * <code>-1</code> otherwise
2039 @Contract(pure = true)
2040 public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
2041 return StringUtilRt.lastIndexOf(s, c, start, end);
2045 @Contract(pure = true)
2046 public static String first(@NotNull String text, final int maxLength, final boolean appendEllipsis) {
2047 return text.length() > maxLength ? text.substring(0, maxLength) + (appendEllipsis ? "..." : "") : text;
2051 @Contract(pure = true)
2052 public static CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
2053 return text.length() > length ? text.subSequence(0, length) + (appendEllipsis ? "..." : "") : text;
2057 @Contract(pure = true)
2058 public static CharSequence last(@NotNull CharSequence text, final int length, boolean prependEllipsis) {
2059 return text.length() > length ? (prependEllipsis ? "..." : "") + text.subSequence(text.length() - length, text.length()) : text;
2063 @Contract(pure = true)
2064 public static String firstLast(@NotNull String text, int length) {
2065 return text.length() > length
2066 ? text.subSequence(0, length / 2) + "..." + text.subSequence(text.length() - length / 2, text.length())
2071 @Contract(pure = true)
2072 public static String escapeChar(@NotNull final String str, final char character) {
2073 return escapeChars(str, character);
2077 @Contract(pure = true)
2078 public static String escapeChars(@NotNull final String str, final char... character) {
2079 final StringBuilder buf = new StringBuilder(str);
2080 for (char c : character) {
2083 return buf.toString();
2086 private static void escapeChar(@NotNull final StringBuilder buf, final char character) {
2088 while ((idx = indexOf(buf, character, idx)) >= 0) {
2089 buf.insert(idx, "\\");
2095 @Contract(pure = true)
2096 public static String escapeQuotes(@NotNull final String str) {
2097 return escapeChar(str, '"');
2100 public static void escapeQuotes(@NotNull final StringBuilder buf) {
2101 escapeChar(buf, '"');
2105 @Contract(pure = true)
2106 public static String escapeSlashes(@NotNull final String str) {
2107 return escapeChar(str, '/');
2111 @Contract(pure = true)
2112 public static String escapeBackSlashes(@NotNull final String str) {
2113 return escapeChar(str, '\\');
2116 public static void escapeSlashes(@NotNull final StringBuilder buf) {
2117 escapeChar(buf, '/');
2121 @Contract(pure = true)
2122 public static String unescapeSlashes(@NotNull final String str) {
2123 final StringBuilder buf = new StringBuilder(str.length());
2124 unescapeChar(buf, str, '/');
2125 return buf.toString();
2129 @Contract(pure = true)
2130 public static String unescapeBackSlashes(@NotNull final String str) {
2131 final StringBuilder buf = new StringBuilder(str.length());
2132 unescapeChar(buf, str, '\\');
2133 return buf.toString();
2137 @Contract(pure = true)
2138 public static String unescapeChar(@NotNull final String str, char unescapeChar) {
2139 final StringBuilder buf = new StringBuilder(str.length());
2140 unescapeChar(buf, str, unescapeChar);
2141 return buf.toString();
2144 private static void unescapeChar(@NotNull StringBuilder buf, @NotNull String str, char unescapeChar) {
2145 final int length = str.length();
2146 final int last = length - 1;
2147 for (int i = 0; i < length; i++) {
2148 char ch = str.charAt(i);
2149 if (ch == '\\' && i != last) {
2152 if (ch != unescapeChar) buf.append('\\');
2159 public static void quote(@NotNull final StringBuilder builder) {
2160 quote(builder, '\"');
2163 public static void quote(@NotNull final StringBuilder builder, final char quotingChar) {
2164 builder.insert(0, quotingChar);
2165 builder.append(quotingChar);
2169 @Contract(pure = true)
2170 public static String wrapWithDoubleQuote(@NotNull String str) {
2171 return '\"' + str + "\"";
2174 @NonNls private static final String[] REPLACES_REFS = {"<", ">", "&", "'", """};
2175 @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""};
2177 @Contract(value = "null -> null; !null -> !null",pure = true)
2178 public static String unescapeXml(@Nullable final String text) {
2179 if (text == null) return null;
2180 return replace(text, REPLACES_REFS, REPLACES_DISP);
2183 @Contract(value = "null -> null; !null -> !null",pure = true)
2184 public static String escapeXml(@Nullable final String text) {
2185 if (text == null) return null;
2186 return replace(text, REPLACES_DISP, REPLACES_REFS);
2189 public static String removeHtmlTags (@Nullable String htmlString) {
2190 if (isEmpty(htmlString)) return htmlString;
2192 html2TextParser.parse(new StringReader(htmlString));
2194 catch (IOException e) {
2197 return html2TextParser.getText();
2200 @NonNls private static final String[] MN_QUOTED = {"&&", "__"};
2201 @NonNls private static final String[] MN_CHARS = {"&", "_"};
2203 @Contract(value = "null -> null; !null -> !null", pure = true)
2204 public static String escapeMnemonics(@Nullable String text) {
2205 if (text == null) return null;
2206 return replace(text, MN_CHARS, MN_QUOTED);
2210 @Contract(pure = true)
2211 public static String htmlEmphasize(@NotNull String text) {
2212 return "<b><code>" + escapeXml(text) + "</code></b>";
2217 @Contract(pure = true)
2218 public static String escapeToRegexp(@NotNull String text) {
2219 final StringBuilder result = new StringBuilder(text.length());
2220 return escapeToRegexp(text, result).toString();
2224 public static StringBuilder escapeToRegexp(@NotNull CharSequence text, @NotNull StringBuilder builder) {
2225 for (int i = 0; i < text.length(); i++) {
2226 final char c = text.charAt(i);
2227 if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_') {
2230 else if (c == '\n') {
2231 builder.append("\\n");
2234 builder.append('\\').append(c);
2241 @Contract(pure = true)
2242 public static boolean isEscapedBackslash(@NotNull char[] chars, int startOffset, int backslashOffset) {
2243 if (chars[backslashOffset] != '\\') {
2246 boolean escaped = false;
2247 for (int i = startOffset; i < backslashOffset; i++) {
2248 if (chars[i] == '\\') {
2258 @Contract(pure = true)
2259 public static boolean isEscapedBackslash(@NotNull CharSequence text, int startOffset, int backslashOffset) {
2260 if (text.charAt(backslashOffset) != '\\') {
2263 boolean escaped = false;
2264 for (int i = startOffset; i < backslashOffset; i++) {
2265 if (text.charAt(i) == '\\') {
2276 @Contract(pure = true)
2277 public static String replace(@NotNull String text, @NotNull String[] from, @NotNull String[] to) {
2278 final StringBuilder result = new StringBuilder(text.length());
2280 for (int i = 0; i < text.length(); i++) {
2281 for (int j = 0; j < from.length; j += 1) {
2282 String toReplace = from[j];
2283 String replaceWith = to[j];
2285 final int len = toReplace.length();
2286 if (text.regionMatches(i, toReplace, 0, len)) {
2287 result.append(replaceWith);
2292 result.append(text.charAt(i));
2294 return result.toString();
2298 @Contract(pure = true)
2299 public static String[] filterEmptyStrings(@NotNull String[] strings) {
2301 for (String string : strings) {
2302 if (string == null || string.isEmpty()) emptyCount++;
2304 if (emptyCount == 0) return strings;
2306 String[] result = ArrayUtil.newStringArray(strings.length - emptyCount);
2308 for (String string : strings) {
2309 if (string == null || string.isEmpty()) continue;
2310 result[count++] = string;
2316 @Contract(pure = true)
2317 public static int countNewLines(@NotNull CharSequence text) {
2318 return countChars(text, '\n');
2321 @Contract(pure = true)
2322 public static int countChars(@NotNull CharSequence text, char c) {
2323 return countChars(text, c, 0, false);
2326 @Contract(pure = true)
2327 public static int countChars(@NotNull CharSequence text, char c, int offset, boolean continuous) {
2329 for (int i = offset; i < text.length(); ++i) {
2330 if (text.charAt(i) == c) {
2333 else if (continuous) {
2341 @Contract(pure = true)
2342 public static String capitalsOnly(@NotNull String s) {
2343 StringBuilder b = new StringBuilder();
2344 for (int i = 0; i < s.length(); i++) {
2345 if (Character.isUpperCase(s.charAt(i))) {
2346 b.append(s.charAt(i));
2350 return b.toString();
2354 * @param args Strings to join.
2355 * @return {@code null} if any of given Strings is {@code null}.
2358 @Contract(pure = true)
2359 public static String joinOrNull(@NotNull String... args) {
2360 StringBuilder r = new StringBuilder();
2361 for (String arg : args) {
2362 if (arg == null) return null;
2365 return r.toString();
2369 @Contract(pure = true)
2370 public static String getPropertyName(@NonNls @NotNull String methodName) {
2371 if (methodName.startsWith("get")) {
2372 return Introspector.decapitalize(methodName.substring(3));
2374 if (methodName.startsWith("is")) {
2375 return Introspector.decapitalize(methodName.substring(2));
2377 if (methodName.startsWith("set")) {
2378 return Introspector.decapitalize(methodName.substring(3));
2383 @Contract(pure = true)
2384 public static boolean isJavaIdentifierStart(char c) {
2385 return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierStart(c);
2388 @Contract(pure = true)
2389 public static boolean isJavaIdentifierPart(char c) {
2390 return c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierPart(c);
2393 @Contract(pure = true)
2394 public static boolean isJavaIdentifier(@NotNull String text) {
2395 int len = text.length();
2396 if (len == 0) return false;
2398 if (!isJavaIdentifierStart(text.charAt(0))) return false;
2400 for (int i = 1; i < len; i++) {
2401 if (!isJavaIdentifierPart(text.charAt(i))) return false;
2408 * Escape property name or key in property file. Unicode characters are escaped as well.
2410 * @param input an input to escape
2411 * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case.
2412 * @return an escaped string
2415 @Contract(pure = true)
2416 public static String escapeProperty(@NotNull String input, final boolean isKey) {
2417 final StringBuilder escaped = new StringBuilder(input.length());
2418 for (int i = 0; i < input.length(); i++) {
2419 final char ch = input.charAt(i);
2422 if (isKey && i == 0) {
2423 // only the leading space has to be escaped
2424 escaped.append('\\');
2426 escaped.append(' ');
2429 escaped.append("\\t");
2432 escaped.append("\\r");
2435 escaped.append("\\n");
2438 escaped.append("\\f");
2445 escaped.append('\\');
2449 if (20 < ch && ch < 0x7F) {
2453 escaped.append("\\u");
2454 escaped.append(Character.forDigit((ch >> 12) & 0xF, 16));
2455 escaped.append(Character.forDigit((ch >> 8) & 0xF, 16));
2456 escaped.append(Character.forDigit((ch >> 4) & 0xF, 16));
2457 escaped.append(Character.forDigit((ch) & 0xF, 16));
2462 return escaped.toString();
2465 @Contract(pure = true)
2466 public static String getQualifiedName(@Nullable String packageName, String className) {
2467 if (packageName == null || packageName.isEmpty()) {
2470 return packageName + '.' + className;
2473 @Contract(pure = true)
2474 public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
2475 // todo duplicates com.intellij.util.text.VersionComparatorUtil.compare
2476 // todo please refactor next time you make changes here
2477 if (v1 == null && v2 == null) {
2487 String[] part1 = v1.split("[\\.\\_\\-]");
2488 String[] part2 = v2.split("[\\.\\_\\-]");
2491 for (; idx < part1.length && idx < part2.length; idx++) {
2492 String p1 = part1[idx];
2493 String p2 = part2[idx];
2496 if (p1.matches("\\d+") && p2.matches("\\d+")) {
2497 cmp = new Integer(p1).compareTo(new Integer(p2));
2500 cmp = part1[idx].compareTo(part2[idx]);
2502 if (cmp != 0) return cmp;
2505 if (part1.length == part2.length) {
2509 boolean left = part1.length > idx;
2510 String[] parts = left ? part1 : part2;
2512 for (; idx < parts.length; idx++) {
2513 String p = parts[idx];
2515 if (p.matches("\\d+")) {
2516 cmp = new Integer(p).compareTo(0);
2521 if (cmp != 0) return left ? cmp : -cmp;
2527 @Contract(pure = true)
2528 public static int getOccurrenceCount(@NotNull String text, final char c) {
2531 while (i < text.length()) {
2532 i = text.indexOf(c, i);
2544 @Contract(pure = true)
2545 public static int getOccurrenceCount(@NotNull String text, @NotNull String s) {
2548 while (i < text.length()) {
2549 i = text.indexOf(s, i);
2562 @Contract(pure = true)
2563 public static String fixVariableNameDerivedFromPropertyName(@NotNull String name) {
2564 if (isEmptyOrSpaces(name)) return name;
2565 char c = name.charAt(0);
2567 return "an" + Character.toUpperCase(c) + name.substring(1);
2569 return "a" + Character.toUpperCase(c) + name.substring(1);
2573 @Contract(pure = true)
2574 public static String sanitizeJavaIdentifier(@NotNull String name) {
2575 final StringBuilder result = new StringBuilder(name.length());
2577 for (int i = 0; i < name.length(); i++) {
2578 final char ch = name.charAt(i);
2579 if (Character.isJavaIdentifierPart(ch)) {
2580 if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
2587 return result.toString();
2590 public static void assertValidSeparators(@NotNull CharSequence s) {
2591 char[] chars = CharArrayUtil.fromSequenceWithoutCopying(s);
2592 int slashRIndex = -1;
2594 if (chars != null) {
2595 for (int i = 0, len = s.length(); i < len; ++i) {
2596 if (chars[i] == '\r') {
2603 for (int i = 0, len = s.length(); i < len; i++) {
2604 if (s.charAt(i) == '\r') {
2611 if (slashRIndex != -1) {
2613 String.valueOf(last(s.subSequence(0, slashRIndex), 10, true)) + first(s.subSequence(slashRIndex, s.length()), 10, true);
2614 context = escapeStringCharacters(context);
2615 LOG.error("Wrong line separators: '" + context + "' at offset " + slashRIndex);
2620 @Contract(pure = true)
2621 public static String tail(@NotNull String s, final int idx) {
2622 return idx >= s.length() ? "" : s.substring(idx, s.length());
2626 * Splits string by lines.
2628 * @param string String to split
2629 * @return array of strings
2632 @Contract(pure = true)
2633 public static String[] splitByLines(@NotNull String string) {
2634 return splitByLines(string, true);
2638 * Splits string by lines. If several line separators are in a row corresponding empty lines
2639 * are also added to result if {@code excludeEmptyStrings} is {@code false}.
2641 * @param string String to split
2642 * @return array of strings
2645 @Contract(pure = true)
2646 public static String[] splitByLines(@NotNull String string, boolean excludeEmptyStrings) {
2647 return (excludeEmptyStrings ? EOL_SPLIT_PATTERN : EOL_SPLIT_PATTERN_WITH_EMPTY).split(string);
2651 @Contract(pure = true)
2652 public static String[] splitByLinesDontTrim(@NotNull String string) {
2653 return EOL_SPLIT_DONT_TRIM_PATTERN.split(string);
2657 * Splits string by lines, keeping all line separators at the line ends and in the empty lines.
2658 * <br> E.g. splitting text
2667 * will return the following array: foo\r\n, \n, bar\n, \r\n, baz\r, \r
2671 @Contract(pure = true)
2672 public static String[] splitByLinesKeepSeparators(@NotNull String string) {
2673 return EOL_SPLIT_KEEP_SEPARATORS.split(string);
2677 @Contract(pure = true)
2678 public static List<Pair<String, Integer>> getWordsWithOffset(@NotNull String s) {
2679 List<Pair<String, Integer>> res = ContainerUtil.newArrayList();
2681 StringBuilder name = new StringBuilder();
2683 for (int i = 0; i < s.length(); i++) {
2684 if (Character.isWhitespace(s.charAt(i))) {
2685 if (name.length() > 0) {
2686 res.add(Pair.create(name.toString(), startInd));
2692 if (startInd == -1) {
2695 name.append(s.charAt(i));
2702 * Implementation of <a href="http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html"/>
2703 * "Sorting for Humans: Natural Sort Order"</a>
2705 @Contract(pure = true)
2706 public static int naturalCompare(@Nullable String string1, @Nullable String string2) {
2707 return naturalCompare(string1, string2, false);
2710 @Contract(pure = true)
2711 private static int naturalCompare(@Nullable String string1, @Nullable String string2, boolean caseSensitive) {
2712 //noinspection StringEquality
2713 if (string1 == string2) {
2716 if (string1 == null) {
2719 if (string2 == null) {
2723 final int string1Length = string1.length();
2724 final int string2Length = string2.length();
2727 for (; i < string1Length && j < string2Length; i++, j++) {
2728 char ch1 = string1.charAt(i);
2729 char ch2 = string2.charAt(j);
2730 if ((isDecimalDigit(ch1) || ch1 == ' ') && (isDecimalDigit(ch2) || ch2 == ' ')) {
2732 while (ch1 == ' ' || ch1 == '0') { // skip leading spaces and zeros
2734 if (startNum1 >= string1Length) break;
2735 ch1 = string1.charAt(startNum1);
2738 while (ch2 == ' ' || ch2 == '0') { // skip leading spaces and zeros
2740 if (startNum2 >= string2Length) break;
2741 ch2 = string2.charAt(startNum2);
2745 // find end index of number
2746 while (i < string1Length && isDecimalDigit(string1.charAt(i))) i++;
2747 while (j < string2Length && isDecimalDigit(string2.charAt(j))) j++;
2748 final int lengthDiff = (i - startNum1) - (j - startNum2);
2749 if (lengthDiff != 0) {
2750 // numbers with more digits are always greater than shorter numbers
2753 for (; startNum1 < i; startNum1++, startNum2++) {
2754 // compare numbers with equal digit count
2755 final int diff = string1.charAt(startNum1) - string2.charAt(startNum2);
2764 if (caseSensitive) {
2768 // similar logic to charsMatch() below
2770 final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
2772 final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
2781 // After the loop the end of one of the strings might not have been reached, if the other
2782 // string ends with a number and the strings are equal until the end of that number. When
2783 // there are more characters in the string, then it is greater.
2784 if (i < string1Length) {
2787 if (j < string2Length) {
2790 if (!caseSensitive && string1Length == string2Length) {
2791 // do case sensitive compare if case insensitive strings are equal
2792 return naturalCompare(string1, string2, true);
2794 return string1Length - string2Length;
2797 @Contract(pure = true)
2798 public static boolean isDecimalDigit(char c) {
2799 return c >= '0' && c <= '9';
2802 @Contract(pure = true)
2803 public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) {
2804 //noinspection StringEquality
2805 if (s1 == s2) return 0;
2806 if (s1 == null) return -1;
2807 if (s2 == null) return 1;
2808 return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
2811 @Contract(pure = true)
2812 public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
2813 final int compare = compare(s1, s2, ignoreCase);
2814 return compare != 0 ? compare : compare(t1, t2, ignoreCase);
2817 @Contract(pure = true)
2818 public static int hashCode(@NotNull CharSequence s) {
2819 return stringHashCode(s);
2822 @Contract(pure = true)
2823 public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2824 if (s1 == null ^ s2 == null) {
2832 if (s1.length() != s2.length()) {
2835 for (int i = 0; i < s1.length(); i++) {
2836 if (s1.charAt(i) != s2.charAt(i)) {
2843 @Contract(pure = true)
2844 public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2845 if (s1 == null ^ s2 == null) {
2853 if (s1.length() != s2.length()) {
2856 for (int i = 0; i < s1.length(); i++) {
2857 if (!charsMatch(s1.charAt(i), s2.charAt(i), true)) {
2864 @Contract(pure = true)
2865 public static boolean equalsIgnoreWhitespaces(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2866 if (s1 == null ^ s2 == null) {
2874 int len1 = s1.length();
2875 int len2 = s2.length();
2879 while (index1 < len1 && index2 < len2) {
2880 if (s1.charAt(index1) == s2.charAt(index2)) {
2886 boolean skipped = false;
2887 while (index1 != len1 && isWhiteSpace(s1.charAt(index1))) {
2891 while (index2 != len2 && isWhiteSpace(s2.charAt(index2))) {
2896 if (!skipped) return false;
2899 for (; index1 != len1; index1++) {
2900 if (!isWhiteSpace(s1.charAt(index1))) return false;
2902 for (; index2 != len2; index2++) {
2903 if (!isWhiteSpace(s2.charAt(index2))) return false;
2909 @Contract(pure = true)
2910 public static boolean equalsTrimWhitespaces(@NotNull CharSequence s1, @NotNull CharSequence s2) {
2912 int end1 = s1.length();
2914 int end2 = s2.length();
2916 while (start1 < end1) {
2917 char c = s1.charAt(start1);
2918 if (!isWhiteSpace(c)) break;
2922 while (start1 < end1) {
2923 char c = s1.charAt(end1 - 1);
2924 if (!isWhiteSpace(c)) break;
2928 while (start2 < end2) {
2929 char c = s2.charAt(start2);
2930 if (!isWhiteSpace(c)) break;
2934 while (start2 < end2) {
2935 char c = s2.charAt(end2 - 1);
2936 if (!isWhiteSpace(c)) break;
2940 CharSequence ts1 = new CharSequenceSubSequence(s1, start1, end1);
2941 CharSequence ts2 = new CharSequenceSubSequence(s2, start2, end2);
2943 return equals(ts1, ts2);
2946 @Contract(pure = true)
2947 public static int compare(char c1, char c2, boolean ignoreCase) {
2948 // duplicating String.equalsIgnoreCase logic
2950 if (d == 0 || !ignoreCase) {
2953 // If characters don't match but case may be ignored,
2954 // try converting both characters to uppercase.
2955 // If the results match, then the comparison scan should
2957 char u1 = StringUtilRt.toUpperCase(c1);
2958 char u2 = StringUtilRt.toUpperCase(c2);
2961 // Unfortunately, conversion to uppercase does not work properly
2962 // for the Georgian alphabet, which has strange rules about case
2963 // conversion. So we need to make one last check before
2965 d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
2970 @Contract(pure = true)
2971 public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
2972 return compare(c1, c2, ignoreCase) == 0;
2976 @Contract(pure = true)
2977 public static String formatLinks(@NotNull String message) {
2978 Pattern linkPattern = Pattern.compile("http://[a-zA-Z0-9\\./\\-\\+]+");
2979 StringBuffer result = new StringBuffer();
2980 Matcher m = linkPattern.matcher(message);
2982 m.appendReplacement(result, "<a href=\"" + m.group() + "\">" + m.group() + "</a>");
2984 m.appendTail(result);
2985 return result.toString();
2988 @Contract(pure = true)
2989 public static boolean isHexDigit(char c) {
2990 return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
2993 @Contract(pure = true)
2994 public static boolean isOctalDigit(char c) {
2995 return '0' <= c && c <= '7';
2999 @Contract(pure = true)
3000 public static String shortenTextWithEllipsis(@NotNull final String text, final int maxLength, final int suffixLength) {
3001 return shortenTextWithEllipsis(text, maxLength, suffixLength, false);