efff439b12c254ce2dc860271ab906a40c12cc50
[idea/community.git] / platform / util / src / com / intellij / openapi / util / text / StringUtil.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.util.text;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.ProcessCanceledException;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.util.TextRange;
22 import com.intellij.util.*;
23 import com.intellij.util.containers.ContainerUtil;
24 import com.intellij.util.text.CharArrayCharSequence;
25 import com.intellij.util.text.CharArrayUtil;
26 import com.intellij.util.text.CharSequenceSubSequence;
27 import com.intellij.util.text.StringFactory;
28 import org.jetbrains.annotations.Contract;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.beans.Introspector;
34 import java.io.IOException;
35 import java.util.*;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 //TeamCity inherits StringUtil: do not add private constructors!!!
40 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "MethodOverridesStaticMethodOfSuperclass"})
41 public class StringUtil extends StringUtilRt {
42   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.text.StringUtil");
43
44   @NonNls private static final String VOWELS = "aeiouy";
45   @NonNls private static final Pattern EOL_SPLIT_KEEP_SEPARATORS = Pattern.compile("(?<=(\r\n|\n))|(?<=\r)(?=[^\n])");
46   @NonNls private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r|\n|\r\n)+ *");
47   @NonNls private static final Pattern EOL_SPLIT_PATTERN_WITH_EMPTY = Pattern.compile(" *(\r|\n|\r\n) *");
48   @NonNls private static final Pattern EOL_SPLIT_DONT_TRIM_PATTERN = Pattern.compile("(\r|\n|\r\n)+");
49
50   public static final NotNullFunction<String, String> QUOTER = new NotNullFunction<String, String>() {
51     @Override
52     @NotNull
53     public String fun(String s) {
54       return "\"" + s + "\"";
55     }
56   };
57
58   public static final NotNullFunction<String, String> SINGLE_QUOTER = new NotNullFunction<String, String>() {
59     @Override
60     @NotNull
61     public String fun(String s) {
62       return "'" + s + "'";
63     }
64   };
65
66   @NotNull
67   @Contract(pure = true)
68   public static List<String> getWordsInStringLongestFirst(@NotNull String find) {
69     List<String> words = getWordsIn(find);
70     // hope long words are rare
71     Collections.sort(words, new Comparator<String>() {
72       @Override
73       public int compare(@NotNull final String o1, @NotNull final String o2) {
74         return o2.length() - o1.length();
75       }
76     });
77     return words;
78   }
79
80   @NotNull
81   @Contract(pure = true)
82   public static String escapePattern(@NotNull final String text) {
83     return replace(replace(text, "'", "''"), "{", "'{'");
84   }
85
86   @NotNull
87   @Contract(pure = true)
88   public static <T> Function<T, String> createToStringFunction(@NotNull Class<T> cls) {
89     return new Function<T, String>() {
90       @Override
91       public String fun(@NotNull T o) {
92         return o.toString();
93       }
94     };
95   }
96
97   @NotNull
98   public static Function<String, String> TRIMMER = new Function<String, String>() {
99     @Nullable
100     @Override
101     public String fun(@Nullable String s) {
102       return trim(s);
103     }
104   };
105
106   @NotNull
107   @Contract(pure = true)
108   public static String replace(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
109     return replace(text, oldS, newS, false);
110   }
111
112   @NotNull
113   @Contract(pure = true)
114   public static String replaceIgnoreCase(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @NotNull String newS) {
115     return replace(text, oldS, newS, true);
116   }
117
118   public static void replaceChar(@NotNull char[] buffer, char oldChar, char newChar, int start, int end) {
119     for (int i = start; i < end; i++) {
120       char c = buffer[i];
121       if (c == oldChar) {
122         buffer[i] = newChar;
123       }
124     }
125   }
126
127   @NotNull
128   @Contract(pure = true)
129   public static String replaceChar(@NotNull String buffer, char oldChar, char newChar) {
130     StringBuilder newBuffer = null;
131     for (int i = 0; i < buffer.length(); i++) {
132       char c = buffer.charAt(i);
133       if (c == oldChar) {
134         if (newBuffer == null) {
135           newBuffer = new StringBuilder(buffer.length());
136           newBuffer.append(buffer, 0, i);
137         }
138
139         newBuffer.append(newChar);
140       }
141       else if (newBuffer != null) {
142         newBuffer.append(c);
143       }
144     }
145     return newBuffer == null ? buffer : newBuffer.toString();
146   }
147
148   @Contract(pure = true)
149   public static String replace(@NonNls @NotNull final String text, @NonNls @NotNull final String oldS, @NonNls @NotNull final String newS, final boolean ignoreCase) {
150     if (text.length() < oldS.length()) return text;
151
152     StringBuilder newText = null;
153     int i = 0;
154
155     while (i < text.length()) {
156       final int index = ignoreCase? indexOfIgnoreCase(text, oldS, i) : text.indexOf(oldS, i);
157       if (index < 0) {
158         if (i == 0) {
159           return text;
160         }
161
162         newText.append(text, i, text.length());
163         break;
164       }
165       else {
166         if (newText == null) {
167           if (text.length() == oldS.length()) {
168             return newS;
169           }
170           newText = new StringBuilder(text.length() - i);
171         }
172
173         newText.append(text, i, index);
174         newText.append(newS);
175         i = index + oldS.length();
176       }
177     }
178     return newText != null ? newText.toString() : "";
179   }
180
181   /**
182    * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
183    */
184   @Contract(pure = true)
185   public static int indexOfIgnoreCase(@NotNull String where, @NotNull String what, int fromIndex) {
186     int targetCount = what.length();
187     int sourceCount = where.length();
188
189     if (fromIndex >= sourceCount) {
190       return targetCount == 0 ? sourceCount : -1;
191     }
192
193     if (fromIndex < 0) {
194       fromIndex = 0;
195     }
196
197     if (targetCount == 0) {
198       return fromIndex;
199     }
200
201     char first = what.charAt(0);
202     int max = sourceCount - targetCount;
203
204     for (int i = fromIndex; i <= max; i++) {
205       /* Look for first character. */
206       if (!charsEqualIgnoreCase(where.charAt(i), first)) {
207         while (++i <= max && !charsEqualIgnoreCase(where.charAt(i), first)) ;
208       }
209
210       /* Found first character, now look at the rest of v2 */
211       if (i <= max) {
212         int j = i + 1;
213         int end = j + targetCount - 1;
214         for (int k = 1; j < end && charsEqualIgnoreCase(where.charAt(j), what.charAt(k)); j++, k++) ;
215
216         if (j == end) {
217           /* Found whole string. */
218           return i;
219         }
220       }
221     }
222
223     return -1;
224   }
225
226   @Contract(pure = true)
227   public static int indexOfIgnoreCase(@NotNull String where, char what, int fromIndex) {
228     int sourceCount = where.length();
229
230     if (fromIndex >= sourceCount) {
231       return -1;
232     }
233
234     if (fromIndex < 0) {
235       fromIndex = 0;
236     }
237
238     for (int i = fromIndex; i < sourceCount; i++) {
239       if (charsEqualIgnoreCase(where.charAt(i), what)) {
240         return i;
241       }
242     }
243
244     return -1;
245   }
246
247   @Contract(pure = true)
248   public static boolean containsIgnoreCase(@NotNull String where, @NotNull String what) {
249     return indexOfIgnoreCase(where, what, 0) >= 0;
250   }
251
252   @Contract(pure = true)
253   public static boolean endsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String suffix) {
254     return StringUtilRt.endsWithIgnoreCase(str, suffix);
255   }
256
257   @Contract(pure = true)
258   public static boolean startsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String prefix) {
259     return StringUtilRt.startsWithIgnoreCase(str, prefix);
260   }
261
262   @Contract(pure = true)
263   @NotNull
264   public static String stripHtml(@NotNull String html, boolean convertBreaks) {
265     if (convertBreaks) {
266       html = html.replaceAll("<br/?>", "\n\n");
267     }
268
269     return html.replaceAll("<(.|\n)*?>", "");
270   }
271
272   @Contract(value = "null -> null; !null -> !null", pure = true)
273   public static String toLowerCase(@Nullable final String str) {
274     //noinspection ConstantConditions
275     return str == null ? null : str.toLowerCase();
276   }
277
278   @NotNull
279   @Contract(pure = true)
280   public static String getPackageName(@NotNull String fqName) {
281     return getPackageName(fqName, '.');
282   }
283
284   @NotNull
285   @Contract(pure = true)
286   public static String getPackageName(@NotNull String fqName, char separator) {
287     int lastPointIdx = fqName.lastIndexOf(separator);
288     if (lastPointIdx >= 0) {
289       return fqName.substring(0, lastPointIdx);
290     }
291     return "";
292   }
293
294   @Contract(pure = true)
295   public static int getLineBreakCount(@NotNull CharSequence text) {
296     int count = 0;
297     for (int i = 0; i < text.length(); i++) {
298       char c = text.charAt(i);
299       if (c == '\n') {
300         count++;
301       }
302       else if (c == '\r') {
303         if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
304           //noinspection AssignmentToForLoopParameter
305           i++;
306           count++;
307         }
308         else {
309           count++;
310         }
311       }
312     }
313     return count;
314   }
315
316   @Contract(pure = true)
317   public static boolean containsLineBreak(@NotNull CharSequence text) {
318     for (int i = 0; i < text.length(); i++) {
319       char c = text.charAt(i);
320       if (isLineBreak(c)) return true;
321     }
322     return false;
323   }
324
325   @Contract(pure = true)
326   public static boolean isLineBreak(char c) {
327     return c == '\n' || c == '\r';
328   }
329
330   @NotNull
331   @Contract(pure = true)
332   public static String escapeLineBreak(@NotNull String text) {
333     StringBuilder buffer = new StringBuilder(text.length());
334     for (int i = 0; i < text.length(); i++) {
335       char c = text.charAt(i);
336       switch (c) {
337         case '\n':
338           buffer.append("\\n");
339           break;
340         case '\r':
341           buffer.append("\\r");
342           break;
343         default:
344           buffer.append(c);
345       }
346     }
347     return buffer.toString();
348   }
349
350   @Contract(pure = true)
351   public static boolean endsWithLineBreak(@NotNull CharSequence text) {
352     int len = text.length();
353     return len > 0 && isLineBreak(text.charAt(len - 1));
354   }
355
356   @Contract(pure = true)
357   public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
358     int curLine = 0;
359     int offset = 0;
360     while (line != curLine) {
361       if (offset == text.length()) return -1;
362       char c = text.charAt(offset);
363       if (c == '\n') {
364         curLine++;
365       }
366       else if (c == '\r') {
367         curLine++;
368         if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
369           offset++;
370         }
371       }
372       offset++;
373     }
374     return offset + col;
375   }
376
377   @Contract(pure = true)
378   public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
379     int curLine = 0;
380     int curOffset = 0;
381     while (curOffset < offset) {
382       if (curOffset == text.length()) return -1;
383       char c = text.charAt(curOffset);
384       if (c == '\n') {
385         curLine++;
386       }
387       else if (c == '\r') {
388         curLine++;
389         if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
390           curOffset++;
391         }
392       }
393       curOffset++;
394     }
395     return curLine;
396   }
397
398   /**
399    * Classic dynamic programming algorithm for string differences.
400    */
401   @Contract(pure = true)
402   public static int difference(@NotNull String s1, @NotNull String s2) {
403     int[][] a = new int[s1.length()][s2.length()];
404
405     for (int i = 0; i < s1.length(); i++) {
406       a[i][0] = i;
407     }
408
409     for (int j = 0; j < s2.length(); j++) {
410       a[0][j] = j;
411     }
412
413     for (int i = 1; i < s1.length(); i++) {
414       for (int j = 1; j < s2.length(); j++) {
415
416         a[i][j] = Math.min(Math.min(a[i - 1][j - 1] + (s1.charAt(i) == s2.charAt(j) ? 0 : 1), a[i - 1][j] + 1), a[i][j - 1] + 1);
417       }
418     }
419
420     return a[s1.length() - 1][s2.length() - 1];
421   }
422
423   @NotNull
424   @Contract(pure = true)
425   public static String wordsToBeginFromUpperCase(@NotNull String s) {
426     return fixCapitalization(s, ourPrepositions, true);
427   }
428
429   @NotNull
430   @Contract(pure = true)
431   public static String wordsToBeginFromLowerCase(@NotNull String s) {
432     return fixCapitalization(s, ourPrepositions, false);
433   }
434
435   @NotNull
436   @Contract(pure = true)
437   public static String toTitleCase(@NotNull String s) {
438     return fixCapitalization(s, ArrayUtil.EMPTY_STRING_ARRAY, true);
439   }
440
441   @NotNull
442   private static String fixCapitalization(@NotNull String s, @NotNull String[] prepositions, boolean title) {
443     StringBuilder buffer = null;
444     for (int i = 0; i < s.length(); i++) {
445       char prevChar = i == 0 ? ' ' : s.charAt(i - 1);
446       char currChar = s.charAt(i);
447       if (!Character.isLetterOrDigit(prevChar) && prevChar != '\'') {
448         if (Character.isLetterOrDigit(currChar)) {
449           if (title || Character.isUpperCase(currChar)) {
450             int j = i;
451             for (; j < s.length(); j++) {
452               if (!Character.isLetterOrDigit(s.charAt(j))) {
453                 break;
454               }
455             }
456             if (!isPreposition(s, i, j - 1, prepositions)) {
457               if (buffer == null) {
458                 buffer = new StringBuilder(s);
459               }
460               buffer.setCharAt(i, title ? toUpperCase(currChar) : toLowerCase(currChar));
461             }
462           }
463         }
464       }
465     }
466     if (buffer == null) {
467       return s;
468     }
469     else {
470       return buffer.toString();
471     }
472   }
473
474   @NonNls private static final String[] ourPrepositions = {
475     "a", "an", "and", "as", "at", "but", "by", "down", "for", "from", "if", "in", "into", "not", "of", "on", "onto", "or", "out", "over",
476     "per", "nor", "the", "to", "up", "upon", "via", "with"
477   };
478
479   @Contract(pure = true)
480   public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar) {
481     return isPreposition(s, firstChar, lastChar, ourPrepositions);
482   }
483
484   @Contract(pure = true)
485   public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar, @NotNull String[] prepositions) {
486     for (String preposition : prepositions) {
487       boolean found = false;
488       if (lastChar - firstChar + 1 == preposition.length()) {
489         found = true;
490         for (int j = 0; j < preposition.length(); j++) {
491           if (!(toLowerCase(s.charAt(firstChar + j)) == preposition.charAt(j))) {
492             found = false;
493           }
494         }
495       }
496       if (found) {
497         return true;
498       }
499     }
500     return false;
501   }
502
503   @NotNull
504   @Contract(pure = true)
505   public static NotNullFunction<String, String> escaper(final boolean escapeSlash, @Nullable final String additionalChars) {
506     return new NotNullFunction<String, String>() {
507       @NotNull
508       @Override
509       public String fun(@NotNull String dom) {
510         final StringBuilder builder = new StringBuilder(dom.length());
511         escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder);
512         return builder.toString();
513       }
514     };
515   }
516
517
518   public static void escapeStringCharacters(int length, @NotNull String str, @NotNull @NonNls StringBuilder buffer) {
519     escapeStringCharacters(length, str, "\"", buffer);
520   }
521
522   @NotNull
523   public static StringBuilder escapeStringCharacters(int length,
524                                                      @NotNull String str,
525                                                      @Nullable String additionalChars,
526                                                      @NotNull @NonNls StringBuilder buffer) {
527     return escapeStringCharacters(length, str, additionalChars, true, buffer);
528   }
529
530   @NotNull
531   public static StringBuilder escapeStringCharacters(int length,
532                                                      @NotNull String str,
533                                                      @Nullable String additionalChars,
534                                                      boolean escapeSlash,
535                                                      @NotNull @NonNls StringBuilder buffer) {
536     char prev = 0;
537     for (int idx = 0; idx < length; idx++) {
538       char ch = str.charAt(idx);
539       switch (ch) {
540         case '\b':
541           buffer.append("\\b");
542           break;
543
544         case '\t':
545           buffer.append("\\t");
546           break;
547
548         case '\n':
549           buffer.append("\\n");
550           break;
551
552         case '\f':
553           buffer.append("\\f");
554           break;
555
556         case '\r':
557           buffer.append("\\r");
558           break;
559
560         default:
561           if (escapeSlash && ch == '\\') {
562             buffer.append("\\\\");
563           }
564           else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
565             buffer.append("\\").append(ch);
566           }
567           else if (!isPrintableUnicode(ch)) {
568             String hexCode = StringUtilRt.toUpperCase(Integer.toHexString(ch));
569             buffer.append("\\u");
570             int paddingCount = 4 - hexCode.length();
571             while (paddingCount-- > 0) {
572               buffer.append(0);
573             }
574             buffer.append(hexCode);
575           }
576           else {
577             buffer.append(ch);
578           }
579       }
580       prev = ch;
581     }
582     return buffer;
583   }
584
585   @Contract(pure = true)
586   private static boolean isPrintableUnicode(char c) {
587     int t = Character.getType(c);
588     return t != Character.UNASSIGNED && t != Character.LINE_SEPARATOR && t != Character.PARAGRAPH_SEPARATOR &&
589            t != Character.CONTROL && t != Character.FORMAT && t != Character.PRIVATE_USE && t != Character.SURROGATE;
590   }
591
592   @NotNull
593   @Contract(pure = true)
594   public static String escapeStringCharacters(@NotNull String s) {
595     StringBuilder buffer = new StringBuilder(s.length());
596     escapeStringCharacters(s.length(), s, "\"", buffer);
597     return buffer.toString();
598   }
599
600   @NotNull
601   @Contract(pure = true)
602   public static String escapeCharCharacters(@NotNull String s) {
603     StringBuilder buffer = new StringBuilder(s.length());
604     escapeStringCharacters(s.length(), s, "\'", buffer);
605     return buffer.toString();
606   }
607
608   @NotNull
609   @Contract(pure = true)
610   public static String unescapeStringCharacters(@NotNull String s) {
611     StringBuilder buffer = new StringBuilder(s.length());
612     unescapeStringCharacters(s.length(), s, buffer);
613     return buffer.toString();
614   }
615
616   private static boolean isQuoteAt(@NotNull String s, int ind) {
617     char ch = s.charAt(ind);
618     return ch == '\'' || ch == '\"';
619   }
620
621   @Contract(pure = true)
622   public static boolean isQuotedString(@NotNull String s) {
623     return s.length() > 1 && isQuoteAt(s, 0) && s.charAt(0) == s.charAt(s.length() - 1);
624   }
625
626   @NotNull
627   @Contract(pure = true)
628   public static String unquoteString(@NotNull String s) {
629     if (isQuotedString(s)) {
630       return s.substring(1, s.length() - 1);
631     }
632     return s;
633   }
634
635   @NotNull
636   @Contract(pure = true)
637   public static String unquoteString(@NotNull String s, char quotationChar) {
638     if (s.length() > 1 && quotationChar == s.charAt(0) && quotationChar == s.charAt(s.length() - 1)) {
639       return s.substring(1, s.length() - 1);
640     }
641     return s;
642   }
643
644   /**
645    * This is just an optimized version of Matcher.quoteReplacement
646    */
647   @NotNull
648   @Contract(pure = true)
649   public static String quoteReplacement(@NotNull String s) {
650     boolean needReplacements = false;
651
652     for (int i = 0; i < s.length(); i++) {
653       char c = s.charAt(i);
654       if (c == '\\' || c == '$') {
655         needReplacements = true;
656         break;
657       }
658     }
659
660     if (!needReplacements) return s;
661
662     StringBuilder sb = new StringBuilder(s.length() * 6 / 5);
663     for (int i = 0; i < s.length(); i++) {
664       char c = s.charAt(i);
665       if (c == '\\') {
666         sb.append('\\');
667         sb.append('\\');
668       }
669       else if (c == '$') {
670         sb.append('\\');
671         sb.append('$');
672       }
673       else {
674         sb.append(c);
675       }
676     }
677     return sb.toString();
678   }
679
680   private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
681     boolean escaped = false;
682     for (int idx = 0; idx < length; idx++) {
683       char ch = s.charAt(idx);
684       if (!escaped) {
685         if (ch == '\\') {
686           escaped = true;
687         }
688         else {
689           buffer.append(ch);
690         }
691       }
692       else {
693         switch (ch) {
694           case 'n':
695             buffer.append('\n');
696             break;
697
698           case 'r':
699             buffer.append('\r');
700             break;
701
702           case 'b':
703             buffer.append('\b');
704             break;
705
706           case 't':
707             buffer.append('\t');
708             break;
709
710           case 'f':
711             buffer.append('\f');
712             break;
713
714           case '\'':
715             buffer.append('\'');
716             break;
717
718           case '\"':
719             buffer.append('\"');
720             break;
721
722           case '\\':
723             buffer.append('\\');
724             break;
725
726           case 'u':
727             if (idx + 4 < length) {
728               try {
729                 int code = Integer.parseInt(s.substring(idx + 1, idx + 5), 16);
730                 idx += 4;
731                 buffer.append((char)code);
732               }
733               catch (NumberFormatException e) {
734                 buffer.append("\\u");
735               }
736             }
737             else {
738               buffer.append("\\u");
739             }
740             break;
741
742           default:
743             buffer.append(ch);
744             break;
745         }
746         escaped = false;
747       }
748     }
749
750     if (escaped) buffer.append('\\');
751   }
752
753   @SuppressWarnings({"HardCodedStringLiteral"})
754   @NotNull
755   @Contract(pure = true)
756   public static String pluralize(@NotNull String suggestion) {
757     if (suggestion.endsWith("Child") || suggestion.endsWith("child")) {
758       return suggestion + "ren";
759     }
760
761     if (suggestion.equals("this")) {
762       return "these";
763     }
764     if (suggestion.equals("This")) {
765       return "These";
766     }
767
768     if (endsWithIgnoreCase(suggestion, "es")) {
769       return suggestion;
770     }
771
772     if (endsWithIgnoreCase(suggestion, "s") || endsWithIgnoreCase(suggestion, "x") || endsWithIgnoreCase(suggestion, "ch")) {
773       return suggestion + "es";
774     }
775
776     int len = suggestion.length();
777     if (endsWithIgnoreCase(suggestion, "y") && len > 1 && !isVowel(toLowerCase(suggestion.charAt(len - 2)))) {
778       return suggestion.substring(0, len - 1) + "ies";
779     }
780
781     return suggestion + "s";
782   }
783
784   @NotNull
785   @Contract(pure = true)
786   public static String capitalizeWords(@NotNull String text,
787                                        boolean allWords) {
788     return capitalizeWords(text, " \t\n\r\f", allWords, false);
789   }
790
791   @NotNull
792   @Contract(pure = true)
793   public static String capitalizeWords(@NotNull String text,
794                                        @NotNull String tokenizerDelim,
795                                        boolean allWords,
796                                        boolean leaveOriginalDelims) {
797     final StringTokenizer tokenizer = new StringTokenizer(text, tokenizerDelim, leaveOriginalDelims);
798     final StringBuilder out = new StringBuilder(text.length());
799     boolean toCapitalize = true;
800     while (tokenizer.hasMoreTokens()) {
801       final String word = tokenizer.nextToken();
802       if (!leaveOriginalDelims && out.length() > 0) {
803         out.append(' ');
804       }
805       out.append(toCapitalize ? capitalize(word) : word);
806       if (!allWords) {
807         toCapitalize = false;
808       }
809     }
810     return out.toString();
811   }
812
813   @Contract(pure = true)
814   public static String decapitalize(String s) {
815     return Introspector.decapitalize(s);
816   }
817
818   @Contract(pure = true)
819   public static boolean isVowel(char c) {
820     return VOWELS.indexOf(c) >= 0;
821   }
822
823   @NotNull
824   @Contract(pure = true)
825   public static String capitalize(@NotNull String s) {
826     if (s.isEmpty()) return s;
827     if (s.length() == 1) return StringUtilRt.toUpperCase(s);
828
829     // Optimization
830     if (Character.isUpperCase(s.charAt(0))) return s;
831     return toUpperCase(s.charAt(0)) + s.substring(1);
832   }
833
834   @Contract(value = "null -> false", pure = true)
835   public static boolean isCapitalized(@Nullable String s) {
836     return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0));
837   }
838
839   @NotNull
840   @Contract(pure = true)
841   public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
842     if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
843       return s;
844     }
845     return capitalize(s);
846   }
847
848   @Contract(pure = true)
849   public static int stringHashCode(@NotNull CharSequence chars) {
850     if (chars instanceof String) return chars.hashCode();
851     if (chars instanceof CharSequenceWithStringHash) return chars.hashCode();
852     if (chars instanceof CharArrayCharSequence) return chars.hashCode();
853
854     return stringHashCode(chars, 0, chars.length());
855   }
856
857   @Contract(pure = true)
858   public static int stringHashCode(@NotNull CharSequence chars, int from, int to) {
859     int h = 0;
860     for (int off = from; off < to; off++) {
861       h = 31 * h + chars.charAt(off);
862     }
863     return h;
864   }
865
866   @Contract(pure = true)
867   public static int stringHashCode(char[] chars, int from, int to) {
868     int h = 0;
869     for (int off = from; off < to; off++) {
870       h = 31 * h + chars[off];
871     }
872     return h;
873   }
874
875   @Contract(pure = true)
876   public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) {
877     int h = 0;
878     for (int off = from; off < to; off++) {
879       h = 31 * h + toLowerCase(chars[off]);
880     }
881     return h;
882   }
883
884   @Contract(pure = true)
885   public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
886     int h = 0;
887     for (int off = from; off < to; off++) {
888       h = 31 * h + toLowerCase(chars.charAt(off));
889     }
890     return h;
891   }
892
893   @Contract(pure = true)
894   public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
895     return stringHashCodeInsensitive(chars, 0, chars.length());
896   }
897
898   @Contract(pure = true)
899   public static int stringHashCodeIgnoreWhitespaces(char[] chars, int from, int to) {
900     int h = 0;
901     for (int off = from; off < to; off++) {
902       char c = chars[off];
903       if (!isWhiteSpace(c)) {
904         h = 31 * h + c;
905       }
906     }
907     return h;
908   }
909
910   @Contract(pure = true)
911   public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars, int from, int to) {
912     int h = 0;
913     for (int off = from; off < to; off++) {
914       char c = chars.charAt(off);
915       if (!isWhiteSpace(c)) {
916         h = 31 * h + c;
917       }
918     }
919     return h;
920   }
921
922   @Contract(pure = true)
923   public static int stringHashCodeIgnoreWhitespaces(@NotNull CharSequence chars) {
924     return stringHashCodeIgnoreWhitespaces(chars, 0, chars.length());
925   }
926
927   /**
928    * Equivalent to string.startsWith(prefixes[0] + prefixes[1] + ...) but avoids creating an object for concatenation.
929    */
930   @Contract(pure = true)
931   public static boolean startsWithConcatenation(@NotNull String string, @NotNull String... prefixes) {
932     int offset = 0;
933     for (String prefix : prefixes) {
934       int prefixLen = prefix.length();
935       if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
936         return false;
937       }
938       offset += prefixLen;
939     }
940     return true;
941   }
942
943   /**
944    * @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14).
945    */
946   @SuppressWarnings("UnusedDeclaration")
947   @Contract(pure = true)
948   public static boolean startsWithConcatenationOf(@NotNull String string, @NotNull String firstPrefix, @NotNull String secondPrefix) {
949     return startsWithConcatenation(string, firstPrefix, secondPrefix);
950   }
951
952   /**
953    * @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14).
954    */
955   @SuppressWarnings("UnusedDeclaration")
956   @Contract(pure = true)
957   public static boolean startsWithConcatenationOf(@NotNull String string,
958                                                   @NotNull String firstPrefix,
959                                                   @NotNull String secondPrefix,
960                                                   @NotNull String thirdPrefix) {
961     return startsWithConcatenation(string, firstPrefix, secondPrefix, thirdPrefix);
962   }
963
964   @Contract(value = "null -> null; !null -> !null", pure = true)
965   public static String trim(@Nullable String s) {
966     return s == null ? null : s.trim();
967   }
968
969   @NotNull
970   @Contract(pure = true)
971   public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix) {
972     if (s.endsWith(suffix)) {
973       return s.substring(0, s.length() - suffix.length());
974     }
975     return s;
976   }
977
978   @NotNull
979   @Contract(pure = true)
980   public static String trimLog(@NotNull final String text, final int limit) {
981     if (limit > 5 && text.length() > limit) {
982       return text.substring(0, limit - 5) + " ...\n";
983     }
984     return text;
985   }
986
987   @NotNull
988   @Contract(pure = true)
989   public static String trimLeading(@NotNull String string) {
990     return trimLeading((CharSequence)string).toString();
991   }
992   @NotNull
993   @Contract(pure = true)
994   public static CharSequence trimLeading(@NotNull CharSequence string) {
995     int index = 0;
996     while (index < string.length() && Character.isWhitespace(string.charAt(index))) index++;
997     return string.subSequence(index, string.length());
998   }
999
1000   @NotNull
1001   @Contract(pure = true)
1002   public static String trimLeading(@NotNull String string, char symbol) {
1003     int index = 0;
1004     while (index < string.length() && string.charAt(index) == symbol) index++;
1005     return string.substring(index);
1006   }
1007
1008   @NotNull
1009   @Contract(pure = true)
1010   public static String trimTrailing(@NotNull String string) {
1011     return trimTrailing((CharSequence)string).toString();
1012   }
1013
1014   @NotNull
1015   @Contract(pure = true)
1016   public static CharSequence trimTrailing(@NotNull CharSequence string) {
1017     int index = string.length() - 1;
1018     while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
1019     return string.subSequence(0, index + 1);
1020   }
1021
1022   @Contract(pure = true)
1023   public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
1024     return s != null && s.length() != 0 && s.charAt(0) == prefix;
1025   }
1026
1027   @Contract(pure = true)
1028   public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
1029     return StringUtilRt.endsWithChar(s, suffix);
1030   }
1031
1032   @NotNull
1033   @Contract(pure = true)
1034   public static String trimStart(@NotNull String s, @NonNls @NotNull String prefix) {
1035     if (s.startsWith(prefix)) {
1036       return s.substring(prefix.length());
1037     }
1038     return s;
1039   }
1040
1041   @NotNull
1042   @Contract(pure = true)
1043   public static String pluralize(@NotNull String base, int n) {
1044     if (n == 1) return base;
1045     return pluralize(base);
1046   }
1047
1048   public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
1049     assert times >= 0 : times;
1050     try {
1051       for (int i = 0; i < times; i++) {
1052         buffer.append(symbol);
1053       }
1054     }
1055     catch (IOException e) {
1056       LOG.error(e);
1057     }
1058   }
1059
1060   @Contract(pure = true)
1061   public static String defaultIfEmpty(@Nullable String value, String defaultValue) {
1062     return isEmpty(value) ? defaultValue : value;
1063   }
1064
1065   @Contract(value = "null -> false", pure = true)
1066   public static boolean isNotEmpty(@Nullable String s) {
1067     return s != null && !s.isEmpty();
1068   }
1069
1070   @Contract(value = "null -> true", pure=true)
1071   public static boolean isEmpty(@Nullable String s) {
1072     return s == null || s.isEmpty();
1073   }
1074
1075   @Contract(value = "null -> true",pure = true)
1076   public static boolean isEmpty(@Nullable CharSequence cs) {
1077     return cs == null || cs.length() == 0;
1078   }
1079
1080   @Contract(pure = true)
1081   public static int length(@Nullable CharSequence cs) {
1082     return cs == null ? 0 : cs.length();
1083   }
1084
1085   @NotNull
1086   @Contract(pure = true)
1087   public static String notNullize(@Nullable final String s) {
1088     return notNullize(s, "");
1089   }
1090
1091   @NotNull
1092   @Contract(pure = true)
1093   public static String notNullize(@Nullable final String s, @NotNull String defaultValue) {
1094     return s == null ? defaultValue : s;
1095   }
1096
1097   @Nullable
1098   @Contract(pure = true)
1099   public static String nullize(@Nullable final String s) {
1100     return nullize(s, false);
1101   }
1102
1103   @Nullable
1104   @Contract(pure = true)
1105   public static String nullize(@Nullable final String s, boolean nullizeSpaces) {
1106     if (nullizeSpaces) {
1107       if (isEmptyOrSpaces(s)) return null;
1108     }
1109     else {
1110       if (isEmpty(s)) return null;
1111     }
1112     return s;
1113   }
1114
1115   @Contract(value = "null -> true",pure = true)
1116   // we need to keep this method to preserve backward compatibility
1117   public static boolean isEmptyOrSpaces(@Nullable String s) {
1118     return isEmptyOrSpaces(((CharSequence)s));
1119   }
1120
1121   @Contract(value = "null -> true", pure = true)
1122   public static boolean isEmptyOrSpaces(@Nullable CharSequence s) {
1123     if (isEmpty(s)) {
1124       return true;
1125     }
1126     for (int i = 0; i < s.length(); i++) {
1127       if (s.charAt(i) > ' ') {
1128         return false;
1129       }
1130     }
1131     return true;
1132   }
1133
1134   /**
1135    * Allows to answer if given symbol is white space, tabulation or line feed.
1136    *
1137    * @param c symbol to check
1138    * @return <code>true</code> if given symbol is white space, tabulation or line feed; <code>false</code> otherwise
1139    */
1140   @Contract(pure = true)
1141   public static boolean isWhiteSpace(char c) {
1142     return c == '\n' || c == '\t' || c == ' ';
1143   }
1144
1145   @NotNull
1146   @Contract(pure = true)
1147   public static String getThrowableText(@NotNull Throwable aThrowable) {
1148     return ExceptionUtil.getThrowableText(aThrowable);
1149   }
1150
1151   @NotNull
1152   @Contract(pure = true)
1153   public static String getThrowableText(@NotNull Throwable aThrowable, @NonNls @NotNull final String stackFrameSkipPattern) {
1154     return ExceptionUtil.getThrowableText(aThrowable, stackFrameSkipPattern);
1155   }
1156
1157   @Nullable
1158   @Contract(pure = true)
1159   public static String getMessage(@NotNull Throwable e) {
1160     return ExceptionUtil.getMessage(e);
1161   }
1162
1163   @NotNull
1164   @Contract(pure = true)
1165   public static String repeatSymbol(final char aChar, final int count) {
1166     char[] buffer = new char[count];
1167     Arrays.fill(buffer, aChar);
1168     return StringFactory.createShared(buffer);
1169   }
1170
1171   @NotNull
1172   @Contract(pure = true)
1173   public static String repeat(@NotNull String s, int count) {
1174     assert count >= 0 : count;
1175     StringBuilder sb = new StringBuilder(s.length() * count);
1176     for (int i = 0; i < count; i++) {
1177       sb.append(s);
1178     }
1179     return sb.toString();
1180   }
1181
1182   @NotNull
1183   @Contract(pure = true)
1184   public static List<String> splitHonorQuotes(@NotNull String s, char separator) {
1185     final List<String> result = new ArrayList<String>();
1186     final StringBuilder builder = new StringBuilder(s.length());
1187     boolean inQuotes = false;
1188     for (int i = 0; i < s.length(); i++) {
1189       final char c = s.charAt(i);
1190       if (c == separator && !inQuotes) {
1191         if (builder.length() > 0) {
1192           result.add(builder.toString());
1193           builder.setLength(0);
1194         }
1195         continue;
1196       }
1197
1198       if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
1199         inQuotes = !inQuotes;
1200       }
1201       builder.append(c);
1202     }
1203
1204     if (builder.length() > 0) {
1205       result.add(builder.toString());
1206     }
1207     return result;
1208   }
1209
1210
1211   @NotNull
1212   @Contract(pure = true)
1213   public static List<String> split(@NotNull String s, @NotNull String separator) {
1214     return split(s, separator, true);
1215   }
1216   @NotNull
1217   @Contract(pure = true)
1218   public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator) {
1219     return split(s, separator, true, true);
1220   }
1221
1222   @NotNull
1223   @Contract(pure = true)
1224   public static List<String> split(@NotNull String s, @NotNull String separator,
1225                                    boolean excludeSeparator) {
1226     return split(s, separator, excludeSeparator, true);
1227   }
1228
1229   @NotNull
1230   @Contract(pure = true)
1231   public static List<String> split(@NotNull String s, @NotNull String separator,
1232                                    boolean excludeSeparator, boolean excludeEmptyStrings) {
1233     return (List)split((CharSequence)s,separator,excludeSeparator,excludeEmptyStrings);
1234   }
1235   @NotNull
1236   @Contract(pure = true)
1237   public static List<CharSequence> split(@NotNull CharSequence s, @NotNull CharSequence separator,
1238                                    boolean excludeSeparator, boolean excludeEmptyStrings) {
1239     if (separator.length() == 0) {
1240       return Collections.singletonList(s);
1241     }
1242     List<CharSequence> result = new ArrayList<CharSequence>();
1243     int pos = 0;
1244     while (true) {
1245       int index = indexOf(s,separator, pos);
1246       if (index == -1) break;
1247       final int nextPos = index + separator.length();
1248       CharSequence token = s.subSequence(pos, excludeSeparator ? index : nextPos);
1249       if (token.length() != 0 || !excludeEmptyStrings) {
1250         result.add(token);
1251       }
1252       pos = nextPos;
1253     }
1254     if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
1255       result.add(s.subSequence(pos, s.length()));
1256     }
1257     return result;
1258   }
1259
1260   @NotNull
1261   @Contract(pure = true)
1262   public static Iterable<String> tokenize(@NotNull String s, @NotNull String separators) {
1263     final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators);
1264     return new Iterable<String>() {
1265       @NotNull
1266       @Override
1267       public Iterator<String> iterator() {
1268         return new Iterator<String>() {
1269           @Override
1270           public boolean hasNext() {
1271             return tokenizer.hasMoreTokens();
1272           }
1273
1274           @Override
1275           public String next() {
1276             return tokenizer.nextToken();
1277           }
1278
1279           @Override
1280           public void remove() {
1281             throw new UnsupportedOperationException();
1282           }
1283         };
1284       }
1285     };
1286   }
1287
1288   @NotNull
1289   @Contract(pure = true)
1290   public static Iterable<String> tokenize(@NotNull final StringTokenizer tokenizer) {
1291     return new Iterable<String>() {
1292       @NotNull
1293       @Override
1294       public Iterator<String> iterator() {
1295         return new Iterator<String>() {
1296           @Override
1297           public boolean hasNext() {
1298             return tokenizer.hasMoreTokens();
1299           }
1300
1301           @Override
1302           public String next() {
1303             return tokenizer.nextToken();
1304           }
1305
1306           @Override
1307           public void remove() {
1308             throw new UnsupportedOperationException();
1309           }
1310         };
1311       }
1312     };
1313   }
1314
1315   /**
1316    * @return list containing all words in {@code text}, or {@link ContainerUtil#emptyList()} if there are none.
1317    * The <b>word</b> here means the maximum sub-string consisting entirely of characters which are <code>Character.isJavaIdentifierPart(c)</code>.
1318    */
1319   @NotNull
1320   @Contract(pure = true)
1321   public static List<String> getWordsIn(@NotNull String text) {
1322     List<String> result = null;
1323     int start = -1;
1324     for (int i = 0; i < text.length(); i++) {
1325       char c = text.charAt(i);
1326       boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1327       if (isIdentifierPart && start == -1) {
1328         start = i;
1329       }
1330       if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1331         if (result == null) {
1332           result = new SmartList<String>();
1333         }
1334         result.add(text.substring(start, i + 1));
1335       }
1336       else if (!isIdentifierPart && start != -1) {
1337         if (result == null) {
1338           result = new SmartList<String>();
1339         }
1340         result.add(text.substring(start, i));
1341         start = -1;
1342       }
1343     }
1344     if (result == null) {
1345       return ContainerUtil.emptyList();
1346     }
1347     return result;
1348   }
1349
1350   @NotNull
1351   @Contract(pure = true)
1352   public static List<TextRange> getWordIndicesIn(@NotNull String text) {
1353     List<TextRange> result = new SmartList<TextRange>();
1354     int start = -1;
1355     for (int i = 0; i < text.length(); i++) {
1356       char c = text.charAt(i);
1357       boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1358       if (isIdentifierPart && start == -1) {
1359         start = i;
1360       }
1361       if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1362         result.add(new TextRange(start, i + 1));
1363       }
1364       else if (!isIdentifierPart && start != -1) {
1365         result.add(new TextRange(start, i));
1366         start = -1;
1367       }
1368     }
1369     return result;
1370   }
1371
1372   @NotNull
1373   @Contract(pure = true)
1374   public static String join(@NotNull final String[] strings, @NotNull final String separator) {
1375     return join(strings, 0, strings.length, separator);
1376   }
1377
1378   @NotNull
1379   @Contract(pure = true)
1380   public static String join(@NotNull final String[] strings, int startIndex, int endIndex, @NotNull final String separator) {
1381     final StringBuilder result = new StringBuilder();
1382     for (int i = startIndex; i < endIndex; i++) {
1383       if (i > startIndex) result.append(separator);
1384       result.append(strings[i]);
1385     }
1386     return result.toString();
1387   }
1388
1389   @NotNull
1390   @Contract(pure = true)
1391   public static String[] zip(@NotNull String[] strings1, @NotNull String[] strings2, String separator) {
1392     if (strings1.length != strings2.length) throw new IllegalArgumentException();
1393
1394     String[] result = ArrayUtil.newStringArray(strings1.length);
1395     for (int i = 0; i < result.length; i++) {
1396       result[i] = strings1[i] + separator + strings2[i];
1397     }
1398
1399     return result;
1400   }
1401
1402   @NotNull
1403   @Contract(pure = true)
1404   public static String[] surround(@NotNull String[] strings1, String prefix, String suffix) {
1405     String[] result = ArrayUtil.newStringArray(strings1.length);
1406     for (int i = 0; i < result.length; i++) {
1407       result[i] = prefix + strings1[i] + suffix;
1408     }
1409
1410     return result;
1411   }
1412
1413   @NotNull
1414   @Contract(pure = true)
1415   public static <T> String join(@NotNull T[] items, @NotNull Function<T, String> f, @NotNull @NonNls String separator) {
1416     return join(Arrays.asList(items), f, separator);
1417   }
1418
1419   @NotNull
1420   @Contract(pure = true)
1421   public static <T> String join(@NotNull Collection<? extends T> items,
1422                                 @NotNull Function<? super T, String> f,
1423                                 @NotNull @NonNls String separator) {
1424     if (items.isEmpty()) return "";
1425     return join((Iterable<? extends T>)items, f, separator);
1426   }
1427
1428   @Contract(pure = true)
1429   public static String join(@NotNull Iterable<?> items, @NotNull @NonNls String separator) {
1430     StringBuilder result = new StringBuilder();
1431     for (Object item : items) {
1432       result.append(item).append(separator);
1433     }
1434     if (result.length() > 0) {
1435       result.setLength(result.length() - separator.length());
1436     }
1437     return result.toString();
1438   }
1439
1440   @NotNull
1441   @Contract(pure = true)
1442   public static <T> String join(@NotNull Iterable<? extends T> items,
1443                                 @NotNull Function<? super T, String> f,
1444                                 @NotNull @NonNls String separator) {
1445     final StringBuilder result = new StringBuilder();
1446     for (T item : items) {
1447       String string = f.fun(item);
1448       if (string != null && !string.isEmpty()) {
1449         if (result.length() != 0) result.append(separator);
1450         result.append(string);
1451       }
1452     }
1453     return result.toString();
1454   }
1455
1456   @NotNull
1457   @Contract(pure = true)
1458   public static String join(@NotNull Collection<String> strings, @NotNull String separator) {
1459     if (strings.size() <= 1) {
1460       return notNullize(ContainerUtil.getFirstItem(strings));
1461     }
1462     StringBuilder result = new StringBuilder();
1463     join(strings, separator, result);
1464     return result.toString();
1465   }
1466
1467   public static void join(@NotNull Collection<String> strings, @NotNull String separator, @NotNull StringBuilder result) {
1468     boolean isFirst = true;
1469     for (String string : strings) {
1470       if (string != null) {
1471         if (isFirst) {
1472           isFirst = false;
1473         }
1474         else {
1475           result.append(separator);
1476         }
1477         result.append(string);
1478       }
1479     }
1480   }
1481
1482   @NotNull
1483   @Contract(pure = true)
1484   public static String join(@NotNull final int[] strings, @NotNull final String separator) {
1485     final StringBuilder result = new StringBuilder();
1486     for (int i = 0; i < strings.length; i++) {
1487       if (i > 0) result.append(separator);
1488       result.append(strings[i]);
1489     }
1490     return result.toString();
1491   }
1492
1493   @NotNull
1494   @Contract(pure = true)
1495   public static String join(@Nullable final String... strings) {
1496     if (strings == null || strings.length == 0) return "";
1497
1498     final StringBuilder builder = new StringBuilder();
1499     for (final String string : strings) {
1500       builder.append(string);
1501     }
1502     return builder.toString();
1503   }
1504
1505   /**
1506    * Strips quotes around the value.
1507    * Quotes are removed even if leading and trailing quotes are different or if there is only one quote (leading or trailing).
1508    * @deprecated use {@link com.intellij.openapi.util.text.StringUtil#unquoteString(String)} instead
1509    * To be removed in IDEA 17
1510    */
1511   @NotNull
1512   @Contract(pure = true)
1513   @Deprecated
1514   public static String stripQuotesAroundValue(@NotNull String text) {
1515     final int len = text.length();
1516     if (len > 0) {
1517       final int from = isQuoteAt(text, 0) ? 1 : 0;
1518       final int to = len > 1 && isQuoteAt(text, len - 1) ? len - 1 : len;
1519       if (from > 0 || to < len) {
1520         return text.substring(from, to);
1521       }
1522     }
1523     return text;
1524   }
1525
1526   /**
1527    * Formats the specified file size as a string.
1528    *
1529    * @param fileSize the size to format.
1530    * @return the size formatted as a string.
1531    * @since 5.0.1
1532    */
1533   @NotNull
1534   @Contract(pure = true)
1535   public static String formatFileSize(long fileSize) {
1536     return formatValue(fileSize, null,
1537                        new String[]{"B", "K", "M", "G", "T", "P", "E"},
1538                        new long[]{1000, 1000, 1000, 1000, 1000, 1000});
1539   }
1540
1541   @NotNull
1542   @Contract(pure = true)
1543   public static String formatDuration(long duration) {
1544     return formatValue(duration, " ",
1545                        new String[]{"ms", "s", "m", "h", "d", "w", "mo", "yr", "c", "ml", "ep"},
1546                        new long[]{1000, 60, 60, 24, 7, 4, 12, 100, 10, 10000});
1547   }
1548
1549   @NotNull
1550   private static String formatValue(long value, String partSeparator, String[] units, long[] multipliers) {
1551     StringBuilder sb = new StringBuilder();
1552     long count = value;
1553     long remainder = 0;
1554     int i = 0;
1555     for (; i < units.length; i++) {
1556       long multiplier = i < multipliers.length ? multipliers[i] : -1;
1557       if (multiplier == -1 || count < multiplier) break;
1558       remainder = count % multiplier;
1559       count /= multiplier;
1560       if (partSeparator != null && (remainder != 0 || sb.length() > 0)) {
1561         sb.insert(0, units[i]).insert(0, remainder).insert(0, partSeparator);
1562       }
1563     }
1564     if (partSeparator != null || remainder == 0) {
1565       sb.insert(0, units[i]).insert(0, count);
1566     }
1567     else if (remainder > 0) {
1568       sb.append(String.format(Locale.US, "%.2f", count + (double)remainder / multipliers[i - 1])).append(units[i]);
1569     }
1570     return sb.toString();
1571   }
1572
1573   /**
1574    * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1575    * Returns <code>null</code> if failed to match appropriate heuristic.
1576    *
1577    * @param name english word in plural form
1578    * @return name in singular form or <code>null</code> if failed to find one.
1579    */
1580   @SuppressWarnings({"HardCodedStringLiteral"})
1581   @Nullable
1582   @Contract(pure = true)
1583   public static String unpluralize(@NotNull final String name) {
1584     if (name.endsWith("sses") || name.endsWith("shes") || name.endsWith("ches") || name.endsWith("xes")) { //?
1585       return name.substring(0, name.length() - 2);
1586     }
1587
1588     if (name.endsWith("ses")) {
1589       return name.substring(0, name.length() - 1);
1590     }
1591
1592     if (name.endsWith("ies")) {
1593       if (name.endsWith("cookies") || name.endsWith("Cookies")) {
1594         return name.substring(0, name.length() - "ookies".length()) + "ookie";
1595       }
1596
1597       return name.substring(0, name.length() - 3) + "y";
1598     }
1599
1600     if (name.endsWith("leaves") || name.endsWith("Leaves")) {
1601       return name.substring(0, name.length() - "eaves".length()) + "eaf";
1602     }
1603
1604     String result = stripEnding(name, "s");
1605     if (result != null) {
1606       return result;
1607     }
1608
1609     if (name.endsWith("children")) {
1610       return name.substring(0, name.length() - "children".length()) + "child";
1611     }
1612
1613     if (name.endsWith("Children") && name.length() > "Children".length()) {
1614       return name.substring(0, name.length() - "Children".length()) + "Child";
1615     }
1616
1617
1618     return null;
1619   }
1620
1621   @Nullable
1622   @Contract(pure = true)
1623   private static String stripEnding(@NotNull String name, @NotNull String ending) {
1624     if (name.endsWith(ending)) {
1625       if (name.equals(ending)) return name; // do not return empty string
1626       return name.substring(0, name.length() - 1);
1627     }
1628     return null;
1629   }
1630
1631   @Contract(pure = true)
1632   public static boolean containsAlphaCharacters(@NotNull String value) {
1633     for (int i = 0; i < value.length(); i++) {
1634       if (Character.isLetter(value.charAt(i))) return true;
1635     }
1636     return false;
1637   }
1638
1639   @Contract(pure = true)
1640   public static boolean containsAnyChar(@NotNull final String value, @NotNull final String chars) {
1641     if (chars.length() > value.length()) {
1642       return containsAnyChar(value, chars, 0, value.length());
1643     }
1644     else {
1645       return containsAnyChar(chars, value, 0, chars.length());
1646     }
1647   }
1648
1649   @Contract(pure = true)
1650   public static boolean containsAnyChar(@NotNull final String value,
1651                                         @NotNull final String chars,
1652                                         final int start, final int end) {
1653     for (int i = start; i < end; i++) {
1654       if (chars.indexOf(value.charAt(i)) >= 0) {
1655         return true;
1656       }
1657     }
1658
1659     return false;
1660   }
1661
1662   @Contract(pure = true)
1663   public static boolean containsChar(@NotNull final String value, final char ch) {
1664     return value.indexOf(ch) >= 0;
1665   }
1666
1667   /**
1668    * @deprecated use #capitalize(String)
1669    */
1670   @Contract(value = "null -> null; !null -> !null", pure = true)
1671   public static String firstLetterToUpperCase(@Nullable final String displayString) {
1672     if (displayString == null || displayString.isEmpty()) return displayString;
1673     char firstChar = displayString.charAt(0);
1674     char uppedFirstChar = toUpperCase(firstChar);
1675
1676     if (uppedFirstChar == firstChar) return displayString;
1677
1678     char[] buffer = displayString.toCharArray();
1679     buffer[0] = uppedFirstChar;
1680     return StringFactory.createShared(buffer);
1681   }
1682
1683   /**
1684    * Strip out all characters not accepted by given filter
1685    *
1686    * @param s      e.g. "/n    my string "
1687    * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1688    * @return stripped string e.g. "mystring"
1689    */
1690   @NotNull
1691   @Contract(pure = true)
1692   public static String strip(@NotNull final String s, @NotNull final CharFilter filter) {
1693     final StringBuilder result = new StringBuilder(s.length());
1694     for (int i = 0; i < s.length(); i++) {
1695       char ch = s.charAt(i);
1696       if (filter.accept(ch)) {
1697         result.append(ch);
1698       }
1699     }
1700     return result.toString();
1701   }
1702
1703   @NotNull
1704   @Contract(pure = true)
1705   public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern) {
1706     return findMatches(s, pattern, 1);
1707   }
1708
1709   @NotNull
1710   @Contract(pure = true)
1711   public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) {
1712     List<String> result = new SmartList<String>();
1713     Matcher m = pattern.matcher(s);
1714     while (m.find()) {
1715       String group = m.group(groupIndex);
1716       if (group != null) {
1717         result.add(group);
1718       }
1719     }
1720     return result;
1721   }
1722
1723   /**
1724    * Find position of the first character accepted by given filter.
1725    *
1726    * @param s      the string to search
1727    * @param filter search filter
1728    * @return position of the first character accepted or -1 if not found
1729    */
1730   @Contract(pure = true)
1731   public static int findFirst(@NotNull final CharSequence s, @NotNull CharFilter filter) {
1732     for (int i = 0; i < s.length(); i++) {
1733       char ch = s.charAt(i);
1734       if (filter.accept(ch)) {
1735         return i;
1736       }
1737     }
1738     return -1;
1739   }
1740
1741   @NotNull
1742   @Contract(pure = true)
1743   public static String replaceSubstring(@NotNull String string, @NotNull TextRange range, @NotNull String replacement) {
1744     return range.replace(string, replacement);
1745   }
1746
1747   @Contract(pure = true)
1748   public static boolean startsWithWhitespace(@NotNull String text) {
1749     return !text.isEmpty() && Character.isWhitespace(text.charAt(0));
1750   }
1751
1752   @Contract(pure = true)
1753   public static boolean isChar(CharSequence seq, int index, char c) {
1754     return index >= 0 && index < seq.length() && seq.charAt(index) == c;
1755   }
1756
1757   @Contract(pure = true)
1758   public static boolean startsWith(@NotNull CharSequence text, @NotNull CharSequence prefix) {
1759     int l1 = text.length();
1760     int l2 = prefix.length();
1761     if (l1 < l2) return false;
1762
1763     for (int i = 0; i < l2; i++) {
1764       if (text.charAt(i) != prefix.charAt(i)) return false;
1765     }
1766
1767     return true;
1768   }
1769
1770   @Contract(pure = true)
1771   public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
1772     int l1 = text.length() - startIndex;
1773     int l2 = prefix.length();
1774     if (l1 < l2) return false;
1775
1776     for (int i = 0; i < l2; i++) {
1777       if (text.charAt(i + startIndex) != prefix.charAt(i)) return false;
1778     }
1779
1780     return true;
1781   }
1782
1783   @Contract(pure = true)
1784   public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
1785     int l1 = text.length();
1786     int l2 = suffix.length();
1787     if (l1 < l2) return false;
1788
1789     for (int i = l1 - 1; i >= l1 - l2; i--) {
1790       if (text.charAt(i) != suffix.charAt(i + l2 - l1)) return false;
1791     }
1792
1793     return true;
1794   }
1795
1796   @NotNull
1797   @Contract(pure = true)
1798   public static String commonPrefix(@NotNull String s1, @NotNull String s2) {
1799     return s1.substring(0, commonPrefixLength(s1, s2));
1800   }
1801
1802   @Contract(pure = true)
1803   public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1804     int i;
1805     int minLength = Math.min(s1.length(), s2.length());
1806     for (i = 0; i < minLength; i++) {
1807       if (s1.charAt(i) != s2.charAt(i)) {
1808         break;
1809       }
1810     }
1811     return i;
1812   }
1813
1814   @NotNull
1815   @Contract(pure = true)
1816   public static String commonSuffix(@NotNull String s1, @NotNull String s2) {
1817     return s1.substring(s1.length() - commonSuffixLength(s1, s2));
1818   }
1819
1820   @Contract(pure = true)
1821   public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1822     int s1Length = s1.length();
1823     int s2Length = s2.length();
1824     if (s1Length == 0 || s2Length == 0) return 0;
1825     int i;
1826     for (i = 0; i < s1Length && i < s2Length; i++) {
1827       if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
1828         break;
1829       }
1830     }
1831     return i;
1832   }
1833
1834   /**
1835    * Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> interval.
1836    *
1837    * @param s     target char sequence to check
1838    * @param start start offset to use within the given char sequence (inclusive)
1839    * @param end   end offset to use within the given char sequence (exclusive)
1840    * @param c     target symbol to check
1841    * @return <code>true</code> if given symbol is contained at the target range of the given char sequence;
1842    * <code>false</code> otherwise
1843    */
1844   @Contract(pure = true)
1845   public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
1846     return indexOf(s, c, start, end) >= 0;
1847   }
1848
1849   @Contract(pure = true)
1850   public static boolean containsWhitespaces(@Nullable CharSequence s) {
1851     if (s == null) return false;
1852
1853     for (int i = 0; i < s.length(); i++) {
1854       if (Character.isWhitespace(s.charAt(i))) return true;
1855     }
1856     return false;
1857   }
1858
1859   @Contract(pure = true)
1860   public static int indexOf(@NotNull CharSequence s, char c) {
1861     return indexOf(s, c, 0, s.length());
1862   }
1863
1864   @Contract(pure = true)
1865   public static int indexOf(@NotNull CharSequence s, char c, int start) {
1866     return indexOf(s, c, start, s.length());
1867   }
1868
1869   @Contract(pure = true)
1870   public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
1871     for (int i = start; i < end; i++) {
1872       if (s.charAt(i) == c) return i;
1873     }
1874     return -1;
1875   }
1876
1877   @Contract(pure = true)
1878   public static boolean contains(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1879     return indexOf(sequence, infix) >= 0;
1880   }
1881
1882   @Contract(pure = true)
1883   public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix) {
1884     return indexOf(sequence, infix, 0);
1885   }
1886
1887   @Contract(pure = true)
1888   public static int indexOf(@NotNull CharSequence sequence, @NotNull CharSequence infix, int start) {
1889     for (int i = start; i <= sequence.length() - infix.length(); i++) {
1890       if (startsWith(sequence, i, infix)) {
1891         return i;
1892       }
1893     }
1894     return -1;
1895   }
1896
1897   @Contract(pure = true)
1898   public static int indexOf(@NotNull CharSequence s, char c, int start, int end, boolean caseSensitive) {
1899     for (int i = start; i < end; i++) {
1900       if (charsMatch(s.charAt(i), c, !caseSensitive)) return i;
1901     }
1902     return -1;
1903   }
1904
1905   @Contract(pure = true)
1906   public static int indexOf(@NotNull char[] s, char c, int start, int end, boolean caseSensitive) {
1907     for (int i = start; i < end; i++) {
1908       if (charsMatch(s[i], c, !caseSensitive)) return i;
1909     }
1910     return -1;
1911   }
1912
1913   @Contract(pure = true)
1914   public static int indexOfSubstringEnd(@NotNull String text, @NotNull String subString) {
1915     int i = text.indexOf(subString);
1916     if (i == -1) return -1;
1917     return i + subString.length();
1918   }
1919
1920   @Contract(pure = true)
1921   public static int indexOfAny(@NotNull final String s, @NotNull final String chars) {
1922     return indexOfAny(s, chars, 0, s.length());
1923   }
1924
1925   @Contract(pure = true)
1926   public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars) {
1927     return indexOfAny(s, chars, 0, s.length());
1928   }
1929
1930   @Contract(pure = true)
1931   public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) {
1932     return indexOfAny((CharSequence)s, chars, start, end);
1933   }
1934
1935   @Contract(pure = true)
1936   public static int indexOfAny(@NotNull final CharSequence s, @NotNull final String chars, final int start, final int end) {
1937     for (int i = start; i < end; i++) {
1938       if (containsChar(chars, s.charAt(i))) return i;
1939     }
1940     return -1;
1941   }
1942
1943   @Nullable
1944   @Contract(pure = true)
1945   public static String substringBefore(@NotNull String text, @NotNull String subString) {
1946     int i = text.indexOf(subString);
1947     if (i == -1) return null;
1948     return text.substring(0, i);
1949   }
1950
1951   @Nullable
1952   @Contract(pure = true)
1953   public static String substringAfter(@NotNull String text, @NotNull String subString) {
1954     int i = text.indexOf(subString);
1955     if (i == -1) return null;
1956     return text.substring(i + subString.length());
1957   }
1958
1959   /**
1960    * Allows to retrieve index of last occurrence of the given symbols at <code>[start; end)</code> sub-sequence of the given text.
1961    *
1962    * @param s     target text
1963    * @param c     target symbol which last occurrence we want to check
1964    * @param start start offset of the target text (inclusive)
1965    * @param end   end offset of the target text (exclusive)
1966    * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
1967    * <code>-1</code> otherwise
1968    */
1969   @Contract(pure = true)
1970   public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
1971     return StringUtilRt.lastIndexOf(s, c, start, end);
1972   }
1973
1974   @NotNull
1975   @Contract(pure = true)
1976   public static String first(@NotNull String text, final int maxLength, final boolean appendEllipsis) {
1977     return text.length() > maxLength ? text.substring(0, maxLength) + (appendEllipsis ? "..." : "") : text;
1978   }
1979
1980   @NotNull
1981   @Contract(pure = true)
1982   public static CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
1983     return text.length() > length ? text.subSequence(0, length) + (appendEllipsis ? "..." : "") : text;
1984   }
1985
1986   @NotNull
1987   @Contract(pure = true)
1988   public static CharSequence last(@NotNull CharSequence text, final int length, boolean prependEllipsis) {
1989     return text.length() > length ? (prependEllipsis ? "..." : "") + text.subSequence(text.length() - length, text.length()) : text;
1990   }
1991
1992   @NotNull
1993   @Contract(pure = true)
1994   public static String escapeChar(@NotNull final String str, final char character) {
1995     return escapeChars(str, character);
1996   }
1997
1998   @NotNull
1999   @Contract(pure = true)
2000   public static String escapeChars(@NotNull final String str, final char... character) {
2001     final StringBuilder buf = new StringBuilder(str);
2002     for (char c : character) {
2003       escapeChar(buf, c);
2004     }
2005     return buf.toString();
2006   }
2007
2008   private static void escapeChar(@NotNull final StringBuilder buf, final char character) {
2009     int idx = 0;
2010     while ((idx = indexOf(buf, character, idx)) >= 0) {
2011       buf.insert(idx, "\\");
2012       idx += 2;
2013     }
2014   }
2015
2016   @NotNull
2017   @Contract(pure = true)
2018   public static String escapeQuotes(@NotNull final String str) {
2019     return escapeChar(str, '"');
2020   }
2021
2022   public static void escapeQuotes(@NotNull final StringBuilder buf) {
2023     escapeChar(buf, '"');
2024   }
2025
2026   @NotNull
2027   @Contract(pure = true)
2028   public static String escapeSlashes(@NotNull final String str) {
2029     return escapeChar(str, '/');
2030   }
2031
2032   @NotNull
2033   @Contract(pure = true)
2034   public static String escapeBackSlashes(@NotNull final String str) {
2035     return escapeChar(str, '\\');
2036   }
2037
2038   public static void escapeSlashes(@NotNull final StringBuilder buf) {
2039     escapeChar(buf, '/');
2040   }
2041
2042   @NotNull
2043   @Contract(pure = true)
2044   public static String unescapeSlashes(@NotNull final String str) {
2045     final StringBuilder buf = new StringBuilder(str.length());
2046     unescapeChar(buf, str, '/');
2047     return buf.toString();
2048   }
2049
2050   @NotNull
2051   @Contract(pure = true)
2052   public static String unescapeBackSlashes(@NotNull final String str) {
2053     final StringBuilder buf = new StringBuilder(str.length());
2054     unescapeChar(buf, str, '\\');
2055     return buf.toString();
2056   }
2057
2058   @NotNull
2059   @Contract(pure = true)
2060   public static String unescapeChar(@NotNull final String str, char unescapeChar) {
2061     final StringBuilder buf = new StringBuilder(str.length());
2062     unescapeChar(buf, str, unescapeChar);
2063     return buf.toString();
2064   }
2065
2066   private static void unescapeChar(@NotNull StringBuilder buf, @NotNull String str, char unescapeChar) {
2067     final int length = str.length();
2068     final int last = length - 1;
2069     for (int i = 0; i < length; i++) {
2070       char ch = str.charAt(i);
2071       if (ch == '\\' && i != last) {
2072         i++;
2073         ch = str.charAt(i);
2074         if (ch != unescapeChar) buf.append('\\');
2075       }
2076
2077       buf.append(ch);
2078     }
2079   }
2080
2081   public static void quote(@NotNull final StringBuilder builder) {
2082     quote(builder, '\"');
2083   }
2084
2085   public static void quote(@NotNull final StringBuilder builder, final char quotingChar) {
2086     builder.insert(0, quotingChar);
2087     builder.append(quotingChar);
2088   }
2089
2090   @NotNull
2091   @Contract(pure = true)
2092   public static String wrapWithDoubleQuote(@NotNull String str) {
2093     return '\"' + str + "\"";
2094   }
2095
2096   @NonNls private static final String[] REPLACES_REFS = {"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"};
2097   @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""};
2098
2099   @Contract(value = "null -> null; !null -> !null",pure = true)
2100   public static String unescapeXml(@Nullable final String text) {
2101     if (text == null) return null;
2102     return replace(text, REPLACES_REFS, REPLACES_DISP);
2103   }
2104
2105   @Contract(value = "null -> null; !null -> !null",pure = true)
2106   public static String escapeXml(@Nullable final String text) {
2107     if (text == null) return null;
2108     return replace(text, REPLACES_DISP, REPLACES_REFS);
2109   }
2110
2111   @NotNull
2112   @Contract(pure = true)
2113   public static String htmlEmphasize(@NotNull String text) {
2114     return "<b><code>" + escapeXml(text) + "</code></b>";
2115   }
2116
2117
2118   @NotNull
2119   @Contract(pure = true)
2120   public static String escapeToRegexp(@NotNull String text) {
2121     final StringBuilder result = new StringBuilder(text.length());
2122     return escapeToRegexp(text, result).toString();
2123   }
2124
2125   @NotNull
2126   public static StringBuilder escapeToRegexp(@NotNull CharSequence text, @NotNull StringBuilder builder) {
2127     for (int i = 0; i < text.length(); i++) {
2128       final char c = text.charAt(i);
2129       if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_') {
2130         builder.append(c);
2131       }
2132       else if (c == '\n') {
2133         builder.append("\\n");
2134       }
2135       else {
2136         builder.append('\\').append(c);
2137       }
2138     }
2139
2140     return builder;
2141   }
2142
2143   @Contract(pure = true)
2144   public static boolean isNotEscapedBackslash(@NotNull char[] chars, int startOffset, int backslashOffset) {
2145     if (chars[backslashOffset] != '\\') {
2146       return false;
2147     }
2148     boolean escaped = false;
2149     for (int i = startOffset; i < backslashOffset; i++) {
2150       if (chars[i] == '\\') {
2151         escaped = !escaped;
2152       }
2153       else {
2154         escaped = false;
2155       }
2156     }
2157     return !escaped;
2158   }
2159
2160   @Contract(pure = true)
2161   public static boolean isNotEscapedBackslash(@NotNull CharSequence text, int startOffset, int backslashOffset) {
2162     if (text.charAt(backslashOffset) != '\\') {
2163       return false;
2164     }
2165     boolean escaped = false;
2166     for (int i = startOffset; i < backslashOffset; i++) {
2167       if (text.charAt(i) == '\\') {
2168         escaped = !escaped;
2169       }
2170       else {
2171         escaped = false;
2172       }
2173     }
2174     return !escaped;
2175   }
2176
2177   @NotNull
2178   @Contract(pure = true)
2179   public static String replace(@NotNull String text, @NotNull String[] from, @NotNull String[] to) {
2180     final StringBuilder result = new StringBuilder(text.length());
2181     replace:
2182     for (int i = 0; i < text.length(); i++) {
2183       for (int j = 0; j < from.length; j += 1) {
2184         String toReplace = from[j];
2185         String replaceWith = to[j];
2186
2187         final int len = toReplace.length();
2188         if (text.regionMatches(i, toReplace, 0, len)) {
2189           result.append(replaceWith);
2190           i += len - 1;
2191           continue replace;
2192         }
2193       }
2194       result.append(text.charAt(i));
2195     }
2196     return result.toString();
2197   }
2198
2199   @NotNull
2200   @Contract(pure = true)
2201   public static String[] filterEmptyStrings(@NotNull String[] strings) {
2202     int emptyCount = 0;
2203     for (String string : strings) {
2204       if (string == null || string.isEmpty()) emptyCount++;
2205     }
2206     if (emptyCount == 0) return strings;
2207
2208     String[] result = ArrayUtil.newStringArray(strings.length - emptyCount);
2209     int count = 0;
2210     for (String string : strings) {
2211       if (string == null || string.isEmpty()) continue;
2212       result[count++] = string;
2213     }
2214
2215     return result;
2216   }
2217
2218   @Contract(pure = true)
2219   public static int countNewLines(@NotNull CharSequence text) {
2220     return countChars(text, '\n');
2221   }
2222
2223   @Contract(pure = true)
2224   public static int countChars(@NotNull CharSequence text, char c) {
2225     return countChars(text, c, 0, false);
2226   }
2227
2228   @Contract(pure = true)
2229   public static int countChars(@NotNull CharSequence text, char c, int offset, boolean continuous) {
2230     int count = 0;
2231     for (int i = offset; i < text.length(); ++i) {
2232       if (text.charAt(i) == c) {
2233         count++;
2234       }
2235       else if (continuous) {
2236         break;
2237       }
2238     }
2239     return count;
2240   }
2241
2242   @NotNull
2243   @Contract(pure = true)
2244   public static String capitalsOnly(@NotNull String s) {
2245     StringBuilder b = new StringBuilder();
2246     for (int i = 0; i < s.length(); i++) {
2247       if (Character.isUpperCase(s.charAt(i))) {
2248         b.append(s.charAt(i));
2249       }
2250     }
2251
2252     return b.toString();
2253   }
2254
2255   /**
2256    * @param args Strings to join.
2257    * @return {@code null} if any of given Strings is {@code null}.
2258    */
2259   @Nullable
2260   @Contract(pure = true)
2261   public static String joinOrNull(@NotNull String... args) {
2262     StringBuilder r = new StringBuilder();
2263     for (String arg : args) {
2264       if (arg == null) return null;
2265       r.append(arg);
2266     }
2267     return r.toString();
2268   }
2269
2270   @Nullable
2271   @Contract(pure = true)
2272   public static String getPropertyName(@NonNls @NotNull String methodName) {
2273     if (methodName.startsWith("get")) {
2274       return Introspector.decapitalize(methodName.substring(3));
2275     }
2276     else if (methodName.startsWith("is")) {
2277       return Introspector.decapitalize(methodName.substring(2));
2278     }
2279     else if (methodName.startsWith("set")) {
2280       return Introspector.decapitalize(methodName.substring(3));
2281     }
2282     else {
2283       return null;
2284     }
2285   }
2286
2287   @Contract(pure = true)
2288   public static boolean isJavaIdentifierStart(char c) {
2289     return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierStart(c);
2290   }
2291
2292   @Contract(pure = true)
2293   public static boolean isJavaIdentifierPart(char c) {
2294     return c >= '0' && c <= '9' || isJavaIdentifierStart(c);
2295   }
2296
2297   @Contract(pure = true)
2298   public static boolean isJavaIdentifier(@NotNull String text) {
2299     int len = text.length();
2300     if (len == 0) return false;
2301
2302     if (!isJavaIdentifierStart(text.charAt(0))) return false;
2303
2304     for (int i = 1; i < len; i++) {
2305       if (!isJavaIdentifierPart(text.charAt(i))) return false;
2306     }
2307
2308     return true;
2309   }
2310
2311   /**
2312    * Escape property name or key in property file. Unicode characters are escaped as well.
2313    *
2314    * @param input an input to escape
2315    * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case.
2316    * @return an escaped string
2317    */
2318   @NotNull
2319   @Contract(pure = true)
2320   public static String escapeProperty(@NotNull String input, final boolean isKey) {
2321     final StringBuilder escaped = new StringBuilder(input.length());
2322     for (int i = 0; i < input.length(); i++) {
2323       final char ch = input.charAt(i);
2324       switch (ch) {
2325         case ' ':
2326           if (isKey && i == 0) {
2327             // only the leading space has to be escaped
2328             escaped.append('\\');
2329           }
2330           escaped.append(' ');
2331           break;
2332         case '\t':
2333           escaped.append("\\t");
2334           break;
2335         case '\r':
2336           escaped.append("\\r");
2337           break;
2338         case '\n':
2339           escaped.append("\\n");
2340           break;
2341         case '\f':
2342           escaped.append("\\f");
2343           break;
2344         case '\\':
2345         case '#':
2346         case '!':
2347         case ':':
2348         case '=':
2349           escaped.append('\\');
2350           escaped.append(ch);
2351           break;
2352         default:
2353           if (20 < ch && ch < 0x7F) {
2354             escaped.append(ch);
2355           }
2356           else {
2357             escaped.append("\\u");
2358             escaped.append(Character.forDigit((ch >> 12) & 0xF, 16));
2359             escaped.append(Character.forDigit((ch >> 8) & 0xF, 16));
2360             escaped.append(Character.forDigit((ch >> 4) & 0xF, 16));
2361             escaped.append(Character.forDigit((ch) & 0xF, 16));
2362           }
2363           break;
2364       }
2365     }
2366     return escaped.toString();
2367   }
2368
2369   @Contract(pure = true)
2370   public static String getQualifiedName(@Nullable String packageName, String className) {
2371     if (packageName == null || packageName.isEmpty()) {
2372       return className;
2373     }
2374     return packageName + '.' + className;
2375   }
2376
2377   @Contract(pure = true)
2378   public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
2379     // todo duplicates com.intellij.util.text.VersionComparatorUtil.compare
2380     // todo please refactor next time you make changes here
2381     if (v1 == null && v2 == null) {
2382       return 0;
2383     }
2384     if (v1 == null) {
2385       return -1;
2386     }
2387     if (v2 == null) {
2388       return 1;
2389     }
2390
2391     String[] part1 = v1.split("[\\.\\_\\-]");
2392     String[] part2 = v2.split("[\\.\\_\\-]");
2393
2394     int idx = 0;
2395     for (; idx < part1.length && idx < part2.length; idx++) {
2396       String p1 = part1[idx];
2397       String p2 = part2[idx];
2398
2399       int cmp;
2400       if (p1.matches("\\d+") && p2.matches("\\d+")) {
2401         cmp = new Integer(p1).compareTo(new Integer(p2));
2402       }
2403       else {
2404         cmp = part1[idx].compareTo(part2[idx]);
2405       }
2406       if (cmp != 0) return cmp;
2407     }
2408
2409     if (part1.length == part2.length) {
2410       return 0;
2411     }
2412     else {
2413       boolean left = part1.length > idx;
2414       String[] parts = left ? part1 : part2;
2415
2416       for (; idx < parts.length; idx++) {
2417         String p = parts[idx];
2418         int cmp;
2419         if (p.matches("\\d+")) {
2420           cmp = new Integer(p).compareTo(0);
2421         }
2422         else {
2423           cmp = 1;
2424         }
2425         if (cmp != 0) return left ? cmp : -cmp;
2426       }
2427       return 0;
2428     }
2429   }
2430
2431   @Contract(pure = true)
2432   public static int getOccurrenceCount(@NotNull String text, final char c) {
2433     int res = 0;
2434     int i = 0;
2435     while (i < text.length()) {
2436       i = text.indexOf(c, i);
2437       if (i >= 0) {
2438         res++;
2439         i++;
2440       }
2441       else {
2442         break;
2443       }
2444     }
2445     return res;
2446   }
2447
2448   @Contract(pure = true)
2449   public static int getOccurrenceCount(@NotNull String text, @NotNull String s) {
2450     int res = 0;
2451     int i = 0;
2452     while (i < text.length()) {
2453       i = text.indexOf(s, i);
2454       if (i >= 0) {
2455         res++;
2456         i++;
2457       }
2458       else {
2459         break;
2460       }
2461     }
2462     return res;
2463   }
2464
2465   @NotNull
2466   @Contract(pure = true)
2467   public static String fixVariableNameDerivedFromPropertyName(@NotNull String name) {
2468     if (isEmptyOrSpaces(name)) return name;
2469     char c = name.charAt(0);
2470     if (isVowel(c)) {
2471       return "an" + Character.toUpperCase(c) + name.substring(1);
2472     }
2473     return "a" + Character.toUpperCase(c) + name.substring(1);
2474   }
2475
2476   @NotNull
2477   @Contract(pure = true)
2478   public static String sanitizeJavaIdentifier(@NotNull String name) {
2479     final StringBuilder result = new StringBuilder(name.length());
2480
2481     for (int i = 0; i < name.length(); i++) {
2482       final char ch = name.charAt(i);
2483       if (Character.isJavaIdentifierPart(ch)) {
2484         if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
2485           result.append("_");
2486         }
2487         result.append(ch);
2488       }
2489     }
2490
2491     return result.toString();
2492   }
2493
2494   public static void assertValidSeparators(@NotNull CharSequence s) {
2495     char[] chars = CharArrayUtil.fromSequenceWithoutCopying(s);
2496     int slashRIndex = -1;
2497
2498     if (chars != null) {
2499       for (int i = 0, len = s.length(); i < len; ++i) {
2500         if (chars[i] == '\r') {
2501           slashRIndex = i;
2502           break;
2503         }
2504       }
2505     }
2506     else {
2507       for (int i = 0, len = s.length(); i < len; i++) {
2508         if (s.charAt(i) == '\r') {
2509           slashRIndex = i;
2510           break;
2511         }
2512       }
2513     }
2514
2515     if (slashRIndex != -1) {
2516       String context =
2517         String.valueOf(last(s.subSequence(0, slashRIndex), 10, true)) + first(s.subSequence(slashRIndex, s.length()), 10, true);
2518       context = escapeStringCharacters(context);
2519       LOG.error("Wrong line separators: '" + context + "' at offset " + slashRIndex);
2520     }
2521   }
2522
2523   @NotNull
2524   @Contract(pure = true)
2525   public static String tail(@NotNull String s, final int idx) {
2526     return idx >= s.length() ? "" : s.substring(idx, s.length());
2527   }
2528
2529   /**
2530    * Splits string by lines.
2531    *
2532    * @param string String to split
2533    * @return array of strings
2534    */
2535   @NotNull
2536   @Contract(pure = true)
2537   public static String[] splitByLines(@NotNull String string) {
2538     return splitByLines(string, true);
2539   }
2540
2541   /**
2542    * Splits string by lines. If several line separators are in a row corresponding empty lines
2543    * are also added to result if {@code excludeEmptyStrings} is {@code false}.
2544    *
2545    * @param string String to split
2546    * @return array of strings
2547    */
2548   @NotNull
2549   @Contract(pure = true)
2550   public static String[] splitByLines(@NotNull String string, boolean excludeEmptyStrings) {
2551     return (excludeEmptyStrings ? EOL_SPLIT_PATTERN : EOL_SPLIT_PATTERN_WITH_EMPTY).split(string);
2552   }
2553
2554   @NotNull
2555   @Contract(pure = true)
2556   public static String[] splitByLinesDontTrim(@NotNull String string) {
2557     return EOL_SPLIT_DONT_TRIM_PATTERN.split(string);
2558   }
2559
2560   /**
2561    * Splits string by lines, keeping all line separators at the line ends and in the empty lines.
2562    * <br> E.g. splitting text
2563    * <blockquote>
2564    *   foo\r\n<br>
2565    *   \n<br>
2566    *   bar\n<br>
2567    *   \r\n<br>
2568    *   baz\r<br>
2569    *   \r<br>
2570    * </blockquote>
2571    * will return the following array: foo\r\n, \n, bar\n, \r\n, baz\r, \r
2572    *
2573    */
2574   @NotNull
2575   @Contract(pure = true)
2576   public static String[] splitByLinesKeepSeparators(@NotNull String string) {
2577     return EOL_SPLIT_KEEP_SEPARATORS.split(string);
2578   }
2579
2580   @NotNull
2581   @Contract(pure = true)
2582   public static List<Pair<String, Integer>> getWordsWithOffset(@NotNull String s) {
2583     List<Pair<String, Integer>> res = ContainerUtil.newArrayList();
2584     s += " ";
2585     StringBuilder name = new StringBuilder();
2586     int startInd = -1;
2587     for (int i = 0; i < s.length(); i++) {
2588       if (Character.isWhitespace(s.charAt(i))) {
2589         if (name.length() > 0) {
2590           res.add(Pair.create(name.toString(), startInd));
2591           name.setLength(0);
2592           startInd = -1;
2593         }
2594       }
2595       else {
2596         if (startInd == -1) {
2597           startInd = i;
2598         }
2599         name.append(s.charAt(i));
2600       }
2601     }
2602     return res;
2603   }
2604
2605   /**
2606    * Implementation of "Sorting for Humans: Natural Sort Order":
2607    * http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
2608    */
2609   @Contract(pure = true)
2610   public static int naturalCompare(@Nullable String string1, @Nullable String string2) {
2611     return naturalCompare(string1, string2, false);
2612   }
2613
2614   @Contract(pure = true)
2615   private static int naturalCompare(@Nullable String string1, @Nullable String string2, boolean caseSensitive) {
2616     //noinspection StringEquality
2617     if (string1 == string2) {
2618       return 0;
2619     }
2620     if (string1 == null) {
2621       return -1;
2622     }
2623     if (string2 == null) {
2624       return 1;
2625     }
2626
2627     final int string1Length = string1.length();
2628     final int string2Length = string2.length();
2629     int i = 0;
2630     int j = 0;
2631     for (; i < string1Length && j < string2Length; i++, j++) {
2632       char ch1 = string1.charAt(i);
2633       char ch2 = string2.charAt(j);
2634       if ((isDecimalDigit(ch1) || ch1 == ' ') && (isDecimalDigit(ch2) || ch2 == ' ')) {
2635         int startNum1 = i;
2636         while (ch1 == ' ' || ch1 == '0') { // skip leading spaces and zeros
2637           startNum1++;
2638           if (startNum1 >= string1Length) break;
2639           ch1 = string1.charAt(startNum1);
2640         }
2641         int startNum2 = j;
2642         while (ch2 == ' ' || ch2 == '0') { // skip leading spaces and zeros
2643           startNum2++;
2644           if (startNum2 >= string2Length) break;
2645           ch2 = string2.charAt(startNum2);
2646         }
2647         i = startNum1;
2648         j = startNum2;
2649         // find end index of number
2650         while (i < string1Length && isDecimalDigit(string1.charAt(i))) i++;
2651         while (j < string2Length && isDecimalDigit(string2.charAt(j))) j++;
2652         final int lengthDiff = (i - startNum1) - (j - startNum2);
2653         if (lengthDiff != 0) {
2654           // numbers with more digits are always greater than shorter numbers
2655           return lengthDiff;
2656         }
2657         for (; startNum1 < i; startNum1++, startNum2++) {
2658           // compare numbers with equal digit count
2659           final int diff = string1.charAt(startNum1) - string2.charAt(startNum2);
2660           if (diff != 0) {
2661             return diff;
2662           }
2663         }
2664         i--;
2665         j--;
2666       }
2667       else {
2668         if (caseSensitive) {
2669           return ch1 - ch2;
2670         }
2671         else {
2672           // similar logic to charsMatch() below
2673           if (ch1 != ch2) {
2674             final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
2675             if (diff1 != 0) {
2676               final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
2677               if (diff2 != 0) {
2678                 return diff2;
2679               }
2680             }
2681           }
2682         }
2683       }
2684     }
2685     // After the loop the end of one of the strings might not have been reached, if the other
2686     // string ends with a number and the strings are equal until the end of that number. When
2687     // there are more characters in the string, then it is greater.
2688     if (i < string1Length) {
2689       return 1;
2690     }
2691     else if (j < string2Length) {
2692       return -1;
2693     }
2694     if (!caseSensitive && string1Length == string2Length) {
2695       // do case sensitive compare if case insensitive strings are equal
2696       return naturalCompare(string1, string2, true);
2697     }
2698     return string1Length - string2Length;
2699   }
2700
2701   @Contract(pure = true)
2702   public static boolean isDecimalDigit(char c) {
2703     return c >= '0' && c <= '9';
2704   }
2705
2706   @Contract(pure = true)
2707   public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) {
2708     //noinspection StringEquality
2709     if (s1 == s2) return 0;
2710     if (s1 == null) return -1;
2711     if (s2 == null) return 1;
2712     return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
2713   }
2714
2715   @Contract(pure = true)
2716   public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
2717     final int compare = compare(s1, s2, ignoreCase);
2718     return compare != 0 ? compare : compare(t1, t2, ignoreCase);
2719   }
2720
2721   @Contract(pure = true)
2722   public static int hashCode(@NotNull CharSequence s) {
2723     return stringHashCode(s);
2724   }
2725
2726   @Contract(pure = true)
2727   public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2728     if (s1 == null ^ s2 == null) {
2729       return false;
2730     }
2731
2732     if (s1 == null) {
2733       return true;
2734     }
2735
2736     if (s1.length() != s2.length()) {
2737       return false;
2738     }
2739     for (int i = 0; i < s1.length(); i++) {
2740       if (s1.charAt(i) != s2.charAt(i)) {
2741         return false;
2742       }
2743     }
2744     return true;
2745   }
2746
2747   @Contract(pure = true)
2748   public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2749     if (s1 == null ^ s2 == null) {
2750       return false;
2751     }
2752
2753     if (s1 == null) {
2754       return true;
2755     }
2756
2757     if (s1.length() != s2.length()) {
2758       return false;
2759     }
2760     for (int i = 0; i < s1.length(); i++) {
2761       if (!charsMatch(s1.charAt(i), s2.charAt(i), true)) {
2762         return false;
2763       }
2764     }
2765     return true;
2766   }
2767
2768   @Contract(pure = true)
2769   public static boolean equalsIgnoreWhitespaces(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2770     if (s1 == null ^ s2 == null) {
2771       return false;
2772     }
2773
2774     if (s1 == null) {
2775       return true;
2776     }
2777
2778     int len1 = s1.length();
2779     int len2 = s2.length();
2780
2781     int index1 = 0;
2782     int index2 = 0;
2783     while (index1 < len1 && index2 < len2) {
2784       if (s1.charAt(index1) == s2.charAt(index2)) {
2785         index1++;
2786         index2++;
2787         continue;
2788       }
2789
2790       boolean skipped = false;
2791       while (index1 != len1 && isWhiteSpace(s1.charAt(index1))) {
2792         skipped = true;
2793         index1++;
2794       }
2795       while (index2 != len2 && isWhiteSpace(s2.charAt(index2))) {
2796         skipped = true;
2797         index2++;
2798       }
2799
2800       if (!skipped) return false;
2801     }
2802
2803     for (; index1 != len1; index1++) {
2804       if (!isWhiteSpace(s1.charAt(index1))) return false;
2805     }
2806     for (; index2 != len2; index2++) {
2807       if (!isWhiteSpace(s2.charAt(index2))) return false;
2808     }
2809
2810     return true;
2811   }
2812
2813   @Contract(pure = true)
2814   public static boolean equalsTrimWhitespaces(@NotNull CharSequence s1, @NotNull CharSequence s2) {
2815     int start1 = 0;
2816     int end1 = s1.length();
2817     int start2 = 0;
2818     int end2 = s2.length();
2819
2820     while (start1 < end1) {
2821       char c = s1.charAt(start1);
2822       if (!isWhiteSpace(c)) break;
2823       start1++;
2824     }
2825
2826     while (start1 < end1) {
2827       char c = s1.charAt(end1 - 1);
2828       if (!isWhiteSpace(c)) break;
2829       end1--;
2830     }
2831
2832     while (start2 < end2) {
2833       char c = s2.charAt(start2);
2834       if (!isWhiteSpace(c)) break;
2835       start2++;
2836     }
2837
2838     while (start2 < end2) {
2839       char c = s2.charAt(end2 - 1);
2840       if (!isWhiteSpace(c)) break;
2841       end2--;
2842     }
2843
2844     CharSequence ts1 = new CharSequenceSubSequence(s1, start1, end1);
2845     CharSequence ts2 = new CharSequenceSubSequence(s2, start2, end2);
2846
2847     return equals(ts1, ts2);
2848   }
2849
2850   @Contract(pure = true)
2851   public static int compare(char c1, char c2, boolean ignoreCase) {
2852     // duplicating String.equalsIgnoreCase logic
2853     int d = c1 - c2;
2854     if (d == 0 || !ignoreCase) {
2855       return d;
2856     }
2857     // If characters don't match but case may be ignored,
2858     // try converting both characters to uppercase.
2859     // If the results match, then the comparison scan should
2860     // continue.
2861     char u1 = StringUtilRt.toUpperCase(c1);
2862     char u2 = StringUtilRt.toUpperCase(c2);
2863     d = u1 - u2;
2864     if (d != 0) {
2865       // Unfortunately, conversion to uppercase does not work properly
2866       // for the Georgian alphabet, which has strange rules about case
2867       // conversion.  So we need to make one last check before
2868       // exiting.
2869       d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
2870     }
2871     return d;
2872   }
2873
2874   @Contract(pure = true)
2875   public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
2876     return compare(c1, c2, ignoreCase) == 0;
2877   }
2878
2879   @NotNull
2880   @Contract(pure = true)
2881   public static String formatLinks(@NotNull String message) {
2882     Pattern linkPattern = Pattern.compile("http://[a-zA-Z0-9\\./\\-\\+]+");
2883     StringBuffer result = new StringBuffer();
2884     Matcher m = linkPattern.matcher(message);
2885     while (m.find()) {
2886       m.appendReplacement(result, "<a href=\"" + m.group() + "\">" + m.group() + "</a>");
2887     }
2888     m.appendTail(result);
2889     return result.toString();
2890   }
2891
2892   @Contract(pure = true)
2893   public static boolean isHexDigit(char c) {
2894     return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
2895   }
2896
2897   @Contract(pure = true)
2898   public static boolean isOctalDigit(char c) {
2899     return '0' <= c && c <= '7';
2900   }
2901
2902   @NotNull
2903   @Contract(pure = true)
2904   public static String shortenTextWithEllipsis(@NotNull final String text, final int maxLength, final int suffixLength) {
2905     return shortenTextWithEllipsis(text, maxLength, suffixLength, false);
2906   }
2907
2908   @NotNull
2909   @Contract(pure = true)
2910   public static String trimMiddle(@NotNull String text, int maxLength) {
2911     return shortenTextWithEllipsis(text, maxLength, maxLength >> 1, true);
2912   }
2913
2914   @NotNull
2915   @Contract(pure = true)
2916   public static String shortenTextWithEllipsis(@NotNull final String text,
2917                                                final int maxLength,
2918                                                final int suffixLength,
2919                                                @NotNull String symbol) {
2920     final int textLength = text.length();
2921     if (textLength > maxLength) {
2922       final int prefixLength = maxLength - suffixLength - symbol.length();
2923       assert prefixLength > 0;
2924       return text.substring(0, prefixLength) + symbol + text.substring(textLength - suffixLength);
2925     }
2926     else {
2927       return text;
2928     }
2929   }
2930
2931   @NotNull
2932   @Contract(pure = true)
2933   public static String shortenTextWithEllipsis(@NotNull final String text,
2934                                                final int maxLength,
2935                                                final int suffixLength,
2936                                                boolean useEllipsisSymbol) {
2937     String symbol = useEllipsisSymbol ? "\u2026" : "...";
2938     return shortenTextWithEllipsis(text, maxLength, suffixLength, symbol);
2939   }
2940
2941   @NotNull
2942   @Contract(pure = true)
2943   public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength, boolean useEllipsisSymbol) {
2944     return shortenTextWithEllipsis(path, maxLength, (int)(maxLength * 0.7), useEllipsisSymbol);
2945   }
2946
2947   @NotNull
2948   @Contract(pure = true)
2949   public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength) {
2950     return shortenPathWithEllipsis(path, maxLength, false);
2951   }
2952
2953   @Contract(pure = true)
2954   public static boolean charsEqual(char a, char b, boolean ignoreCase) {
2955     return ignoreCase ? charsEqualIgnoreCase(a, b) : a == b;
2956   }
2957
2958   @Contract(pure = true)
2959   public static boolean charsEqualIgnoreCase(char a, char b) {
2960     return StringUtilRt.charsEqualIgnoreCase(a, b);
2961   }
2962
2963   @Contract(pure = true)
2964   public static char toUpperCase(char a) {
2965     return StringUtilRt.toUpperCase(a);
2966   }
2967
2968   @NotNull
2969   @Contract(pure = true)
2970   public static String toUpperCase(@NotNull String a) {
2971     return StringUtilRt.toUpperCase(a);
2972   }
2973
2974   @Contract(pure = true)
2975   public static char toLowerCase(final char a) {
2976     return StringUtilRt.toLowerCase(a);
2977   }
2978
2979   @Nullable
2980   public static LineSeparator detectSeparators(@NotNull CharSequence text) {
2981     int index = indexOfAny(text, "\n\r");
2982     if (index == -1) return null;
2983     if (startsWith(text, index, "\r\n")) return LineSeparator.CRLF;
2984     if (text.charAt(index) == '\r') return LineSeparator.CR;
2985     if (text.charAt(index) == '\n') return LineSeparator.LF;
2986     throw new IllegalStateException();
2987   }
2988
2989   @NotNull
2990   @Contract(pure = true)
2991   public static String convertLineSeparators(@NotNull String text) {
2992     return StringUtilRt.convertLineSeparators(text);
2993   }
2994
2995   @NotNull
2996   @Contract(pure = true)
2997   public static String convertLineSeparators(@NotNull String text, boolean keepCarriageReturn) {
2998     return StringUtilRt.convertLineSeparators(text, keepCarriageReturn);
2999   }
3000
3001   @NotNull
3002   @Contract(pure = true)
3003   public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator) {
3004     return StringUtilRt.convertLineSeparators(text, newSeparator);
3005   }
3006
3007   @NotNull
3008   public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator, @Nullable int[] offsetsToKeep) {
3009     return StringUtilRt.convertLineSeparators(text, newSeparator, offsetsToKeep);
3010   }
3011
3012   @NotNull
3013   public static String convertLineSeparators(@NotNull String text,
3014                                              @NotNull String newSeparator,
3015                                              @Nullable int[] offsetsToKeep,
3016                                              boolean keepCarriageReturn) {
3017     return StringUtilRt.convertLineSeparators(text, newSeparator, offsetsToKeep, keepCarriageReturn);
3018   }
3019
3020   @Contract(pure = true)
3021   public static int parseInt(final String string, final int defaultValue) {
3022     return StringUtilRt.parseInt(string, defaultValue);
3023   }
3024
3025   @Contract(pure = true)
3026   public static double parseDouble(final String string, final double defaultValue) {
3027     return StringUtilRt.parseDouble(string, defaultValue);
3028   }
3029
3030   @Contract(pure = true)
3031   public static boolean parseBoolean(String string, final boolean defaultValue) {
3032     return StringUtilRt.parseBoolean(string, defaultValue);
3033   }
3034
3035   @NotNull
3036   @Contract(pure = true)
3037   public static String getShortName(@NotNull Class aClass) {
3038     return StringUtilRt.getShortName(aClass);
3039   }
3040
3041   @NotNull
3042   @Contract(pure = true)
3043   public static String getShortName(@NotNull String fqName) {
3044     return StringUtilRt.getShortName(fqName);
3045   }
3046
3047   @NotNull
3048   @Contract(pure = true)
3049   public static String getShortName(@NotNull String fqName, char separator) {
3050     return StringUtilRt.getShortName(fqName, separator);
3051   }
3052
3053   @NotNull
3054   @Contract(pure = true)
3055   public static CharSequence newBombedCharSequence(@NotNull CharSequence sequence, long delay) {
3056     final long myTime = System.currentTimeMillis() + delay;
3057     return new BombedCharSequence(sequence) {
3058       @Override
3059       protected void checkCanceled() {
3060         long l = System.currentTimeMillis();
3061         if (l >= myTime) {
3062           throw new ProcessCanceledException();
3063         }
3064       }
3065     };
3066   }
3067
3068   public static boolean trimEnd(@NotNull StringBuilder buffer, @NotNull CharSequence end) {
3069     if (endsWith(buffer, end)) {
3070       buffer.delete(buffer.length() - end.length(), buffer.length());
3071       return true;
3072     }
3073     return false;
3074   }
3075
3076   /**
3077    * Say smallPart = "op" and bigPart="open". Method returns true for "Ope" and false for "ops"
3078    */
3079   @Contract(pure = true)
3080   public static boolean isBetween(@NotNull String string, @NotNull String smallPart, @NotNull String bigPart) {
3081     final String s = string.toLowerCase();
3082     return s.startsWith(smallPart.toLowerCase()) && bigPart.toLowerCase().startsWith(s);
3083   }
3084
3085   @Contract(pure = true)
3086   public static String getShortened(@NotNull String s, int maxWidth) {
3087     int length = s.length();
3088     if (isEmpty(s) || length <= maxWidth) return s;
3089     ArrayList<String> words = new ArrayList<String>();
3090
3091     StringBuilder builder = new StringBuilder();
3092     for (int i = 0; i < length; i++) {
3093       char ch = s.charAt(i);
3094
3095       if (i == length - 1) {
3096         builder.append(ch);
3097         words.add(builder.toString());
3098         builder.delete(0, builder.length());
3099         continue;
3100       }
3101
3102       if (i > 0 && (ch == '/' || ch == '\\' || ch == '.' || ch == '-' || Character.isUpperCase(ch))) {
3103         words.add(builder.toString());
3104         builder.delete(0, builder.length());
3105       }
3106       builder.append(ch);
3107     }
3108     for (int i = 0; i < words.size(); i++) {
3109       String word = words.get(i);
3110       if (i < words.size() - 1 && word.length() == 1) {
3111         words.remove(i);
3112         words.set(i, word + words.get(i));
3113       }
3114     }
3115
3116     int removedLength = 0;
3117
3118     String toPaste = "...";
3119     int index;
3120     while (true) {
3121       index = Math.max(0, (words.size() - 1) / 2);
3122       String aWord = words.get(index);
3123       words.remove(index);
3124       int toCut = length - removedLength - maxWidth + 3;
3125       if (words.size() < 2 || (toCut < aWord.length() - 2 && removedLength == 0)) {
3126         int pos = (aWord.length() - toCut) / 2;
3127         toPaste = aWord.substring(0, pos) + "..." + aWord.substring(pos+toCut);
3128         break;
3129       }
3130       removedLength += aWord.length();
3131       if (length - removedLength <= maxWidth - 3) {
3132         break;
3133       }
3134     }
3135     for (int i = 0; i < Math.max(1, words.size()); i++) {
3136       String word = words.isEmpty() ? "" : words.get(i);
3137       if (i == index || words.size() == 1) builder.append(toPaste);
3138       builder.append(word);
3139     }
3140     return builder.toString().replaceAll("\\.{4,}", "...");
3141   }
3142
3143   /**
3144      * Does the string have an uppercase character?
3145      * @param s  the string to test.
3146      * @return   true if the string has an uppercase character, false if not.
3147      */
3148     public static boolean hasUpperCaseChar(String s) {
3149         char[] chars = s.toCharArray();
3150         for (char c : chars) {
3151             if (Character.isUpperCase(c)) {
3152                 return true;
3153             }
3154         }
3155         return false;
3156     }
3157
3158   /**
3159      * Does the string have a lowercase character?
3160      * @param s  the string to test.
3161      * @return   true if the string has a lowercase character, false if not.
3162      */
3163     public static boolean hasLowerCaseChar(String s) {
3164         char[] chars = s.toCharArray();
3165         for (char c : chars) {
3166             if (Character.isLowerCase(c)) {
3167                 return true;
3168             }
3169         }
3170         return false;
3171     }
3172
3173   /**
3174    * Expirable CharSequence. Very useful to control external library execution time,
3175    * i.e. when java.util.regex.Pattern match goes out of control.
3176    */
3177   public abstract static class BombedCharSequence implements CharSequence {
3178     private final CharSequence delegate;
3179     private int i = 0;
3180
3181     public BombedCharSequence(@NotNull CharSequence sequence) {
3182       delegate = sequence;
3183     }
3184
3185     @Override
3186     public int length() {
3187       check();
3188       return delegate.length();
3189     }
3190
3191     @Override
3192     public char charAt(int i) {
3193       check();
3194       return delegate.charAt(i);
3195     }
3196
3197     protected void check() {
3198       if ((++i & 1023) == 0) {
3199         checkCanceled();
3200       }
3201     }
3202
3203     @NotNull
3204     @Override
3205     public String toString() {
3206       check();
3207       return delegate.toString();
3208     }
3209
3210     protected abstract void checkCanceled();
3211
3212     @Override
3213     public CharSequence subSequence(int i, int i1) {
3214       check();
3215       return delegate.subSequence(i, i1);
3216     }
3217   }
3218 }