4f537d5b9daabbd6556036cada803f975abb6cf8
[idea/community.git] / platform / util / src / com / intellij / openapi / util / text / StringUtil.java
1 /*
2  * Copyright 2000-2013 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.CommonBundle;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.progress.ProcessCanceledException;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.TextRange;
23 import com.intellij.util.*;
24 import com.intellij.util.containers.ContainerUtil;
25 import com.intellij.util.text.CharArrayCharSequence;
26 import com.intellij.util.text.CharArrayUtil;
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_PATTERN = Pattern.compile(" *(\r|\n|\r\n)+ *");
46   @NonNls private static final Pattern EOL_SPLIT_PATTERN_WITH_EMPTY = Pattern.compile(" *(\r|\n|\r\n) *");
47
48   public static final NotNullFunction<String, String> QUOTER = new NotNullFunction<String, String>() {
49     @Override
50     @NotNull
51     public String fun(String s) {
52       return "\"" + s + "\"";
53     }
54   };
55
56   public static final NotNullFunction<String, String> SINGLE_QUOTER = new NotNullFunction<String, String>() {
57     @Override
58     @NotNull
59     public String fun(String s) {
60       return "'" + s + "'";
61     }
62   };
63
64   @NotNull
65   public static List<String> getWordsInStringLongestFirst(@NotNull String find) {
66     List<String> words = getWordsIn(find);
67     // hope long words are rare
68     Collections.sort(words, new Comparator<String>() {
69       @Override
70       public int compare(@NotNull final String o1, @NotNull final String o2) {
71         return o2.length() - o1.length();
72       }
73     });
74     return words;
75   }
76
77   @NotNull
78   public static String escapePattern(@NotNull final String text) {
79     return replace(replace(text, "'", "''"), "{", "'{'");
80   }
81
82   @NotNull
83   public static <T> Function<T, String> createToStringFunction(@NotNull Class<T> cls) {
84     return new Function<T, String>() {
85       @Override
86       public String fun(@NotNull T o) {
87         return o.toString();
88       }
89     };
90   }
91
92   @NotNull
93   public static Function<String, String> TRIMMER = new Function<String, String>() {
94     @Nullable
95     @Override
96     public String fun(@Nullable String s) {
97       return s == null ? null : s.trim();
98     }
99   };
100
101   @NotNull
102   public static String replace(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @Nullable String newS) {
103     return replace(text, oldS, newS, false);
104   }
105
106   @NotNull
107   public static String replaceIgnoreCase(@NotNull String text, @NotNull String oldS, @Nullable String newS) {
108     return replace(text, oldS, newS, true);
109   }
110
111   public static void replaceChar(@NotNull char[] buffer, char oldChar, char newChar, int start, int end) {
112     for (int i = start; i < end; i++) {
113       char c = buffer[i];
114       if (c == oldChar) {
115         buffer[i] = newChar;
116       }
117     }
118   }
119
120   @NotNull
121   public static String replaceChar(@NotNull String buffer, char oldChar, char newChar) {
122     StringBuilder newBuffer = null;
123     for (int i = 0; i < buffer.length(); i++) {
124       char c = buffer.charAt(i);
125       if (c == oldChar) {
126         if (newBuffer == null) {
127           newBuffer = new StringBuilder(buffer.length());
128           newBuffer.append(buffer, 0, i);
129         }
130
131         newBuffer.append(newChar);
132       }
133       else if (newBuffer != null) {
134         newBuffer.append(c);
135       }
136     }
137     return newBuffer == null ? buffer : newBuffer.toString();
138   }
139
140   public static String replace(@NotNull final String text, @NotNull final String oldS, @Nullable final String newS, boolean ignoreCase) {
141     if (text.length() < oldS.length()) return text;
142
143     final String text1 = ignoreCase ? text.toLowerCase() : text;
144     final String oldS1 = ignoreCase ? oldS.toLowerCase() : oldS;
145     StringBuilder newText = null;
146     int i = 0;
147
148     while (i < text1.length()) {
149       int i1 = text1.indexOf(oldS1, i);
150       if (i1 < 0) {
151         if (i == 0) return text;
152         newText.append(text, i, text.length());
153         break;
154       }
155       else {
156         if (newS == null) return null;
157         if (newText == null) newText = new StringBuilder(text.length() - i);
158         newText.append(text, i, i1);
159         newText.append(newS);
160         i = i1 + oldS.length();
161       }
162     }
163     return newText != null ? newText.toString() : "";
164   }
165
166   /**
167    * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
168    */
169   public static int indexOfIgnoreCase(@NotNull String where, @NotNull String what, int fromIndex) {
170     int targetCount = what.length();
171     int sourceCount = where.length();
172
173     if (fromIndex >= sourceCount) {
174       return targetCount == 0 ? sourceCount : -1;
175     }
176
177     if (fromIndex < 0) {
178       fromIndex = 0;
179     }
180
181     if (targetCount == 0) {
182       return fromIndex;
183     }
184
185     char first = what.charAt(0);
186     int max = sourceCount - targetCount;
187
188     for (int i = fromIndex; i <= max; i++) {
189       /* Look for first character. */
190       if (!charsEqualIgnoreCase(where.charAt(i), first)) {
191         while (++i <= max && !charsEqualIgnoreCase(where.charAt(i), first)) ;
192       }
193
194       /* Found first character, now look at the rest of v2 */
195       if (i <= max) {
196         int j = i + 1;
197         int end = j + targetCount - 1;
198         for (int k = 1; j < end && charsEqualIgnoreCase(where.charAt(j), what.charAt(k)); j++, k++) ;
199
200         if (j == end) {
201           /* Found whole string. */
202           return i;
203         }
204       }
205     }
206
207     return -1;
208   }
209
210   public static int indexOfIgnoreCase(@NotNull String where, char what, int fromIndex) {
211     int sourceCount = where.length();
212
213     if (fromIndex >= sourceCount) {
214       return -1;
215     }
216
217     if (fromIndex < 0) {
218       fromIndex = 0;
219     }
220
221     for (int i = fromIndex; i < sourceCount; i++) {
222       if (charsEqualIgnoreCase(where.charAt(i), what)) {
223         return i;
224       }
225     }
226
227     return -1;
228   }
229
230   public static boolean containsIgnoreCase(@NotNull String where, @NotNull String what) {
231     return indexOfIgnoreCase(where, what, 0) >= 0;
232   }
233
234   public static boolean endsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String suffix) {
235     final int stringLength = str.length();
236     final int suffixLength = suffix.length();
237     return stringLength >= suffixLength && str.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength);
238   }
239
240   public static boolean startsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String prefix) {
241     return StringUtilRt.startsWithIgnoreCase(str, prefix);
242   }
243
244   public static String stripHtml(@NotNull String html, boolean convertBreaks) {
245     if (convertBreaks) {
246       html = html.replaceAll("<br/?>", "\n\n");
247     }
248
249     return html.replaceAll("<(.|\n)*?>", "");
250   }
251
252   @Nullable
253   public static String toLowerCase(@Nullable final String str) {
254     //noinspection ConstantConditions
255     return str == null ? null : str.toLowerCase();
256   }
257
258   @NotNull
259   public static String getPackageName(@NotNull String fqName) {
260     return getPackageName(fqName, '.');
261   }
262
263   @NotNull
264   public static String getPackageName(@NotNull String fqName, char separator) {
265     int lastPointIdx = fqName.lastIndexOf(separator);
266     if (lastPointIdx >= 0) {
267       return fqName.substring(0, lastPointIdx);
268     }
269     return "";
270   }
271
272   public static int getLineBreakCount(@NotNull CharSequence text) {
273     int count = 0;
274     for (int i = 0; i < text.length(); i++) {
275       char c = text.charAt(i);
276       if (c == '\n') {
277         count++;
278       }
279       else if (c == '\r') {
280         if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
281           //noinspection AssignmentToForLoopParameter
282           i++;
283           count++;
284         }
285         else {
286           count++;
287         }
288       }
289     }
290     return count;
291   }
292
293   public static boolean containsLineBreak(@NotNull CharSequence text) {
294     for (int i = 0; i < text.length(); i++) {
295       char c = text.charAt(i);
296       if (isLineBreak(c)) return true;
297     }
298     return false;
299   }
300
301   public static boolean isLineBreak(char c) {
302     return c == '\n' || c == '\r';
303   }
304
305   @NotNull
306   public static String escapeLineBreak(@NotNull String text) {
307     StringBuilder buffer = new StringBuilder(text.length());
308     for (int i = 0; i < text.length(); i++) {
309       char c = text.charAt(i);
310       switch (c) {
311         case '\n': buffer.append("\\n"); break;
312         case '\r': buffer.append("\\r"); break;
313         default: buffer.append(c);
314       }
315     }
316     return buffer.toString();
317   }
318
319   public static boolean endsWithLineBreak(@NotNull CharSequence text) {
320     int len = text.length();
321     return len > 0 && isLineBreak(text.charAt(len - 1));
322   }
323
324   public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
325     int curLine = 0;
326     int offset = 0;
327     while (line != curLine) {
328       if (offset == text.length()) return -1;
329       char c = text.charAt(offset);
330       if (c == '\n') {
331         curLine++;
332       }
333       else if (c == '\r') {
334         curLine++;
335         if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
336           offset++;
337         }
338       }
339       offset++;
340     }
341     return offset + col;
342   }
343
344   public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
345     int curLine = 0;
346     int curOffset = 0;
347     while (curOffset < offset) {
348       if (curOffset == text.length()) return -1;
349       char c = text.charAt(curOffset);
350       if (c == '\n') {
351         curLine++;
352       }
353       else if (c == '\r') {
354         curLine++;
355         if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
356           curOffset++;
357         }
358       }
359       curOffset++;
360     }
361     return curLine;
362   }
363
364   /**
365    * Classic dynamic programming algorithm for string differences.
366    */
367   public static int difference(@NotNull String s1, @NotNull String s2) {
368     int[][] a = new int[s1.length()][s2.length()];
369
370     for (int i = 0; i < s1.length(); i++) {
371       a[i][0] = i;
372     }
373
374     for (int j = 0; j < s2.length(); j++) {
375       a[0][j] = j;
376     }
377
378     for (int i = 1; i < s1.length(); i++) {
379       for (int j = 1; j < s2.length(); j++) {
380
381         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);
382       }
383     }
384
385     return a[s1.length() - 1][s2.length() - 1];
386   }
387
388   @NotNull
389   public static String wordsToBeginFromUpperCase(@NotNull String s) {
390     return toTitleCase(s, ourPrepositions);
391   }
392
393   @NotNull
394   public static String toTitleCase(@NotNull String s) {
395     return toTitleCase(s, ArrayUtil.EMPTY_STRING_ARRAY);
396   }
397
398   @NotNull
399   private static String toTitleCase(@NotNull String s, @NotNull String[] prepositions) {
400     StringBuilder buffer = null;
401     for (int i = 0; i < s.length(); i++) {
402       char prevChar = i == 0 ? ' ' : s.charAt(i - 1);
403       char currChar = s.charAt(i);
404       if (!Character.isLetterOrDigit(prevChar) && prevChar != '\'') {
405         if (Character.isLetterOrDigit(currChar)) {
406           if (!Character.isUpperCase(currChar)) {
407             int j = i;
408             for (; j < s.length(); j++) {
409               if (!Character.isLetterOrDigit(s.charAt(j))) {
410                 break;
411               }
412             }
413             if (!isPreposition(s, i, j - 1, prepositions)) {
414               if (buffer == null) {
415                 buffer = new StringBuilder(s);
416               }
417               buffer.setCharAt(i, toUpperCase(currChar));
418             }
419           }
420         }
421       }
422     }
423     if (buffer == null) {
424       return s;
425     }
426     else {
427       return buffer.toString();
428     }
429   }
430
431   @NonNls private static final String[] ourPrepositions = {"at", "the", "and", "not", "if", "a", "or", "to", "in", "on", "into", "by"};
432
433
434   public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar) {
435     return isPreposition(s, firstChar, lastChar, ourPrepositions);
436   }
437
438   public static boolean isPreposition(@NotNull String s, int firstChar, int lastChar, @NotNull String[] prepositions) {
439     for (String preposition : prepositions) {
440       boolean found = false;
441       if (lastChar - firstChar + 1 == preposition.length()) {
442         found = true;
443         for (int j = 0; j < preposition.length(); j++) {
444           if (!(toLowerCase(s.charAt(firstChar + j)) == preposition.charAt(j))) {
445             found = false;
446           }
447         }
448       }
449       if (found) {
450         return true;
451       }
452     }
453     return false;
454   }
455
456   @NotNull
457   public static NotNullFunction<String, String> escaper(final boolean escapeSlash, @Nullable final String additionalChars) {
458     return new NotNullFunction<String, String>() {
459       @NotNull
460       @Override
461       public String fun(@NotNull String dom) {
462         final StringBuilder builder = new StringBuilder(dom.length());
463         escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder);
464         return builder.toString();
465       }
466     };
467   }
468
469
470   public static void escapeStringCharacters(int length, @NotNull String str, @NotNull @NonNls StringBuilder buffer) {
471     escapeStringCharacters(length, str, "\"", buffer);
472   }
473
474   @NotNull
475   public static StringBuilder escapeStringCharacters(int length,
476                                                      @NotNull String str,
477                                                      @Nullable String additionalChars,
478                                                      @NotNull @NonNls StringBuilder buffer) {
479     return escapeStringCharacters(length, str, additionalChars, true, buffer);
480   }
481
482   @NotNull
483   public static StringBuilder escapeStringCharacters(int length,
484                                                      @NotNull String str,
485                                                      @Nullable String additionalChars,
486                                                      boolean escapeSlash,
487                                                      @NotNull @NonNls StringBuilder buffer) {
488     char prev = 0;
489     for (int idx = 0; idx < length; idx++) {
490       char ch = str.charAt(idx);
491       switch (ch) {
492         case '\b':
493           buffer.append("\\b");
494           break;
495
496         case '\t':
497           buffer.append("\\t");
498           break;
499
500         case '\n':
501           buffer.append("\\n");
502           break;
503
504         case '\f':
505           buffer.append("\\f");
506           break;
507
508         case '\r':
509           buffer.append("\\r");
510           break;
511
512         default:
513           if (escapeSlash && ch == '\\') {
514             buffer.append("\\\\");
515           }
516           else if (additionalChars != null && additionalChars.indexOf(ch) > -1 && (escapeSlash || prev != '\\')) {
517             buffer.append("\\").append(ch);
518           }
519           else if (Character.isISOControl(ch)) {
520             String hexCode = StringUtilRt.toUpperCase(Integer.toHexString(ch));
521             buffer.append("\\u");
522             int paddingCount = 4 - hexCode.length();
523             while (paddingCount-- > 0) {
524               buffer.append(0);
525             }
526             buffer.append(hexCode);
527           }
528           else {
529             buffer.append(ch);
530           }
531       }
532       prev = ch;
533     }
534     return buffer;
535   }
536
537   @NotNull
538   public static String escapeStringCharacters(@NotNull String s) {
539     StringBuilder buffer = new StringBuilder(s.length());
540     escapeStringCharacters(s.length(), s, buffer);
541     return buffer.toString();
542   }
543
544
545   @NotNull
546   public static String unescapeStringCharacters(@NotNull String s) {
547     StringBuilder buffer = new StringBuilder(s.length());
548     unescapeStringCharacters(s.length(), s, buffer);
549     return buffer.toString();
550   }
551
552   @NotNull
553   public static String unquoteString(@NotNull String s) {
554     char c;
555     if (s.length() <= 1 || (c = s.charAt(0)) != '"' && c != '\'' || s.charAt(s.length() - 1) != c) {
556       return s;
557     }
558     return s.substring(1, s.length() - 1);
559   }
560
561   /**
562    * This is just an optimized version of Matcher.quoteReplacement
563    */
564   @NotNull
565   public static String quoteReplacement(@NotNull String s) {
566     boolean needReplacements = false;
567
568     for (int i = 0; i < s.length(); i++) {
569       char c = s.charAt(i);
570       if (c == '\\' || c == '$') {
571         needReplacements = true;
572         break;
573       }
574     }
575
576     if (!needReplacements) return s;
577
578     StringBuilder sb = new StringBuilder(s.length() * 6 / 5);
579     for (int i = 0; i < s.length(); i++) {
580       char c = s.charAt(i);
581       if (c == '\\') {
582         sb.append('\\');
583         sb.append('\\');
584       }
585       else if (c == '$') {
586         sb.append('\\');
587         sb.append('$');
588       }
589       else {
590         sb.append(c);
591       }
592     }
593     return sb.toString();
594   }
595
596   private static void unescapeStringCharacters(int length, @NotNull String s, @NotNull StringBuilder buffer) {
597     boolean escaped = false;
598     for (int idx = 0; idx < length; idx++) {
599       char ch = s.charAt(idx);
600       if (!escaped) {
601         if (ch == '\\') {
602           escaped = true;
603         }
604         else {
605           buffer.append(ch);
606         }
607       }
608       else {
609         switch (ch) {
610           case 'n':
611             buffer.append('\n');
612             break;
613
614           case 'r':
615             buffer.append('\r');
616             break;
617
618           case 'b':
619             buffer.append('\b');
620             break;
621
622           case 't':
623             buffer.append('\t');
624             break;
625
626           case 'f':
627             buffer.append('\f');
628             break;
629
630           case '\'':
631             buffer.append('\'');
632             break;
633
634           case '\"':
635             buffer.append('\"');
636             break;
637
638           case '\\':
639             buffer.append('\\');
640             break;
641
642           case 'u':
643             if (idx + 4 < length) {
644               try {
645                 int code = Integer.parseInt(s.substring(idx + 1, idx + 5), 16);
646                 idx += 4;
647                 buffer.append((char)code);
648               }
649               catch (NumberFormatException e) {
650                 buffer.append("\\u");
651               }
652             }
653             else {
654               buffer.append("\\u");
655             }
656             break;
657
658           default:
659             buffer.append(ch);
660             break;
661         }
662         escaped = false;
663       }
664     }
665
666     if (escaped) buffer.append('\\');
667   }
668
669   @SuppressWarnings({"HardCodedStringLiteral"})
670   @NotNull
671   public static String pluralize(@NotNull String suggestion) {
672     if (suggestion.endsWith("Child") || suggestion.endsWith("child")) {
673       return suggestion + "ren";
674     }
675
676     if (suggestion.equals("this")) {
677       return "these";
678     }
679     if (suggestion.equals("This")) {
680       return "These";
681     }
682
683     if (endsWithIgnoreCase(suggestion, "s") || endsWithIgnoreCase(suggestion, "x") || endsWithIgnoreCase(suggestion, "ch")) {
684       return suggestion + "es";
685     }
686
687     int len = suggestion.length();
688     if (endsWithIgnoreCase(suggestion, "y") && len > 1 && !isVowel(toLowerCase(suggestion.charAt(len - 2)))) {
689       return suggestion.substring(0, len - 1) + "ies";
690     }
691
692     return suggestion + "s";
693   }
694
695   @NotNull
696   public static String capitalizeWords(@NotNull String text,
697                                        boolean allWords) {
698     return capitalizeWords(text, " \t\n\r\f", allWords, false);
699   }
700
701   @NotNull
702   public static String capitalizeWords(@NotNull String text,
703                                        @NotNull String tokenizerDelim,
704                                        boolean allWords,
705                                        boolean leaveOriginalDelims) {
706     final StringTokenizer tokenizer = new StringTokenizer(text, tokenizerDelim, leaveOriginalDelims);
707     final StringBuilder out = new StringBuilder(text.length());
708     boolean toCapitalize = true;
709     while (tokenizer.hasMoreTokens()) {
710       final String word = tokenizer.nextToken();
711       if (!leaveOriginalDelims && out.length() > 0) {
712         out.append(' ');
713       }
714       out.append(toCapitalize ? capitalize(word) : word);
715       if (!allWords) {
716         toCapitalize = false;
717       }
718     }
719     return out.toString();
720   }
721
722   public static String decapitalize(String s) {
723     return Introspector.decapitalize(s);
724   }
725
726   public static boolean isVowel(char c) {
727     return VOWELS.indexOf(c) >= 0;
728   }
729
730   @NotNull
731   public static String capitalize(@NotNull String s) {
732     if (s.isEmpty()) return s;
733     if (s.length() == 1) return StringUtilRt.toUpperCase(s);
734
735     // Optimization
736     if (Character.isUpperCase(s.charAt(0))) return s;
737     return toUpperCase(s.charAt(0)) + s.substring(1);
738   }
739
740   @Contract("null -> false")
741   public static boolean isCapitalized(@Nullable String s) {
742     return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0));
743   }
744
745   @NotNull
746   public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
747     if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
748       return s;
749     }
750     return capitalize(s);
751   }
752
753   public static int stringHashCode(@NotNull CharSequence chars) {
754     if (chars instanceof String) return chars.hashCode();
755     if (chars instanceof CharSequenceWithStringHash) return chars.hashCode();
756     if (chars instanceof CharArrayCharSequence) return chars.hashCode();
757
758     return stringHashCode(chars, 0, chars.length());
759   }
760
761   public static int stringHashCode(@NotNull CharSequence chars, int from, int to) {
762     int h = 0;
763     for (int off = from; off < to; off++) {
764       h = 31 * h + chars.charAt(off);
765     }
766     return h;
767   }
768
769   public static int stringHashCode(char[] chars, int from, int to) {
770     int h = 0;
771     for (int off = from; off < to; off++) {
772       h = 31 * h + chars[off];
773     }
774     return h;
775   }
776
777   public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) {
778     int h = 0;
779     for (int off = from; off < to; off++) {
780       h = 31 * h + toLowerCase(chars[off]);
781     }
782     return h;
783   }
784
785   public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) {
786     int h = 0;
787     for (int off = from; off < to; off++) {
788       h = 31 * h + toLowerCase(chars.charAt(off));
789     }
790     return h;
791   }
792
793   public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
794     return stringHashCodeInsensitive(chars, 0, chars.length());
795   }
796
797   /**
798    * Equivalent to string.startsWith(prefixes[0] + prefixes[1] + ...) but avoids creating an object for concatenation.
799    */
800   public static boolean startsWithConcatenation(@NotNull String string, @NotNull String... prefixes) {
801     int offset = 0;
802     for (String prefix : prefixes) {
803       int prefixLen = prefix.length();
804       if (!string.regionMatches(offset, prefix, 0, prefixLen)) {
805         return false;
806       }
807       offset += prefixLen;
808     }
809     return true;
810   }
811
812   /** @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14). */
813   @SuppressWarnings("UnusedDeclaration")
814   public static boolean startsWithConcatenationOf(@NotNull String string, @NotNull String firstPrefix, @NotNull String secondPrefix) {
815     return startsWithConcatenation(string, firstPrefix, secondPrefix);
816   }
817
818   /** @deprecated use {@link #startsWithConcatenation(String, String...)} (to remove in IDEA 14). */
819   @SuppressWarnings("UnusedDeclaration")
820   public static boolean startsWithConcatenationOf(@NotNull String string,
821                                                   @NotNull String firstPrefix,
822                                                   @NotNull String secondPrefix,
823                                                   @NotNull String thirdPrefix) {
824     return startsWithConcatenation(string, firstPrefix, secondPrefix, thirdPrefix);
825   }
826
827   @NotNull
828   public static String trimEnd(@NotNull String s, @NonNls @NotNull String suffix) {
829     if (s.endsWith(suffix)) {
830       return s.substring(0, s.length() - suffix.length());
831     }
832     return s;
833   }
834
835   @NotNull
836   public static String trimLog(@NotNull final String text, final int limit) {
837     if (limit > 5 && text.length() > limit) {
838       return text.substring(0, limit - 5) + " ...\n";
839     }
840     return text;
841   }
842
843   @NotNull
844   public static String trimLeading(@NotNull String string) {
845     int index = 0;
846     while (index < string.length() && Character.isWhitespace(string.charAt(index))) index++;
847     return string.substring(index);
848   }
849
850   @NotNull
851   public static String trimTrailing(@NotNull String string) {
852     int index = string.length() - 1;
853     while (index >= 0 && Character.isWhitespace(string.charAt(index))) index--;
854     return string.substring(0, index + 1);
855   }
856
857   public static boolean startsWithChar(@Nullable CharSequence s, char prefix) {
858     return s != null && s.length() != 0 && s.charAt(0) == prefix;
859   }
860
861   public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
862     return StringUtilRt.endsWithChar(s, suffix);
863   }
864
865   @NotNull
866   public static String trimStart(@NotNull String s, @NonNls @NotNull String prefix) {
867     if (s.startsWith(prefix)) {
868       return s.substring(prefix.length());
869     }
870     return s;
871   }
872
873   @NotNull
874   public static String pluralize(@NotNull String base, int n) {
875     if (n == 1) return base;
876     return pluralize(base);
877   }
878
879   public static void repeatSymbol(@NotNull Appendable buffer, char symbol, int times) {
880     assert times >= 0 : times;
881     try {
882       for (int i = 0; i < times; i++) {
883         buffer.append(symbol);
884       }
885     }
886     catch (IOException e) {
887       LOG.error(e);
888     }
889   }
890
891   @Contract("null -> false")
892   public static boolean isNotEmpty(@Nullable String s) {
893     return s != null && !s.isEmpty();
894   }
895
896   @Contract("null -> true")
897   public static boolean isEmpty(@Nullable String s) {
898     return s == null || s.isEmpty();
899   }
900
901   @Contract("null -> true")
902   public static boolean isEmpty(@Nullable CharSequence cs) {
903     return cs == null || cs.length() == 0;
904   }
905
906   public static int length(@Nullable CharSequence cs) {
907     return cs == null ? 0 : cs.length();
908   }
909
910   @NotNull
911   public static String notNullize(@Nullable final String s) {
912     return notNullize(s, "");
913   }
914
915   @NotNull
916   public static String notNullize(@Nullable final String s, @NotNull String defaultValue) {
917     return s == null ? defaultValue : s;
918   }
919
920   @Nullable
921   public static String nullize(@Nullable final String s) {
922     return nullize(s, false);
923   }
924
925   @Nullable
926   public static String nullize(@Nullable final String s, boolean nullizeSpaces) {
927     if (nullizeSpaces) {
928       if (isEmptyOrSpaces(s)) return null;
929     }
930     else {
931       if (isEmpty(s)) return null;
932     }
933     return s;
934   }
935
936   @Contract("null -> true")
937   public static boolean isEmptyOrSpaces(@Nullable final String s) {
938     if(s == null || s.isEmpty()) {
939       return true;
940     }
941     for(int i = 0; i < s.length(); i++) {
942       if(s.charAt(i) > ' ') {
943         return false;
944       }
945     }
946     return true;
947   }
948
949   /**
950    * Allows to answer if given symbol is white space, tabulation or line feed.
951    *
952    * @param c symbol to check
953    * @return <code>true</code> if given symbol is white space, tabulation or line feed; <code>false</code> otherwise
954    */
955   public static boolean isWhiteSpace(char c) {
956     return c == '\n' || c == '\t' || c == ' ';
957   }
958
959   @NotNull
960   public static String getThrowableText(@NotNull Throwable aThrowable) {
961     return ExceptionUtil.getThrowableText(aThrowable);
962   }
963
964   @NotNull
965   public static String getThrowableText(@NotNull Throwable aThrowable, @NonNls @NotNull final String stackFrameSkipPattern) {
966     return ExceptionUtil.getThrowableText(aThrowable, stackFrameSkipPattern);
967   }
968
969   @Nullable
970   public static String getMessage(@NotNull Throwable e) {
971     return ExceptionUtil.getMessage(e);
972   }
973
974   @NotNull
975   public static String repeatSymbol(final char aChar, final int count) {
976     char[] buffer = new char[count];
977     Arrays.fill(buffer, aChar);
978     return StringFactory.createShared(buffer);
979   }
980
981   @NotNull
982   public static String repeat(@NotNull String s, int count) {
983     assert count >=0 : count;
984     StringBuilder sb = new StringBuilder(s.length() * count);
985     for (int i = 0; i < count; i++) {
986       sb.append(s);
987     }
988     return sb.toString();
989   }
990
991   @NotNull
992   public static List<String> splitHonorQuotes(@NotNull String s, char separator) {
993     final List<String> result = new ArrayList<String>();
994     final StringBuilder builder = new StringBuilder(s.length());
995     boolean inQuotes = false;
996     for (int i = 0; i < s.length(); i++) {
997       final char c = s.charAt(i);
998       if (c == separator && !inQuotes) {
999         if (builder.length() > 0) {
1000           result.add(builder.toString());
1001           builder.setLength(0);
1002         }
1003         continue;
1004       }
1005
1006       if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
1007         inQuotes = !inQuotes;
1008       }
1009       builder.append(c);
1010     }
1011
1012     if (builder.length() > 0) {
1013       result.add(builder.toString());
1014     }
1015     return result;
1016   }
1017
1018
1019   @NotNull
1020   public static List<String> split(@NotNull String s, @NotNull String separator) {
1021     return split(s, separator, true);
1022   }
1023
1024   @NotNull
1025   public static List<String> split(@NotNull String s, @NotNull String separator,
1026                                    boolean excludeSeparator) {
1027     return split(s, separator, excludeSeparator, true);
1028   }
1029
1030   @NotNull
1031   public static List<String> split(@NotNull String s, @NotNull String separator,
1032                                    boolean excludeSeparator, boolean excludeEmptyStrings) {
1033     if (separator.isEmpty()) {
1034       return Collections.singletonList(s);
1035     }
1036     List<String> result = new ArrayList<String>();
1037     int pos = 0;
1038     while (true) {
1039       int index = s.indexOf(separator, pos);
1040       if (index == -1) break;
1041       final int nextPos = index + separator.length();
1042       String token = s.substring(pos, excludeSeparator ? index : nextPos);
1043       if (!token.isEmpty() || !excludeEmptyStrings) {
1044         result.add(token);
1045       }
1046       pos = nextPos;
1047     }
1048     if (pos < s.length() || !excludeEmptyStrings && pos == s.length()) {
1049       result.add(s.substring(pos, s.length()));
1050     }
1051     return result;
1052   }
1053
1054   @NotNull
1055   public static Iterable<String> tokenize(@NotNull String s, @NotNull String separators) {
1056     final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators);
1057     return new Iterable<String>() {
1058       @NotNull
1059       @Override
1060       public Iterator<String> iterator() {
1061         return new Iterator<String>() {
1062           @Override
1063           public boolean hasNext() {
1064             return tokenizer.hasMoreTokens();
1065           }
1066
1067           @Override
1068           public String next() {
1069             return tokenizer.nextToken();
1070           }
1071
1072           @Override
1073           public void remove() {
1074             throw new UnsupportedOperationException();
1075           }
1076         };
1077       }
1078     };
1079   }
1080
1081   @NotNull
1082   public static Iterable<String> tokenize(@NotNull final StringTokenizer tokenizer) {
1083     return new Iterable<String>() {
1084       @NotNull
1085       @Override
1086       public Iterator<String> iterator() {
1087         return new Iterator<String>() {
1088           @Override
1089           public boolean hasNext() {
1090             return tokenizer.hasMoreTokens();
1091           }
1092
1093           @Override
1094           public String next() {
1095             return tokenizer.nextToken();
1096           }
1097
1098           @Override
1099           public void remove() {
1100             throw new UnsupportedOperationException();
1101           }
1102         };
1103       }
1104     };
1105   }
1106
1107   @NotNull
1108   public static List<String> getWordsIn(@NotNull String text) {
1109     List<String> result = new SmartList<String>();
1110     int start = -1;
1111     for (int i = 0; i < text.length(); i++) {
1112       char c = text.charAt(i);
1113       boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1114       if (isIdentifierPart && start == -1) {
1115         start = i;
1116       }
1117       if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1118         result.add(text.substring(start, i + 1));
1119       }
1120       else if (!isIdentifierPart && start != -1) {
1121         result.add(text.substring(start, i));
1122         start = -1;
1123       }
1124     }
1125     return result;
1126   }
1127
1128   @NotNull
1129   public static List<TextRange> getWordIndicesIn(@NotNull String text) {
1130     List<TextRange> result = new SmartList<TextRange>();
1131     int start = -1;
1132     for (int i = 0; i < text.length(); i++) {
1133       char c = text.charAt(i);
1134       boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
1135       if (isIdentifierPart && start == -1) {
1136         start = i;
1137       }
1138       if (isIdentifierPart && i == text.length() - 1 && start != -1) {
1139         result.add(new TextRange(start, i + 1));
1140       }
1141       else if (!isIdentifierPart && start != -1) {
1142         result.add(new TextRange(start, i));
1143         start = -1;
1144       }
1145     }
1146     return result;
1147   }
1148
1149   @NotNull
1150   public static String join(@NotNull final String[] strings, @NotNull final String separator) {
1151     return join(strings, 0, strings.length, separator);
1152   }
1153
1154   @NotNull
1155   public static String join(@NotNull final String[] strings, int startIndex, int endIndex, @NotNull final String separator) {
1156     final StringBuilder result = new StringBuilder();
1157     for (int i = startIndex; i < endIndex; i++) {
1158       if (i > startIndex) result.append(separator);
1159       result.append(strings[i]);
1160     }
1161     return result.toString();
1162   }
1163
1164   @NotNull
1165   public static String[] zip(@NotNull String[] strings1, @NotNull String[] strings2, String separator) {
1166     if (strings1.length != strings2.length) throw new IllegalArgumentException();
1167
1168     String[] result = ArrayUtil.newStringArray(strings1.length);
1169     for (int i = 0; i < result.length; i++) {
1170       result[i] = strings1[i] + separator + strings2[i];
1171     }
1172
1173     return result;
1174   }
1175
1176   @NotNull
1177   public static String[] surround(@NotNull String[] strings1, String prefix, String suffix) {
1178     String[] result = ArrayUtil.newStringArray(strings1.length);
1179     for (int i = 0; i < result.length; i++) {
1180       result[i] = prefix + strings1[i] + suffix;
1181     }
1182
1183     return result;
1184   }
1185
1186   @NotNull
1187   public static <T> String join(@NotNull T[] items, @NotNull Function<T, String> f, @NotNull @NonNls String separator) {
1188     return join(Arrays.asList(items), f, separator);
1189   }
1190
1191   @NotNull
1192   public static <T> String join(@NotNull Collection<? extends T> items, @NotNull Function<? super T, String> f, @NotNull @NonNls String separator) {
1193     if (items.isEmpty()) return "";
1194     return join((Iterable<? extends T>)items, f, separator);
1195   }
1196
1197   public static String join(@NotNull Iterable<?> items, @NotNull @NonNls String separator) {
1198     StringBuilder result = new StringBuilder();
1199     for (Object item : items) {
1200       result.append(item).append(separator);
1201     }
1202     if (result.length() > 0) {
1203       result.setLength(result.length() - separator.length());
1204     }
1205     return result.toString();
1206   }
1207
1208   @NotNull
1209   public static <T> String join(@NotNull Iterable<? extends T> items, @NotNull Function<? super T, String> f, @NotNull @NonNls String separator) {
1210     final StringBuilder result = new StringBuilder();
1211     for (T item : items) {
1212       String string = f.fun(item);
1213       if (string != null && !string.isEmpty()) {
1214         if (result.length() != 0) result.append(separator);
1215         result.append(string);
1216       }
1217     }
1218     return result.toString();
1219   }
1220
1221   @NotNull
1222   public static String join(@NotNull Collection<? extends String> strings, @NotNull String separator) {
1223     StringBuilder result = new StringBuilder();
1224     join(strings, separator, result);
1225     return result.toString();
1226   }
1227
1228   public static void join(@NotNull Collection<? extends String> strings, @NotNull String separator, @NotNull StringBuilder result) {
1229     boolean isFirst = true;
1230     for (String string : strings) {
1231       if (string != null && !string.isEmpty()) {
1232         if (isFirst) {
1233           isFirst = false;
1234         }
1235         else {
1236           result.append(separator);
1237         }
1238         result.append(string);
1239       }
1240     }
1241   }
1242
1243   @NotNull
1244   public static String join(@NotNull final int[] strings, @NotNull final String separator) {
1245     final StringBuilder result = new StringBuilder();
1246     for (int i = 0; i < strings.length; i++) {
1247       if (i > 0) result.append(separator);
1248       result.append(strings[i]);
1249     }
1250     return result.toString();
1251   }
1252
1253   @NotNull
1254   public static String join(@Nullable final String... strings) {
1255     if (strings == null || strings.length == 0) return "";
1256
1257     final StringBuilder builder = new StringBuilder();
1258     for (final String string : strings) {
1259       builder.append(string);
1260     }
1261     return builder.toString();
1262   }
1263
1264   @NotNull
1265   public static String stripQuotesAroundValue(@NotNull String text) {
1266     if (startsWithChar(text, '\"') || startsWithChar(text, '\'')) text = text.substring(1);
1267     if (endsWithChar(text, '\"') || endsWithChar(text, '\'')) text = text.substring(0, text.length() - 1);
1268     return text;
1269   }
1270
1271   public static boolean isQuotedString(@NotNull String text) {
1272     return startsWithChar(text, '\"') && endsWithChar(text, '\"')
1273            || startsWithChar(text, '\'') && endsWithChar(text, '\'');
1274   }
1275
1276   /**
1277    * Formats the specified file size as a string.
1278    *
1279    * @param fileSize the size to format.
1280    * @return the size formatted as a string.
1281    * @since 5.0.1
1282    */
1283   @NotNull
1284   public static String formatFileSize(final long fileSize) {
1285     if (fileSize < 0x400) {
1286       return CommonBundle.message("format.file.size.bytes", fileSize);
1287     }
1288     if (fileSize < 0x100000) {
1289       long kbytes = fileSize * 100 / 1024;
1290       final String kbs = kbytes / 100 + "." + formatMinor(kbytes % 100);
1291       return CommonBundle.message("format.file.size.kbytes", kbs);
1292     }
1293     long mbytes = fileSize * 100 / 1024 / 1024;
1294     final String size = mbytes / 100 + "." + formatMinor(mbytes % 100);
1295     return CommonBundle.message("format.file.size.mbytes", size);
1296   }
1297
1298   @NotNull
1299   public static String formatDuration(long duration) {
1300     final long minutes = duration / 60000;
1301     final long seconds = ((duration + 500L) % 60000) / 1000;
1302     if (minutes > 0L) {
1303       return minutes + " min " + seconds + " sec";
1304     }
1305     return seconds + " sec";
1306   }
1307
1308   @NotNull
1309   private static String formatMinor(long number) {
1310     if (number > 0L && number <= 9L) {
1311       return "0" + number;
1312     }
1313     return String.valueOf(number);
1314   }
1315
1316   /**
1317    * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1318    * Returns <code>null</code> if failed to match appropriate heuristic.
1319    *
1320    * @param name english word in plural form
1321    * @return name in singular form or <code>null</code> if failed to find one.
1322    */
1323   @SuppressWarnings({"HardCodedStringLiteral"})
1324   @Nullable
1325   public static String unpluralize(@NotNull final String name) {
1326     if (name.endsWith("sses") || name.endsWith("shes") || name.endsWith("ches") || name.endsWith("xes")) { //?
1327       return name.substring(0, name.length() - 2);
1328     }
1329
1330     if (name.endsWith("ses")) {
1331       return name.substring(0, name.length() - 1);
1332     }
1333
1334     if (name.endsWith("ies")) {
1335       if (name.endsWith("cookies") || name.endsWith("Cookies")) {
1336         return name.substring(0, name.length() - "ookies".length()) + "ookie";
1337       }
1338
1339       return name.substring(0, name.length() - 3) + "y";
1340     }
1341
1342     if (name.endsWith("leaves") || name.endsWith("Leaves")) {
1343       return name.substring(0, name.length() - "eaves".length()) + "eaf";
1344     }
1345
1346     String result = stripEnding(name, "s");
1347     if (result != null) {
1348       return result;
1349     }
1350
1351     if (name.endsWith("children")) {
1352       return name.substring(0, name.length() - "children".length()) + "child";
1353     }
1354
1355     if (name.endsWith("Children") && name.length() > "Children".length()) {
1356       return name.substring(0, name.length() - "Children".length()) + "Child";
1357     }
1358
1359
1360     return null;
1361   }
1362
1363   @Nullable
1364   private static String stripEnding(@NotNull String name, @NotNull String ending) {
1365     if (name.endsWith(ending)) {
1366       if (name.equals(ending)) return name; // do not return empty string
1367       return name.substring(0, name.length() - 1);
1368     }
1369     return null;
1370   }
1371
1372   public static boolean containsAlphaCharacters(@NotNull String value) {
1373     for (int i = 0; i < value.length(); i++) {
1374       if (Character.isLetter(value.charAt(i))) return true;
1375     }
1376     return false;
1377   }
1378
1379   public static boolean containsAnyChar(@NotNull final String value, @NotNull final String chars) {
1380     if (chars.length() > value.length()) {
1381       return containsAnyChar(value, chars, 0, value.length());
1382     }
1383     else {
1384       return containsAnyChar(chars, value, 0, chars.length());
1385     }
1386   }
1387
1388   public static boolean containsAnyChar(@NotNull final String value,
1389                                         @NotNull final String chars,
1390                                         final int start, final int end) {
1391     for (int i = start; i < end; i++) {
1392       if (chars.indexOf(value.charAt(i)) >= 0) {
1393         return true;
1394       }
1395     }
1396
1397     return false;
1398   }
1399
1400   public static boolean containsChar(@NotNull final String value, final char ch) {
1401     return value.indexOf(ch) >= 0;
1402   }
1403
1404   /**
1405    * @deprecated use #capitalize(String)
1406    */
1407   @Nullable
1408   public static String firstLetterToUpperCase(@Nullable final String displayString) {
1409     if (displayString == null || displayString.isEmpty()) return displayString;
1410     char firstChar = displayString.charAt(0);
1411     char uppedFirstChar = toUpperCase(firstChar);
1412
1413     if (uppedFirstChar == firstChar) return displayString;
1414
1415     char[] buffer = displayString.toCharArray();
1416     buffer[0] = uppedFirstChar;
1417     return StringFactory.createShared(buffer);
1418   }
1419
1420   /**
1421    * Strip out all characters not accepted by given filter
1422    *
1423    * @param s      e.g. "/n    my string "
1424    * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1425    * @return stripped string e.g. "mystring"
1426    */
1427   @NotNull
1428   public static String strip(@NotNull final String s, @NotNull final CharFilter filter) {
1429     final StringBuilder result = new StringBuilder(s.length());
1430     for (int i = 0; i < s.length(); i++) {
1431       char ch = s.charAt(i);
1432       if (filter.accept(ch)) {
1433         result.append(ch);
1434       }
1435     }
1436     return result.toString();
1437   }
1438
1439   @NotNull
1440   public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern) {
1441     return findMatches(s, pattern, 1);
1442   }
1443
1444   @NotNull
1445   public static List<String> findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) {
1446     List<String> result = new SmartList<String>();
1447     Matcher m = pattern.matcher(s);
1448     while (m.find()) {
1449       String group = m.group(groupIndex);
1450       if (group != null) {
1451         result.add(group);
1452       }
1453     }
1454     return result;
1455   }
1456
1457   /**
1458    * Find position of the first character accepted by given filter.
1459    *
1460    * @param s      the string to search
1461    * @param filter search filter
1462    * @return position of the first character accepted or -1 if not found
1463    */
1464   public static int findFirst(@NotNull final CharSequence s, @NotNull CharFilter filter) {
1465     for (int i = 0; i < s.length(); i++) {
1466       char ch = s.charAt(i);
1467       if (filter.accept(ch)) {
1468         return i;
1469       }
1470     }
1471     return -1;
1472   }
1473
1474   @NotNull
1475   public static String replaceSubstring(@NotNull String string, @NotNull TextRange range, @NotNull String replacement) {
1476     return range.replace(string, replacement);
1477   }
1478
1479   public static boolean startsWithWhitespace(@NotNull String text) {
1480     return !text.isEmpty() && Character.isWhitespace(text.charAt(0));
1481   }
1482
1483   public static boolean isChar(CharSequence seq, int index, char c) {
1484     return index >= 0 && index < seq.length() && seq.charAt(index) == c;
1485   }
1486
1487   public static boolean startsWith(@NotNull CharSequence text, @NotNull CharSequence prefix) {
1488     int l1 = text.length();
1489     int l2 = prefix.length();
1490     if (l1 < l2) return false;
1491
1492     for (int i = 0; i < l2; i++) {
1493       if (text.charAt(i) != prefix.charAt(i)) return false;
1494     }
1495
1496     return true;
1497   }
1498
1499   public static boolean startsWith(@NotNull CharSequence text, int startIndex, @NotNull CharSequence prefix) {
1500     int l1 = text.length() - startIndex;
1501     int l2 = prefix.length();
1502     if (l1 < l2) return false;
1503
1504     for (int i = 0; i < l2; i++) {
1505       if (text.charAt(i + startIndex) != prefix.charAt(i)) return false;
1506     }
1507
1508     return true;
1509   }
1510
1511   public static boolean endsWith(@NotNull CharSequence text, @NotNull CharSequence suffix) {
1512     int l1 = text.length();
1513     int l2 = suffix.length();
1514     if (l1 < l2) return false;
1515
1516     for (int i = l1 - 1; i >= l1 - l2; i--) {
1517       if (text.charAt(i) != suffix.charAt(i + l2 - l1)) return false;
1518     }
1519
1520     return true;
1521   }
1522
1523   @NotNull
1524   public static String commonPrefix(@NotNull String s1, @NotNull String s2) {
1525     return s1.substring(0, commonPrefixLength(s1, s2));
1526   }
1527
1528   public static int commonPrefixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1529     int i;
1530     int minLength = Math.min(s1.length(), s2.length());
1531     for (i = 0; i < minLength; i++) {
1532       if (s1.charAt(i) != s2.charAt(i)) {
1533         break;
1534       }
1535     }
1536     return i;
1537   }
1538
1539   @NotNull
1540   public static String commonSuffix(@NotNull String s1, @NotNull String s2) {
1541     return s1.substring(s1.length() - commonSuffixLength(s1, s2));
1542   }
1543
1544   public static int commonSuffixLength(@NotNull CharSequence s1, @NotNull CharSequence s2) {
1545     int s1Length = s1.length();
1546     int s2Length = s2.length();
1547     if (s1Length == 0 || s2Length == 0) return 0;
1548     int i;
1549     for (i = 0; i < s1Length && i < s2Length; i++) {
1550       if (s1.charAt(s1Length - i - 1) != s2.charAt(s2Length - i - 1)) {
1551         break;
1552       }
1553     }
1554     return i;
1555   }
1556
1557   /**
1558    * Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> interval.
1559    *
1560    * @param s     target char sequence to check
1561    * @param start start offset to use within the given char sequence (inclusive)
1562    * @param end   end offset to use within the given char sequence (exclusive)
1563    * @param c     target symbol to check
1564    * @return <code>true</code> if given symbol is contained at the target range of the given char sequence;
1565    *         <code>false</code> otherwise
1566    */
1567   public static boolean contains(@NotNull CharSequence s, int start, int end, char c) {
1568     return indexOf(s, c, start, end) >= 0;
1569   }
1570
1571   public static boolean containsWhitespaces(@Nullable CharSequence s) {
1572     if (s == null) return false;
1573
1574     for (int i = 0; i < s.length(); i++) {
1575       if (Character.isWhitespace(s.charAt(i))) return true;
1576     }
1577     return false;
1578   }
1579
1580   public static int indexOf(@NotNull CharSequence s, char c) {
1581     return indexOf(s, c, 0, s.length());
1582   }
1583
1584   public static int indexOf(@NotNull CharSequence s, char c, int start) {
1585     return indexOf(s, c, start, s.length());
1586   }
1587
1588   public static int indexOf(@NotNull CharSequence s, char c, int start, int end) {
1589     for (int i = start; i < end; i++) {
1590       if (s.charAt(i) == c) return i;
1591     }
1592     return -1;
1593   }
1594
1595   public static int indexOf(@NotNull CharSequence s, char c, int start, int end, boolean caseSensitive) {
1596     for (int i = start; i < end; i++) {
1597       if (charsMatch(s.charAt(i), c, !caseSensitive)) return i;
1598     }
1599     return -1;
1600   }
1601
1602   public static int indexOf(@NotNull char[] s, char c, int start, int end, boolean caseSensitive) {
1603     for (int i = start; i < end; i++) {
1604       if (charsMatch(s[i], c, !caseSensitive)) return i;
1605     }
1606     return -1;
1607   }
1608
1609   public static int indexOfSubstringEnd(@NotNull String text, @NotNull String subString) {
1610     int i = text.indexOf(subString);
1611     if (i == -1) return -1;
1612     return i + subString.length();
1613   }
1614
1615   public static int indexOfAny(@NotNull final String s, @NotNull final String chars) {
1616     return indexOfAny(s, chars, 0, s.length());
1617   }
1618
1619   public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) {
1620     for (int i = start; i < end; i++) {
1621       if (containsChar(chars, s.charAt(i))) return i;
1622     }
1623     return -1;
1624   }
1625
1626   @Nullable
1627   public static String substringAfter(@NotNull String text, @NotNull String subString) {
1628     int i = text.indexOf(subString);
1629     if (i == -1) return null;
1630     return text.substring(i + subString.length());
1631   }
1632
1633   /**
1634    * Allows to retrieve index of last occurrence of the given symbols at <code>[start; end)</code> sub-sequence of the given text.
1635    *
1636    * @param s     target text
1637    * @param c     target symbol which last occurrence we want to check
1638    * @param start start offset of the target text (inclusive)
1639    * @param end   end offset of the target text (exclusive)
1640    * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
1641    *         <code>-1</code> otherwise
1642    */
1643   public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
1644     for (int i = end - 1; i >= start; i--) {
1645       if (s.charAt(i) == c) return i;
1646     }
1647     return -1;
1648   }
1649
1650   @NotNull
1651   public static String first(@NotNull String text, final int maxLength, final boolean appendEllipsis) {
1652     return text.length() > maxLength ? text.substring(0, maxLength) + (appendEllipsis ? "..." : "") : text;
1653   }
1654
1655   @NotNull
1656   public static CharSequence first(@NotNull CharSequence text, final int length, final boolean appendEllipsis) {
1657     return text.length() > length ? text.subSequence(0, length) + (appendEllipsis ? "..." : "") : text;
1658   }
1659
1660   @NotNull
1661   public static CharSequence last(@NotNull CharSequence text, final int length, boolean prependEllipsis) {
1662     return text.length() > length ? (prependEllipsis ? "..." : "") + text.subSequence(text.length() - length, text.length()) : text;
1663   }
1664
1665   @NotNull
1666   private static String escapeChar(@NotNull final String str, final char character) {
1667     final StringBuilder buf = new StringBuilder(str);
1668     escapeChar(buf, character);
1669     return buf.toString();
1670   }
1671
1672   private static void escapeChar(@NotNull final StringBuilder buf, final char character) {
1673     int idx = 0;
1674     while ((idx = indexOf(buf, character, idx)) >= 0) {
1675       buf.insert(idx, "\\");
1676       idx += 2;
1677     }
1678   }
1679
1680   @NotNull
1681   public static String escapeQuotes(@NotNull final String str) {
1682     return escapeChar(str, '"');
1683   }
1684
1685   public static void escapeQuotes(@NotNull final StringBuilder buf) {
1686     escapeChar(buf, '"');
1687   }
1688
1689   @NotNull
1690   public static String escapeSlashes(@NotNull final String str) {
1691     return escapeChar(str, '/');
1692   }
1693
1694   @NotNull
1695   public static String escapeBackSlashes(@NotNull final String str) {
1696     return escapeChar(str, '\\');
1697   }
1698
1699   public static void escapeSlashes(@NotNull final StringBuilder buf) {
1700     escapeChar(buf, '/');
1701   }
1702
1703   @NotNull
1704   public static String unescapeSlashes(@NotNull final String str) {
1705     final StringBuilder buf = new StringBuilder(str.length());
1706     unescapeSlashes(buf, str);
1707     return buf.toString();
1708   }
1709
1710   private static void unescapeSlashes(@NotNull StringBuilder buf, @NotNull String str) {
1711     final int length = str.length();
1712     final int last = length - 1;
1713     for (int i = 0; i < length; i++) {
1714       char ch = str.charAt(i);
1715       if (ch == '\\' && i != last) {
1716         i++;
1717         ch = str.charAt(i);
1718         if (ch != '/') buf.append('\\');
1719       }
1720
1721       buf.append(ch);
1722     }
1723   }
1724
1725   public static void quote(@NotNull final StringBuilder builder) {
1726     quote(builder, '\"');
1727   }
1728
1729   public static void quote(@NotNull final StringBuilder builder, final char quotingChar) {
1730     builder.insert(0, quotingChar);
1731     builder.append(quotingChar);
1732   }
1733
1734   @NotNull
1735   public static String wrapWithDoubleQuote(@NotNull String str) {
1736     return '\"' + str + "\"";
1737   }
1738
1739   @NonNls private static final String[] REPLACES_REFS = {"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"};
1740   @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""};
1741
1742   public static String unescapeXml(@Nullable final String text) {
1743     if (text == null) return null;
1744     return replace(text, REPLACES_REFS, REPLACES_DISP);
1745   }
1746
1747   public static String escapeXml(@Nullable final String text) {
1748     if (text == null) return null;
1749     return replace(text, REPLACES_DISP, REPLACES_REFS);
1750   }
1751
1752   @NotNull
1753   public static String escapeToRegexp(@NotNull String text) {
1754     final StringBuilder result = new StringBuilder(text.length());
1755     return escapeToRegexp(text, result).toString();
1756   }
1757
1758   @NotNull
1759   public static StringBuilder escapeToRegexp(@NotNull CharSequence text, @NotNull StringBuilder builder) {
1760     for (int i = 0; i < text.length(); i++) {
1761       final char c = text.charAt(i);
1762       if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_') {
1763         builder.append(c);
1764       }
1765       else if (c == '\n') {
1766         builder.append("\\n");
1767       }
1768       else {
1769         builder.append('\\').append(c);
1770       }
1771     }
1772
1773     return builder;
1774   }
1775
1776   public static boolean isNotEscapedBackslash(@NotNull char[] chars, int startOffset, int backslashOffset) {
1777     if (chars[backslashOffset] != '\\') {
1778       return false;
1779     }
1780     boolean escaped = false;
1781     for (int i = startOffset; i < backslashOffset; i++) {
1782       if (chars[i] == '\\') {
1783         escaped = !escaped;
1784       }
1785       else {
1786         escaped = false;
1787       }
1788     }
1789     return !escaped;
1790   }
1791
1792   public static boolean isNotEscapedBackslash(@NotNull CharSequence text, int startOffset, int backslashOffset) {
1793     if (text.charAt(backslashOffset) != '\\') {
1794       return false;
1795     }
1796     boolean escaped = false;
1797     for (int i = startOffset; i < backslashOffset; i++) {
1798       if (text.charAt(i) == '\\') {
1799         escaped = !escaped;
1800       }
1801       else {
1802         escaped = false;
1803       }
1804     }
1805     return !escaped;
1806   }
1807
1808   @NotNull
1809   public static String replace(@NotNull String text, @NotNull String[] from, @NotNull String[] to) {
1810     final StringBuilder result = new StringBuilder(text.length());
1811     replace:
1812     for (int i = 0; i < text.length(); i++) {
1813       for (int j = 0; j < from.length; j += 1) {
1814         String toReplace = from[j];
1815         String replaceWith = to[j];
1816
1817         final int len = toReplace.length();
1818         if (text.regionMatches(i, toReplace, 0, len)) {
1819           result.append(replaceWith);
1820           i += len - 1;
1821           continue replace;
1822         }
1823       }
1824       result.append(text.charAt(i));
1825     }
1826     return result.toString();
1827   }
1828
1829   @NotNull
1830   public static String[] filterEmptyStrings(@NotNull String[] strings) {
1831     int emptyCount = 0;
1832     for (String string : strings) {
1833       if (string == null || string.isEmpty()) emptyCount++;
1834     }
1835     if (emptyCount == 0) return strings;
1836
1837     String[] result = ArrayUtil.newStringArray(strings.length - emptyCount);
1838     int count = 0;
1839     for (String string : strings) {
1840       if (string == null || string.isEmpty()) continue;
1841       result[count++] = string;
1842     }
1843
1844     return result;
1845   }
1846
1847   public static int countNewLines(@NotNull CharSequence text) {
1848     return countChars(text, '\n');
1849   }
1850
1851   public static int countChars(@NotNull CharSequence text, char c) {
1852     int count = 0;
1853
1854     for (int i = 0; i < text.length(); ++i) {
1855       final char ch = text.charAt(i);
1856       if (ch == c) {
1857         ++count;
1858       }
1859     }
1860     return count;
1861   }
1862
1863   @NotNull
1864   public static String capitalsOnly(@NotNull String s) {
1865     StringBuilder b = new StringBuilder();
1866     for (int i = 0; i < s.length(); i++) {
1867       if (Character.isUpperCase(s.charAt(i))) {
1868         b.append(s.charAt(i));
1869       }
1870     }
1871
1872     return b.toString();
1873   }
1874
1875   /**
1876    * @param args Strings to join.
1877    * @return {@code null} if any of given Strings is {@code null}.
1878    */
1879   @Nullable
1880   public static String joinOrNull(@NotNull String... args) {
1881     StringBuilder r = new StringBuilder();
1882     for (String arg : args) {
1883       if (arg == null) return null;
1884       r.append(arg);
1885     }
1886     return r.toString();
1887   }
1888
1889   @Nullable
1890   public static String getPropertyName(@NonNls @NotNull String methodName) {
1891     if (methodName.startsWith("get")) {
1892       return Introspector.decapitalize(methodName.substring(3));
1893     }
1894     else if (methodName.startsWith("is")) {
1895       return Introspector.decapitalize(methodName.substring(2));
1896     }
1897     else if (methodName.startsWith("set")) {
1898       return Introspector.decapitalize(methodName.substring(3));
1899     }
1900     else {
1901       return null;
1902     }
1903   }
1904
1905   public static boolean isJavaIdentifierStart(char c) {
1906     return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isJavaIdentifierStart(c);
1907   }
1908
1909   public static boolean isJavaIdentifierPart(char c) {
1910     return c >= '0' && c <= '9' || isJavaIdentifierStart(c);
1911   }
1912
1913   public static boolean isJavaIdentifier(@NotNull String text) {
1914     int len = text.length();
1915     if (len == 0) return false;
1916
1917     if (!isJavaIdentifierStart(text.charAt(0))) return false;
1918
1919     for (int i = 1; i < len; i++) {
1920       if (!isJavaIdentifierPart(text.charAt(i))) return false;
1921     }
1922
1923     return true;
1924   }
1925
1926   /**
1927    * Escape property name or key in property file. Unicode characters are escaped as well.
1928    *
1929    * @param input an input to escape
1930    * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case.
1931    * @return an escaped string
1932    */
1933   @NotNull
1934   public static String escapeProperty(@NotNull String input, final boolean isKey) {
1935     final StringBuilder escaped = new StringBuilder(input.length());
1936     for (int i = 0; i < input.length(); i++) {
1937       final char ch = input.charAt(i);
1938       switch (ch) {
1939         case ' ':
1940           if (isKey && i == 0) {
1941             // only the leading space has to be escaped
1942             escaped.append('\\');
1943           }
1944           escaped.append(' ');
1945           break;
1946         case '\t':
1947           escaped.append("\\t");
1948           break;
1949         case '\r':
1950           escaped.append("\\r");
1951           break;
1952         case '\n':
1953           escaped.append("\\n");
1954           break;
1955         case '\f':
1956           escaped.append("\\f");
1957           break;
1958         case '\\':
1959         case '#':
1960         case '!':
1961         case ':':
1962         case '=':
1963           escaped.append('\\');
1964           escaped.append(ch);
1965           break;
1966         default:
1967           if (20 < ch && ch < 0x7F) {
1968             escaped.append(ch);
1969           }
1970           else {
1971             escaped.append("\\u");
1972             escaped.append(Character.forDigit((ch >> 12) & 0xF, 16));
1973             escaped.append(Character.forDigit((ch >> 8) & 0xF, 16));
1974             escaped.append(Character.forDigit((ch >> 4) & 0xF, 16));
1975             escaped.append(Character.forDigit((ch) & 0xF, 16));
1976           }
1977           break;
1978       }
1979     }
1980     return escaped.toString();
1981   }
1982
1983   public static String getQualifiedName(@Nullable String packageName, String className) {
1984     if (packageName == null || packageName.isEmpty()) {
1985       return className;
1986     }
1987     return packageName + '.' + className;
1988   }
1989
1990   public static int compareVersionNumbers(@Nullable String v1, @Nullable String v2) {
1991     if (v1 == null && v2 == null) {
1992       return 0;
1993     }
1994     if (v1 == null) {
1995       return -1;
1996     }
1997     if (v2 == null) {
1998       return 1;
1999     }
2000
2001     String[] part1 = v1.split("[\\.\\_\\-]");
2002     String[] part2 = v2.split("[\\.\\_\\-]");
2003
2004     int idx = 0;
2005     for (; idx < part1.length && idx < part2.length; idx++) {
2006       String p1 = part1[idx];
2007       String p2 = part2[idx];
2008
2009       int cmp;
2010       if (p1.matches("\\d+") && p2.matches("\\d+")) {
2011         cmp = new Integer(p1).compareTo(new Integer(p2));
2012       }
2013       else {
2014         cmp = part1[idx].compareTo(part2[idx]);
2015       }
2016       if (cmp != 0) return cmp;
2017     }
2018
2019     if (part1.length == part2.length) {
2020       return 0;
2021     }
2022     else if (part1.length > idx) {
2023       return 1;
2024     }
2025     else {
2026       return -1;
2027     }
2028   }
2029
2030   @SuppressWarnings("UnusedDeclaration")
2031   /** @deprecated use {@linkplain #getOccurrenceCount(String, char)} (to remove in IDEA 13) */
2032   public static int getOccurenceCount(@NotNull String text, final char c) {
2033     return getOccurrenceCount(text, c);
2034   }
2035
2036   public static int getOccurrenceCount(@NotNull String text, final char c) {
2037     int res = 0;
2038     int i = 0;
2039     while (i < text.length()) {
2040       i = text.indexOf(c, i);
2041       if (i >= 0) {
2042         res++;
2043         i++;
2044       }
2045       else {
2046         break;
2047       }
2048     }
2049     return res;
2050   }
2051
2052   public static int getOccurrenceCount(@NotNull String text, @NotNull String s) {
2053     int res = 0;
2054     int i = 0;
2055     while (i < text.length()) {
2056       i = text.indexOf(s, i);
2057       if (i >= 0) {
2058         res++;
2059         i++;
2060       }
2061       else {
2062         break;
2063       }
2064     }
2065     return res;
2066   }
2067
2068   @NotNull
2069   public static String fixVariableNameDerivedFromPropertyName(@NotNull String name) {
2070     if (isEmptyOrSpaces(name)) return name;
2071     char c = name.charAt(0);
2072     if (isVowel(c)) {
2073       return "an" + Character.toUpperCase(c) + name.substring(1);
2074     }
2075     return "a" + Character.toUpperCase(c) + name.substring(1);
2076   }
2077
2078   @NotNull
2079   public static String sanitizeJavaIdentifier(@NotNull String name) {
2080     final StringBuilder result = new StringBuilder(name.length());
2081
2082     for (int i = 0; i < name.length(); i++) {
2083       final char ch = name.charAt(i);
2084       if (Character.isLetterOrDigit(ch)) {
2085         if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
2086           result.append("_");
2087         }
2088         result.append(ch);
2089       }
2090     }
2091
2092     return result.toString();
2093   }
2094
2095   public static void assertValidSeparators(@NotNull CharSequence s) {
2096     char[] chars = CharArrayUtil.fromSequenceWithoutCopying(s);
2097     int slashRIndex = -1;
2098
2099     if (chars != null) {
2100       for (int i = 0, len = s.length(); i < len; ++i) {
2101         if (chars[i] == '\r') {
2102           slashRIndex = i;
2103           break;
2104         }
2105       }
2106     }
2107     else {
2108       for (int i = 0, len = s.length(); i < len; i++) {
2109         if (s.charAt(i) == '\r') {
2110           slashRIndex = i;
2111           break;
2112         }
2113       }
2114     }
2115
2116     if (slashRIndex != -1) {
2117       String context =
2118         String.valueOf(last(s.subSequence(0, slashRIndex), 10, true)) + first(s.subSequence(slashRIndex, s.length()), 10, true);
2119       context = escapeStringCharacters(context);
2120       LOG.error("Wrong line separators: '" + context + "' at offset " + slashRIndex);
2121     }
2122   }
2123
2124   @NotNull
2125   public static String tail(@NotNull String s, final int idx) {
2126     return idx >= s.length() ? "" : s.substring(idx, s.length());
2127   }
2128
2129   /**
2130    * Splits string by lines.
2131    *
2132    * @param string String to split
2133    * @return array of strings
2134    */
2135   @NotNull
2136   public static String[] splitByLines(@NotNull String string) {
2137     return splitByLines(string, true);
2138   }
2139
2140   /**
2141    * Splits string by lines. If several line separators are in a row corresponding empty lines
2142    * are also added to result if {@code excludeEmptyStrings} is {@code false}.
2143    *
2144    * @param string String to split
2145    * @return array of strings
2146    */
2147   @NotNull
2148   public static String[] splitByLines(@NotNull String string, boolean excludeEmptyStrings) {
2149     return (excludeEmptyStrings ? EOL_SPLIT_PATTERN : EOL_SPLIT_PATTERN_WITH_EMPTY).split(string);
2150   }
2151
2152   @NotNull
2153   public static List<Pair<String, Integer>> getWordsWithOffset(@NotNull String s) {
2154     List<Pair<String, Integer>> res = ContainerUtil.newArrayList();
2155     s += " ";
2156     StringBuilder name = new StringBuilder();
2157     int startInd = -1;
2158     for (int i = 0; i < s.length(); i++) {
2159       if (Character.isWhitespace(s.charAt(i))) {
2160         if (name.length() > 0) {
2161           res.add(Pair.create(name.toString(), startInd));
2162           name.setLength(0);
2163           startInd = -1;
2164         }
2165       }
2166       else {
2167         if (startInd == -1) {
2168           startInd = i;
2169         }
2170         name.append(s.charAt(i));
2171       }
2172     }
2173     return res;
2174   }
2175
2176   /**
2177    * Implementation of "Sorting for Humans: Natural Sort Order":
2178    * http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
2179    */
2180   public static int naturalCompare(@NotNull String string1, @NotNull String string2) {
2181     return naturalCompare(string1, string2, false);
2182   }
2183
2184   private static int naturalCompare(@NotNull String string1, @NotNull String string2, boolean caseSensitive) {
2185     final int string1Length = string1.length();
2186     final int string2Length = string2.length();
2187     for (int i = 0, j = 0; i < string1Length && j < string2Length; i++, j++) {
2188       char ch1 = string1.charAt(i);
2189       char ch2 = string2.charAt(j);
2190       if ((isDigit(ch1) || ch1 == ' ') && (isDigit(ch2) || ch2 == ' ')) {
2191         int startNum1 = i;
2192         while (ch1 == ' ' || ch1 == '0') { // skip leading spaces and zeros
2193           startNum1++;
2194           if (startNum1 >= string1Length) break;
2195           ch1 = string1.charAt(startNum1);
2196         }
2197         int startNum2 = j;
2198         while (ch2 == ' ' || ch2 == '0') {
2199           startNum2++;
2200           if (startNum2 >= string2Length) break;
2201           ch2 = string2.charAt(startNum2);
2202         }
2203         i = startNum1;
2204         j = startNum2;
2205         while (i < string1Length && isDigit(string1.charAt(i))) i++;
2206         while (j < string2Length && isDigit(string2.charAt(j))) j++;
2207         String digits1 = string1.substring(startNum1, i);
2208         String digits2 = string2.substring(startNum2, j);
2209         if (digits1.length() != digits2.length()) {
2210           return digits1.length() - digits2.length();
2211         }
2212         int numberDiff = digits1.compareTo(digits2);
2213         if (numberDiff != 0) {
2214           return numberDiff;
2215         }
2216         i--;
2217         j--;
2218         final int lengthDiff = (i - startNum1) - (j - startNum2);
2219         if (lengthDiff != 0) {
2220           return lengthDiff;
2221         }
2222         for (; startNum1 < i; startNum1++, startNum2++) {
2223           final int diff = string1.charAt(startNum1) - string2.charAt(startNum2);
2224           if (diff != 0) {
2225             return diff;
2226           }
2227         }
2228       }
2229       else {
2230         if (caseSensitive) {
2231           return ch1 - ch2;
2232         }
2233         else {
2234           // similar logic to charsMatch() below
2235           if (ch1 != ch2) {
2236             final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
2237             if (diff1 != 0) {
2238               final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
2239               if (diff2 != 0) {
2240                 return diff2;
2241               }
2242             }
2243           }
2244         }
2245       }
2246     }
2247     if (!caseSensitive && string1Length == string2Length) {
2248       // do case sensitive compare if case insensitive strings are equal
2249       return naturalCompare(string1, string2, true);
2250     }
2251     return string1Length - string2Length;
2252   }
2253
2254   private static boolean isDigit(char c) {
2255     return c >= '0' && c <= '9';
2256   }
2257
2258   public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) {
2259     //noinspection StringEquality
2260     if (s1 == s2) return 0;
2261     if (s1 == null) return -1;
2262     if (s2 == null) return 1;
2263     return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
2264   }
2265
2266   public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
2267     final int compare = compare(s1, s2, ignoreCase);
2268     return compare != 0 ? compare : compare(t1, t2, ignoreCase);
2269   }
2270
2271   public static int hashCode(@NotNull CharSequence s) {
2272     return stringHashCode(s);
2273   }
2274
2275   public static boolean equals(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2276     if (s1 == null ^ s2 == null) {
2277       return false;
2278     }
2279
2280     if (s1 == null) {
2281       return true;
2282     }
2283
2284     if (s1.length() != s2.length()) {
2285       return false;
2286     }
2287     for (int i = 0; i < s1.length(); i++) {
2288       if (s1.charAt(i) != s2.charAt(i)) {
2289         return false;
2290       }
2291     }
2292     return true;
2293   }
2294
2295   public static boolean equalsIgnoreCase(@Nullable CharSequence s1, @Nullable CharSequence s2) {
2296     if (s1 == null ^ s2 == null) {
2297       return false;
2298     }
2299
2300     if (s1 == null) {
2301       return true;
2302     }
2303
2304     if (s1.length() != s2.length()) {
2305       return false;
2306     }
2307     for (int i = 0; i < s1.length(); i++) {
2308       if (!charsMatch(s1.charAt(i),s2.charAt(i), true)) {
2309         return false;
2310       }
2311     }
2312     return true;
2313   }
2314
2315   public static int compare(char c1, char c2, boolean ignoreCase) {
2316     // duplicating String.equalsIgnoreCase logic
2317     int d = c1 - c2;
2318     if (d == 0 || !ignoreCase) {
2319       return d;
2320     }
2321     // If characters don't match but case may be ignored,
2322     // try converting both characters to uppercase.
2323     // If the results match, then the comparison scan should
2324     // continue.
2325     char u1 = StringUtilRt.toUpperCase(c1);
2326     char u2 = StringUtilRt.toUpperCase(c2);
2327     d = u1 - u2;
2328     if (d != 0) {
2329       // Unfortunately, conversion to uppercase does not work properly
2330       // for the Georgian alphabet, which has strange rules about case
2331       // conversion.  So we need to make one last check before
2332       // exiting.
2333       d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
2334     }
2335     return d;
2336   }
2337
2338   public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
2339     return compare(c1,c2,ignoreCase) == 0;
2340   }
2341
2342   @NotNull
2343   public static String formatLinks(@NotNull String message) {
2344     Pattern linkPattern = Pattern.compile("http://[a-zA-Z0-9\\./\\-\\+]+");
2345     StringBuffer result = new StringBuffer();
2346     Matcher m = linkPattern.matcher(message);
2347     while (m.find()) {
2348       m.appendReplacement(result, "<a href=\"" + m.group() + "\">" + m.group() + "</a>");
2349     }
2350     m.appendTail(result);
2351     return result.toString();
2352   }
2353
2354   public static boolean isHexDigit(char c) {
2355     return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
2356   }
2357
2358   public static boolean isOctalDigit(char c) {
2359     return '0' <= c && c <= '7';
2360   }
2361
2362   @NotNull
2363   public static String shortenTextWithEllipsis(@NotNull final String text, final int maxLength, final int suffixLength) {
2364     return shortenTextWithEllipsis(text, maxLength, suffixLength, false);
2365   }
2366
2367   @NotNull
2368   public static String shortenTextWithEllipsis(@NotNull final String text, final int maxLength, final int suffixLength, boolean useEllipsisSymbol) {
2369     final int prefix_length = maxLength - suffixLength - 3;
2370     assert prefix_length > 0;
2371     String result;
2372     final int textLength = text.length();
2373     if (textLength > maxLength) {
2374       result = text.substring(0, prefix_length) + (useEllipsisSymbol ? "\u2026" : "...") + text.substring(textLength - suffixLength);
2375     }
2376     else {
2377       result = text;
2378     }
2379     return result;
2380   }
2381
2382   @NotNull
2383   public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength, boolean useEllipsisSymbol) {
2384     return shortenTextWithEllipsis(path, maxLength, (int)(maxLength * 0.7), useEllipsisSymbol);
2385   }
2386
2387   @NotNull
2388   public static String shortenPathWithEllipsis(@NotNull final String path, final int maxLength) {
2389     return shortenPathWithEllipsis(path, maxLength, false);
2390   }
2391
2392   public static boolean charsEqual(char a, char b, boolean ignoreCase) {
2393     return ignoreCase ? charsEqualIgnoreCase(a, b) : a == b;
2394   }
2395   public static boolean charsEqualIgnoreCase(char a, char b) {
2396     return StringUtilRt.charsEqualIgnoreCase(a, b);
2397   }
2398
2399   public static char toUpperCase(char a) {
2400     return StringUtilRt.toUpperCase(a);
2401   }
2402
2403   @NotNull
2404   public static String toUpperCase(@NotNull String a) {
2405     return StringUtilRt.toUpperCase(a);
2406   }
2407
2408   public static char toLowerCase(final char a) {
2409     return StringUtilRt.toLowerCase(a);
2410   }
2411
2412   @NotNull
2413   public static String convertLineSeparators(@NotNull String text) {
2414     return StringUtilRt.convertLineSeparators(text);
2415   }
2416
2417   @NotNull
2418   public static String convertLineSeparators(@NotNull String text, boolean keepCarriageReturn) {
2419     return StringUtilRt.convertLineSeparators(text, keepCarriageReturn);
2420   }
2421
2422   @NotNull
2423   public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator) {
2424     return StringUtilRt.convertLineSeparators(text, newSeparator);
2425   }
2426
2427   @NotNull
2428   public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator, @Nullable int[] offsetsToKeep) {
2429     return StringUtilRt.convertLineSeparators(text, newSeparator, offsetsToKeep);
2430   }
2431
2432   @NotNull
2433   public static String convertLineSeparators(@NotNull String text,
2434                                              @NotNull String newSeparator,
2435                                              @Nullable int[] offsetsToKeep,
2436                                              boolean keepCarriageReturn) {
2437     return StringUtilRt.convertLineSeparators(text, newSeparator, offsetsToKeep, keepCarriageReturn);
2438   }
2439
2440   public static int parseInt(final String string, final int defaultValue) {
2441     return StringUtilRt.parseInt(string, defaultValue);
2442   }
2443
2444   public static double parseDouble(final String string, final double defaultValue) {
2445     return StringUtilRt.parseDouble(string, defaultValue);
2446   }
2447
2448   public static boolean parseBoolean(String string, final boolean defaultValue) {
2449     return StringUtilRt.parseBoolean(string, defaultValue);
2450   }
2451
2452   @NotNull
2453   public static String getShortName(@NotNull Class aClass) {
2454     return StringUtilRt.getShortName(aClass);
2455   }
2456
2457   @NotNull
2458   public static String getShortName(@NotNull String fqName) {
2459     return StringUtilRt.getShortName(fqName);
2460   }
2461
2462   @NotNull
2463   public static String getShortName(@NotNull String fqName, char separator) {
2464     return StringUtilRt.getShortName(fqName, separator);
2465   }
2466
2467   @NotNull
2468   public static CharSequence newBombedCharSequence(@NotNull CharSequence sequence, long delay) {
2469     final long myTime = System.currentTimeMillis() + delay;
2470     return new BombedCharSequence(sequence) {
2471       @Override
2472       protected void checkCanceled() {
2473         long l = System.currentTimeMillis();
2474         if (l >= myTime) {
2475           throw new ProcessCanceledException();
2476         }
2477       }
2478     };
2479   }
2480
2481   public static boolean trimEnd(@NotNull StringBuilder buffer, @NotNull CharSequence end) {
2482     if (endsWith(buffer, end)) {
2483       buffer.delete(buffer.length() - end.length(), buffer.length());
2484       return true;
2485     }
2486     return false;
2487   }
2488
2489   private static boolean trimStart(@NotNull StringBuilder buffer, @NotNull CharSequence start) {
2490     if (startsWith(buffer, start)) {
2491       buffer.delete(0, start.length());
2492       return true;
2493     }
2494     return false;
2495   }
2496
2497   /**
2498    * Say smallPart = "op" and bigPart="open". Method returns true for "Ope" and false for "ops"
2499    */
2500   public static boolean isBetween(@NotNull String string, @NotNull String smallPart, @NotNull String bigPart) {
2501     final String s = string.toLowerCase();
2502     return s.startsWith(smallPart.toLowerCase()) && bigPart.toLowerCase().startsWith(s);
2503   }
2504
2505   /**
2506    * Expirable CharSequence. Very useful to control external library execution time,
2507    * i.e. when java.util.regex.Pattern match goes out of control.
2508    */
2509   public abstract static class BombedCharSequence implements CharSequence {
2510     private CharSequence delegate;
2511     private int i = 0;
2512
2513     public BombedCharSequence(@NotNull CharSequence sequence) {
2514       delegate = sequence;
2515     }
2516
2517     @Override
2518     public int length() {
2519       check();
2520       return delegate.length();
2521     }
2522
2523     @Override
2524     public char charAt(int i) {
2525       check();
2526       return delegate.charAt(i);
2527     }
2528
2529     protected void check() {
2530       if ((++i & 1023) == 0) {
2531         checkCanceled();
2532       }
2533     }
2534
2535     @NotNull
2536     @Override
2537     public String toString() {
2538       check();
2539       return delegate.toString();
2540     }
2541
2542     protected abstract void checkCanceled();
2543
2544     @Override
2545     public CharSequence subSequence(int i, int i1) {
2546       check();
2547       return delegate.subSequence(i, i1);
2548     }
2549   }
2550 }