2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.util
.text
;
18 import com
.intellij
.CommonBundle
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.util
.TextRange
;
21 import com
.intellij
.util
.ArrayUtil
;
22 import com
.intellij
.util
.Function
;
23 import com
.intellij
.util
.SmartList
;
24 import com
.intellij
.util
.text
.CharArrayCharSequence
;
25 import com
.intellij
.util
.text
.LineReader
;
26 import org
.jetbrains
.annotations
.NonNls
;
27 import org
.jetbrains
.annotations
.NotNull
;
28 import org
.jetbrains
.annotations
.Nullable
;
30 import java
.beans
.Introspector
;
31 import java
.io
.ByteArrayInputStream
;
32 import java
.io
.IOException
;
33 import java
.io
.PrintWriter
;
34 import java
.io
.StringWriter
;
37 public class StringUtil
{
38 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.util.text.StringUtil");
39 @NonNls private static final String VOWELS
= "aeiouy";
41 public static String
replace(@NonNls @NotNull String text
, @NonNls @NotNull String oldS
, @NonNls @Nullable String newS
) {
42 return replace(text
, oldS
, newS
, false);
45 public static String
replaceIgnoreCase(@NotNull String text
, @NotNull String oldS
, @Nullable String newS
) {
46 return replace(text
, oldS
, newS
, true);
49 public static void replaceChar(@NotNull char[] buffer
, char oldChar
, char newChar
, int start
, int end
) {
50 for (int i
= start
; i
< end
; i
++) {
58 public static String
replace(@NotNull final String text
, @NotNull final String oldS
, @Nullable final String newS
, boolean ignoreCase
) {
59 if (text
.length() < oldS
.length()) return text
;
61 final String text1
= ignoreCase ? text
.toLowerCase() : text
;
62 final String oldS1
= ignoreCase ? oldS
.toLowerCase() : oldS
;
63 final StringBuilder newText
= new StringBuilder();
65 while (i
< text1
.length()) {
66 int i1
= text1
.indexOf(oldS1
, i
);
68 if (i
== 0) return text
;
69 newText
.append(text
, i
, text
.length());
73 if (newS
== null) return null;
74 newText
.append(text
, i
, i1
);
76 i
= i1
+ oldS
.length();
79 return newText
.toString();
82 @NotNull public static String
getShortName(@NotNull String fqName
) {
83 return getShortName(fqName
, '.');
86 @NotNull public static String
getShortName(@NotNull Class aClass
) {
87 return getShortName(aClass
.getName());
91 * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
98 public static int indexOfIgnoreCase(@NotNull String where
, @NotNull String what
, int fromIndex
) {
99 int targetCount
= what
.length();
100 int sourceCount
= where
.length();
102 if (fromIndex
>= sourceCount
) {
103 return targetCount
== 0 ? sourceCount
: -1;
110 if (targetCount
== 0) {
114 char first
= what
.charAt(0);
115 int max
= sourceCount
- targetCount
;
117 for (int i
= fromIndex
; i
<= max
; i
++) {
118 /* Look for first character. */
119 if (!charsEqualIgnoreCase(where
.charAt(i
), first
)) {
120 while (++i
<= max
&& !charsEqualIgnoreCase(where
.charAt(i
), first
)) ;
123 /* Found first character, now look at the rest of v2 */
126 int end
= j
+ targetCount
- 1;
127 for (int k
= 1; j
< end
&& charsEqualIgnoreCase(where
.charAt(j
), what
.charAt(k
)); j
++, k
++) ;
130 /* Found whole string. */
139 public static boolean containsIgnoreCase(String where
, String what
) {
140 return indexOfIgnoreCase(where
, what
, 0) >= 0;
143 public static boolean endsWithIgnoreCase(@NonNls String str
, @NonNls String suffix
) {
144 final int stringLength
= str
.length();
145 final int suffixLength
= suffix
.length();
146 return stringLength
>= suffixLength
&& str
.regionMatches(true, stringLength
- suffixLength
, suffix
, 0, suffixLength
);
149 public static boolean startsWithIgnoreCase(@NonNls String str
, @NonNls String prefix
) {
150 final int stringLength
= str
.length();
151 final int prefixLength
= prefix
.length();
152 return stringLength
>= prefixLength
&& str
.regionMatches(true, 0, prefix
, 0, prefixLength
);
155 public static boolean charsEqualIgnoreCase(char a
, char b
) {
156 return a
== b
|| toUpperCase(a
) == toUpperCase(b
) || toLowerCase(a
) == toLowerCase(b
);
159 public static char toUpperCase(char a
) {
163 if (a
>= 'a' && a
<= 'z') {
164 return (char)(a
+ ('A' - 'a'));
166 return Character
.toUpperCase(a
);
169 public static char toLowerCase(final char a
) {
170 if (a
< 'A' || a
>= 'a' && a
<= 'z') {
174 if (a
>= 'A' && a
<= 'Z') {
175 return (char)(a
+ ('a' - 'A'));
178 return Character
.toLowerCase(a
);
182 public static String
toLowerCase(@Nullable final String str
) {
183 return str
== null?
null : str
.toLowerCase();
186 @NotNull public static String
getShortName(@NotNull String fqName
, char separator
) {
187 int lastPointIdx
= fqName
.lastIndexOf(separator
);
188 if (lastPointIdx
>= 0) {
189 return fqName
.substring(lastPointIdx
+ 1);
195 public static String
getPackageName(@NotNull String fqName
) {
196 return getPackageName(fqName
, '.');
199 @NotNull public static String
getPackageName(@NotNull String fqName
, char separator
) {
200 int lastPointIdx
= fqName
.lastIndexOf(separator
);
201 if (lastPointIdx
>= 0) {
202 return fqName
.substring(0, lastPointIdx
);
208 * Converts line separators to <code>"\n"</code>
210 @NotNull public static String
convertLineSeparators(@NotNull String text
) {
211 return convertLineSeparators(text
, "\n", null);
214 @NotNull public static String
convertLineSeparators(@NotNull String text
, @NotNull String newSeparator
) {
215 return convertLineSeparators(text
, newSeparator
, null);
218 @NotNull public static String
convertLineSeparators(@NotNull String text
, @NotNull String newSeparator
, @Nullable int[] offsetsToKeep
) {
219 StringBuilder buffer
= null;
220 int intactLength
= 0;
221 final boolean newSeparatorIsSlashN
= "\n".equals(newSeparator
);
222 for (int i
= 0; i
< text
.length(); i
++) {
223 char c
= text
.charAt(i
);
225 if (!newSeparatorIsSlashN
) {
226 if (buffer
== null) {
227 buffer
= new StringBuilder(text
.length());
228 buffer
.append(text
, 0, intactLength
);
230 buffer
.append(newSeparator
);
231 shiftOffsets(offsetsToKeep
, buffer
.length(), 1, newSeparator
.length());
233 else if (buffer
== null) intactLength
++;
234 else buffer
.append(c
);
236 else if (c
== '\r') {
237 if (buffer
== null) {
238 buffer
= new StringBuilder(text
.length());
239 buffer
.append(text
, 0, intactLength
);
241 buffer
.append(newSeparator
);
242 if (i
< text
.length() - 1 && text
.charAt(i
+ 1) == '\n') {
244 shiftOffsets(offsetsToKeep
, buffer
.length(), 2, newSeparator
.length());
247 shiftOffsets(offsetsToKeep
, buffer
.length(), 1, newSeparator
.length());
251 if (buffer
== null) intactLength
++;
252 else buffer
.append(c
);
255 return buffer
== null ? text
: buffer
.toString();
258 private static void shiftOffsets(int[] offsets
, int changeOffset
, int oldLength
, int newLength
) {
259 if (offsets
== null) return;
260 int shift
= newLength
- oldLength
;
261 if (shift
== 0) return;
262 for (int i
= 0; i
< offsets
.length
; i
++) {
263 int offset
= offsets
[i
];
264 if (offset
>= changeOffset
+ oldLength
) {
270 public static int getLineBreakCount(@NotNull CharSequence text
) {
272 for (int i
= 0; i
< text
.length(); i
++) {
273 char c
= text
.charAt(i
);
277 else if (c
== '\r') {
278 if (i
+ 1 < text
.length() && text
.charAt(i
+ 1) == '\n') {
290 public static int lineColToOffset(@NotNull CharSequence text
, int line
, int col
) {
293 while (line
!= curLine
) {
294 if (offset
== text
.length()) return -1;
295 char c
= text
.charAt(offset
);
299 else if (c
== '\r') {
301 if (offset
< text
.length() - 1 && text
.charAt(offset
+ 1) == '\n') {
310 public static int offsetToLineNumber(@NotNull CharSequence text
, int offset
) {
313 while (curOffset
< offset
) {
314 if (curOffset
== text
.length()) return -1;
315 char c
= text
.charAt(curOffset
);
319 else if (c
== '\r') {
321 if (curOffset
< text
.length() - 1 && text
.charAt(curOffset
+ 1) == '\n') {
331 * Classic dynamic programming algorithm for string differences.
333 public static int difference(@NotNull String s1
, @NotNull String s2
) {
334 int[][] a
= new int[s1
.length()][s2
.length()];
336 for (int i
= 0; i
< s1
.length(); i
++) {
340 for (int j
= 0; j
< s2
.length(); j
++) {
344 for (int i
= 1; i
< s1
.length(); i
++) {
345 for (int j
= 1; j
< s2
.length(); j
++) {
347 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);
351 return a
[s1
.length() - 1][s2
.length() - 1];
354 @NotNull public static String
wordsToBeginFromUpperCase(@NotNull String s
) {
355 return toTitleCase(s
, ourPrepositions
);
359 public static String
toTitleCase(String s
) {
360 return toTitleCase(s
, ArrayUtil
.EMPTY_STRING_ARRAY
);
363 private static String
toTitleCase(String s
, final String
[] prepositions
) {
364 StringBuffer buffer
= null;
365 for (int i
= 0; i
< s
.length(); i
++) {
366 char prevChar
= i
== 0 ?
' ' : s
.charAt(i
- 1);
367 char currChar
= s
.charAt(i
);
368 if (!Character
.isLetterOrDigit(prevChar
)) {
369 if (Character
.isLetterOrDigit(currChar
)) {
370 if (!Character
.isUpperCase(currChar
)) {
372 for (; j
< s
.length(); j
++) {
373 if (!Character
.isLetterOrDigit(s
.charAt(j
))) {
377 if (!isPreposition(s
, i
, j
- 1, prepositions
)) {
378 if (buffer
== null) {
379 buffer
= new StringBuffer(s
);
381 buffer
.setCharAt(i
, toUpperCase(currChar
));
387 if (buffer
== null) {
391 return buffer
.toString();
395 @NonNls private static final String
[] ourPrepositions
= {"at", "the", "and", "not", "if", "a", "or", "to", "in", "on", "into"};
398 public static boolean isPreposition(@NotNull String s
, int firstChar
, int lastChar
) {
399 return isPreposition(s
, firstChar
, lastChar
, ourPrepositions
);
402 public static boolean isPreposition(String s
, int firstChar
, int lastChar
, final String
[] prepositions
) {
403 for (String preposition
: prepositions
) {
404 boolean found
= false;
405 if (lastChar
- firstChar
+ 1 == preposition
.length()) {
407 for (int j
= 0; j
< preposition
.length(); j
++) {
408 if (!(toLowerCase(s
.charAt(firstChar
+ j
)) == preposition
.charAt(j
))) {
420 public static void escapeStringCharacters(int length
, final String str
, @NotNull @NonNls StringBuilder buffer
) {
421 for (int idx
= 0; idx
< length
; idx
++) {
422 char ch
= str
.charAt(idx
);
425 buffer
.append("\\b");
429 buffer
.append("\\t");
433 buffer
.append("\\n");
437 buffer
.append("\\f");
441 buffer
.append("\\r");
445 buffer
.append("\\\"");
449 buffer
.append("\\\\");
453 if (Character
.isISOControl(ch
)) {
454 String hexCode
= Integer
.toHexString(ch
).toUpperCase();
455 buffer
.append("\\u");
456 int paddingCount
= 4 - hexCode
.length();
457 while (paddingCount
-- > 0) {
460 buffer
.append(hexCode
);
469 @NotNull public static String
escapeStringCharacters(@NotNull String s
) {
470 StringBuilder buffer
= new StringBuilder();
471 escapeStringCharacters(s
.length(), s
, buffer
);
472 return buffer
.toString();
476 @NotNull public static String
unescapeStringCharacters(@NotNull String s
) {
477 StringBuilder buffer
= new StringBuilder();
478 unescapeStringCharacters(s
.length(), s
, buffer
);
479 return buffer
.toString();
482 @NotNull public static String
unquoteString( @NotNull String s
) {
483 if (s
.length() <= 1 || s
.charAt(0) != '"' || s
.charAt(s
.length() - 1) != '"') {
486 return s
.substring(1, s
.length() - 1);
489 private static void unescapeStringCharacters(int length
, String s
, StringBuilder buffer
) {
490 boolean escaped
= false;
491 for (int idx
= 0; idx
< length
; idx
++) {
492 char ch
= s
.charAt(idx
);
536 if (idx
+ 4 < length
) {
538 int code
= Integer
.valueOf(s
.substring(idx
+ 1, idx
+ 5), 16).intValue();
540 buffer
.append((char)code
);
542 catch (NumberFormatException e
) {
543 buffer
.append("\\u");
547 buffer
.append("\\u");
559 if (escaped
) buffer
.append('\\');
562 @SuppressWarnings({"HardCodedStringLiteral"})
563 @NotNull public static String
pluralize(@NotNull String suggestion
) {
564 if (suggestion
.endsWith("Child") || suggestion
.endsWith("child")) {
565 return suggestion
+ "ren";
568 if (endsWithChar(suggestion
, 's') || endsWithChar(suggestion
, 'x') || suggestion
.endsWith("ch")) {
569 return suggestion
+ "es";
572 int len
= suggestion
.length();
573 if (endsWithChar(suggestion
, 'y') && len
> 1 && !isVowel(suggestion
.charAt(len
- 2))) {
574 return suggestion
.substring(0, len
- 1) + "ies";
577 return suggestion
+ "s";
580 @NotNull public static String
capitalizeWords(@NotNull String text
, boolean allWords
) {
581 StringTokenizer tokenizer
= new StringTokenizer(text
);
584 boolean toCapitalize
= true;
585 while (tokenizer
.hasMoreTokens()) {
586 String word
= tokenizer
.nextToken();
587 out
+= delim
+ (toCapitalize ?
capitalize(word
) : word
);
590 toCapitalize
= false;
596 public static String
decapitalize(String s
) {
597 return Introspector
.decapitalize(s
);
600 public static boolean isVowel(char c
) {
601 return VOWELS
.indexOf(c
) >= 0;
604 @NotNull public static String
capitalize(@NotNull String s
) {
605 if (s
.length() == 0) return s
;
606 if (s
.length() == 1) return s
.toUpperCase();
609 if (Character
.isUpperCase(s
.charAt(0)) ) return s
;
610 return toUpperCase(s
.charAt(0)) + s
.substring(1);
614 public static String
capitalizeWithJavaBeanConvention(@NotNull String s
) {
615 if (s
.length() > 1 && Character
.isUpperCase(s
.charAt(1))) {
618 return capitalize(s
);
621 public static int stringHashCode(CharSequence chars
) {
622 if (chars
instanceof String
) return chars
.hashCode();
623 if (chars
instanceof CharSequenceWithStringHash
) return chars
.hashCode();
624 if (chars
instanceof CharArrayCharSequence
) return chars
.hashCode();
627 int to
= chars
.length();
628 for (int off
= 0; off
< to
; off
++) {
629 h
= 31 * h
+ chars
.charAt(off
);
634 public static int stringHashCode(CharSequence chars
, int from
, int to
) {
636 for (int off
= from
; off
< to
; off
++) {
637 h
= 31 * h
+ chars
.charAt(off
);
642 public static int stringHashCode(char[] chars
, int from
, int to
) {
644 for (int off
= from
; off
< to
; off
++) {
645 h
= 31 * h
+ chars
[off
];
650 public static int stringHashCodeInsensitive(char[] chars
, int from
, int to
) {
652 for (int off
= from
; off
< to
; off
++) {
653 h
= 31 * h
+ toLowerCase(chars
[off
]);
658 public static int stringHashCodeInsensitive(CharSequence chars
, int from
, int to
) {
660 for (int off
= from
; off
< to
; off
++) {
661 h
= 31 * h
+ toLowerCase(chars
.charAt(off
));
666 public static int stringHashCodeInsensitive(@NotNull CharSequence chars
) {
668 final int len
= chars
.length();
669 for (int i
= 0; i
< len
; i
++) {
670 h
= 31 * h
+ toLowerCase(chars
.charAt(i
));
676 * Equivalent to testee.startsWith(firstPrefix + secondPrefix) but avoids creating an object for concatenation.
679 * @param secondPrefix
682 public static boolean startsWithConcatenationOf(String testee
, String firstPrefix
, String secondPrefix
) {
683 int l1
= firstPrefix
.length();
684 int l2
= secondPrefix
.length();
685 if (testee
.length() < l1
+ l2
) return false;
686 return testee
.startsWith(firstPrefix
) && testee
.regionMatches(l1
, secondPrefix
, 0, l2
);
690 public static String
trimEnd(@NotNull String s
, @NonNls @NotNull String suffix
) {
691 if (s
.endsWith(suffix
)) {
692 return s
.substring(0, s
.lastIndexOf(suffix
));
697 public static boolean startsWithChar(@Nullable CharSequence s
, char prefix
) {
698 return s
!= null && s
.length() != 0 && s
.charAt(0) == prefix
;
701 public static boolean endsWithChar(@Nullable CharSequence s
, char suffix
) {
702 return s
!= null && s
.length() != 0 && s
.charAt(s
.length() - 1) == suffix
;
705 @NotNull public static String
trimStart(@NotNull String s
, @NonNls @NotNull String prefix
) {
706 if (s
.startsWith(prefix
)) {
707 return s
.substring(prefix
.length());
712 @NotNull public static String
pluralize(@NotNull String base
, int n
) {
713 if (n
== 1) return base
;
714 return pluralize(base
);
717 public static void repeatSymbol(Appendable buffer
, char symbol
, int times
) {
719 for (int i
= 0; i
< times
; i
++) {
720 buffer
.append(symbol
);
723 catch (IOException e
) {
728 public static boolean isNotEmpty(final String s
) {
729 return s
!= null && s
.length() > 0;
732 public static boolean isEmpty(final String s
) {
733 return s
== null || s
.length() == 0;
737 public static String
notNullize(final String s
) {
738 return notNullize(s
, "");
742 public static String
notNullize(final String s
, final String defaultValue
) {
743 return s
== null ? defaultValue
: s
;
746 public static boolean isEmptyOrSpaces(final String s
) {
747 return s
== null || s
.trim().length() == 0;
751 public static String
getThrowableText(final Throwable aThrowable
) {
752 StringWriter stringWriter
= new StringWriter();
753 PrintWriter writer
= new PrintWriter(stringWriter
);
754 aThrowable
.printStackTrace(writer
);
755 return stringWriter
.getBuffer().toString();
758 public static String
getThrowableText(final Throwable aThrowable
, @NonNls @NotNull final String stackFrameSkipPattern
) {
759 @NonNls final String prefix
= "\tat ";
760 final String skipPattern
= prefix
+ stackFrameSkipPattern
;
761 final StringWriter stringWriter
= new StringWriter();
762 final PrintWriter writer
= new PrintWriter(stringWriter
) {
763 boolean skipping
= false;
764 public void println(final String x
) {
766 if (!skipping
&& x
.startsWith(skipPattern
)) skipping
= true;
767 else if (skipping
&& !x
.startsWith(prefix
)) skipping
= false;
769 if (skipping
) return;
773 aThrowable
.printStackTrace(writer
);
774 return stringWriter
.getBuffer().toString();
777 public static String
getMessage(Throwable e
) {
778 String result
= e
.getMessage();
779 @NonNls final String exceptionPattern
= "Exception: ";
780 @NonNls final String errorPattern
= "Error: ";
782 while ((result
== null || result
.contains(exceptionPattern
) || result
.contains(errorPattern
)) && e
.getCause() != null) {
784 result
= e
.getMessage();
787 if (result
!= null) {
788 result
= extractMessage(result
, exceptionPattern
);
789 result
= extractMessage(result
, errorPattern
);
795 @NotNull private static String
extractMessage(@NotNull String result
, @NotNull final String errorPattern
) {
796 if (result
.lastIndexOf(errorPattern
) >= 0) {
797 result
= result
.substring(result
.lastIndexOf(errorPattern
) + errorPattern
.length());
802 @NotNull public static String
repeatSymbol(final char aChar
, final int count
) {
803 final StringBuilder buffer
= new StringBuilder(count
);
804 repeatSymbol(buffer
, aChar
, count
);
805 return buffer
.toString();
809 public static List
<String
> splitHonorQuotes(@NotNull String s
, char separator
) {
810 final ArrayList
<String
> result
= new ArrayList
<String
>();
811 final StringBuilder builder
= new StringBuilder();
812 boolean inQuotes
= false;
813 for (int i
= 0; i
< s
.length(); i
++) {
814 final char c
= s
.charAt(i
);
815 if (c
== separator
&& !inQuotes
) {
816 if (builder
.length() > 0) {
817 result
.add(builder
.toString());
818 builder
.setLength(0);
823 if ((c
== '"' || c
== '\'') && !(i
> 0 && s
.charAt(i
- 1) == '\\')) {
824 inQuotes
= !inQuotes
;
829 if (builder
.length() > 0) {
830 result
.add(builder
.toString());
836 @NotNull public static List
<String
> split(@NotNull String s
, @NotNull String separator
) {
837 if (separator
.length() == 0) {
838 return Collections
.singletonList(s
);
840 ArrayList
<String
> result
= new ArrayList
<String
>();
843 int index
= s
.indexOf(separator
, pos
);
844 if (index
== -1) break;
845 String token
= s
.substring(pos
, index
);
846 if (token
.length() != 0) {
849 pos
= index
+ separator
.length();
851 if (pos
< s
.length()) {
852 result
.add(s
.substring(pos
, s
.length()));
858 public static Iterable
<String
> tokenize(@NotNull String s
, @NotNull String separators
) {
859 final com
.intellij
.util
.text
.StringTokenizer tokenizer
= new com
.intellij
.util
.text
.StringTokenizer(s
, separators
);
860 return new Iterable
<String
>() {
861 public Iterator
<String
> iterator() {
862 return new Iterator
<String
>() {
863 public boolean hasNext() {
864 return tokenizer
.hasMoreTokens();
867 public String
next() {
868 return tokenizer
.nextToken();
871 public void remove() {
872 throw new UnsupportedOperationException();
880 public static List
<String
> getWordsIn(@NotNull String text
) {
881 List
<String
> result
= new SmartList
<String
>();
883 for (int i
= 0; i
< text
.length(); i
++) {
884 char c
= text
.charAt(i
);
885 boolean isIdentifierPart
= Character
.isJavaIdentifierPart(c
);
886 if (isIdentifierPart
&& start
== -1) {
889 if (isIdentifierPart
&& i
== text
.length() - 1 && start
!= -1) {
890 result
.add(text
.substring(start
, i
+ 1));
892 else if (!isIdentifierPart
&& start
!= -1) {
893 result
.add(text
.substring(start
, i
));
900 @NotNull public static String
join(@NotNull final String
[] strings
, @NotNull final String separator
) {
901 return join(strings
, 0, strings
.length
, separator
);
904 @NotNull public static String
join(@NotNull final String
[] strings
, int startIndex
, int endIndex
, @NotNull final String separator
) {
905 final StringBuilder result
= new StringBuilder();
906 for (int i
= startIndex
; i
< endIndex
; i
++) {
907 if (i
> startIndex
) result
.append(separator
);
908 result
.append(strings
[i
]);
910 return result
.toString();
913 @NotNull public static String
[] zip(@NotNull String
[] strings1
, @NotNull String
[] strings2
, String separator
) {
914 if (strings1
.length
!= strings2
.length
) throw new IllegalArgumentException();
916 String
[] result
= ArrayUtil
.newStringArray(strings1
.length
);
917 for (int i
= 0; i
< result
.length
; i
++) {
918 result
[i
] = strings1
[i
] + separator
+ strings2
[i
];
924 public static String
[] surround(String
[] strings1
, String prefix
, String suffix
) {
925 String
[] result
= ArrayUtil
.newStringArray(strings1
.length
);
926 for (int i
= 0; i
< result
.length
; i
++) {
927 result
[i
] = prefix
+ strings1
[i
] + suffix
;
934 public static <T
> String
join(@NotNull T
[] items
, @NotNull Function
<T
, String
> f
, @NotNull @NonNls String separator
) {
935 return join(Arrays
.asList(items
), f
, separator
);
939 public static <T
> String
join(@NotNull Collection
<T
> items
, @NotNull Function
<T
, String
> f
, @NotNull @NonNls String separator
) {
940 if (items
.isEmpty()) return "";
941 return join((Iterable
<T
>)items
, f
, separator
);
945 public static <T
> String
join(@NotNull Iterable
<T
> items
, @NotNull Function
<T
, String
> f
, @NotNull @NonNls String separator
) {
946 final StringBuilder result
= new StringBuilder();
947 for (T item
: items
) {
948 String string
= f
.fun(item
);
949 if (string
!= null && string
.length() != 0) {
950 if (result
.length() != 0) result
.append(separator
);
951 result
.append(string
);
954 return result
.toString();
957 @NotNull public static String
join(@NotNull Collection
<?
extends String
> strings
, @NotNull final String separator
) {
958 final StringBuilder result
= new StringBuilder();
959 for (String string
: strings
) {
960 if (string
!= null && string
.length() != 0) {
961 if (result
.length() != 0) result
.append(separator
);
962 result
.append(string
);
965 return result
.toString();
968 @NotNull public static String
join(@NotNull final int[] strings
, @NotNull final String separator
) {
969 final StringBuilder result
= new StringBuilder();
970 for (int i
= 0; i
< strings
.length
; i
++) {
971 if (i
> 0) result
.append(separator
);
972 result
.append(strings
[i
]);
974 return result
.toString();
977 @NotNull public static String
stripQuotesAroundValue(@NotNull String text
) {
978 if (startsWithChar(text
, '\"') || startsWithChar(text
, '\'')) text
= text
.substring(1);
979 if (endsWithChar(text
, '\"') || endsWithChar(text
, '\'')) text
= text
.substring(0, text
.length() - 1);
983 public static boolean isQuotedString(@NotNull String text
) {
984 return startsWithChar(text
, '\"') && endsWithChar(text
, '\"')
985 || startsWithChar(text
, '\'') && endsWithChar(text
, '\'');
989 * Formats the specified file size as a string.
991 * @param fileSize the size to format.
992 * @return the size formatted as a string.
996 @NotNull public static String
formatFileSize(final long fileSize
) {
997 if (fileSize
< 0x400) {
998 return CommonBundle
.message("file.size.format.bytes", fileSize
);
1000 if (fileSize
< 0x100000) {
1001 long kbytes
= fileSize
* 100 / 1024;
1002 final String kbs
= kbytes
/ 100 + "." + formatMinor(kbytes
% 100);
1003 return CommonBundle
.message("file.size.format.kbytes", kbs
);
1005 long mbytes
= fileSize
* 100 / 1024 / 1024;
1006 final String size
= mbytes
/ 100 + "." + formatMinor(mbytes
% 100);
1007 return CommonBundle
.message("file.size.format.mbytes", size
);
1011 private static String
formatMinor(long number
) {
1012 if (number
> 0L && number
<= 9L) {
1013 return "0" + number
;
1015 return String
.valueOf(number
);
1019 * Returns unpluralized variant using English based heuristics like properties -> property, names -> name, children -> child.
1020 * Returns <code>null</code> if failed to match appropriate heuristic.
1022 * @param name english word in plural form
1023 * @return name in singular form or <code>null</code> if failed to find one.
1025 @SuppressWarnings({"HardCodedStringLiteral"})
1027 public static String
unpluralize(@NotNull final String name
) {
1028 if (name
.endsWith("sses") || name
.endsWith("shes") || name
.endsWith("ches") || name
.endsWith("xes")) { //?
1029 return name
.substring(0, name
.length() - 2);
1032 if (name
.endsWith("ses")) {
1033 return name
.substring(0, name
.length() - 1);
1036 if (name
.endsWith("ies")) {
1037 return name
.substring(0, name
.length() - 3) + "y";
1040 String result
= stripEnding(name
, "s");
1041 if (result
!= null) {
1045 if (name
.endsWith("children")) {
1046 return name
.substring(0, name
.length() - "children".length()) + "child";
1049 if (name
.endsWith("Children") && name
.length() > "Children".length()) {
1050 return name
.substring(0, name
.length() - "Children".length()) + "Child";
1056 private static String
stripEnding(String name
, String ending
) {
1057 if (name
.endsWith(ending
)) {
1058 if (name
.equals(ending
)) return name
; // do not return empty string
1059 return name
.substring(0, name
.length() - 1);
1064 public static boolean containsAlphaCharacters(@NotNull String value
) {
1065 for (int i
= 0; i
< value
.length(); i
++) {
1066 if (Character
.isLetter(value
.charAt(i
))) return true;
1071 public static boolean containsAnyChar(@NotNull final String value
, @NotNull final String chars
) {
1072 for (int i
= 0; i
< chars
.length(); i
++) {
1073 if (value
.indexOf(chars
.charAt(i
)) != -1) return true;
1079 public static String
firstLetterToUpperCase(final String displayString
) {
1080 if (displayString
== null || displayString
.length() == 0) return displayString
;
1081 char firstChar
= displayString
.charAt(0);
1082 char uppedFirstChar
= toUpperCase(firstChar
);
1084 if (uppedFirstChar
== firstChar
) return displayString
;
1086 StringBuilder builder
= new StringBuilder(displayString
);
1087 builder
.setCharAt(0, uppedFirstChar
);
1088 return builder
.toString();
1092 * Strip out all characters not accepted by given filter
1093 * @param s e.g. "/n my string "
1094 * @param filter e.g. {@link CharFilter#NOT_WHITESPACE_FILTER}
1095 * @return stripped string e.g. "mystring"
1097 @NotNull public static String
strip(@NotNull final String s
, @NotNull CharFilter filter
) {
1098 StringBuilder result
= new StringBuilder(s
.length());
1099 for (int i
= 0; i
< s
.length(); i
++) {
1100 char ch
= s
.charAt(i
);
1101 if (filter
.accept(ch
)) {
1105 return result
.toString();
1109 * Find position of the first charachter accepted by given filter
1110 * @param s the string to search
1112 * @return position of the first charachter accepted or -1 if not found
1114 public static int findFirst(@NotNull final String s
, @NotNull CharFilter filter
) {
1115 for (int i
= 0; i
< s
.length(); i
++) {
1116 char ch
= s
.charAt(i
);
1117 if (filter
.accept(ch
)) {
1124 @NotNull public static String
replaceSubstring(@NotNull String string
, @NotNull TextRange range
, @NotNull String replacement
) {
1125 return string
.substring(0, range
.getStartOffset()) + replacement
+ string
.substring(range
.getEndOffset());
1128 public static boolean startsWith(@NotNull CharSequence text
, @NotNull CharSequence prefix
) {
1129 int l1
= text
.length();
1130 int l2
= prefix
.length();
1131 if (l1
< l2
) return false;
1133 for (int i
= 0; i
< l2
; i
++) {
1134 if (text
.charAt(i
) != prefix
.charAt(i
)) return false;
1139 public static boolean endsWith(@NotNull CharSequence text
, @NotNull CharSequence suffix
) {
1140 int l1
= text
.length();
1141 int l2
= suffix
.length();
1142 if (l1
< l2
) return false;
1144 for (int i
= l1
-1; i
>= l1
-l2
; i
--) {
1145 if (text
.charAt(i
) != suffix
.charAt(i
+l2
-l1
)) return false;
1152 public static String
commonPrefix(@NotNull String s1
, @NotNull String s2
) {
1153 return s1
.substring(0, commonPrefixLength(s1
, s2
));
1156 public static int commonPrefixLength(@NotNull CharSequence s1
, @NotNull CharSequence s2
) {
1158 for (i
= 0; i
< s1
.length() && i
< s2
.length(); i
++) {
1159 if (s1
.charAt(i
) != s2
.charAt(i
)) {
1167 public static String
commonSuffix(@NotNull String s1
, @NotNull String s2
) {
1168 return s1
.substring(s1
.length() - commonSuffixLength(s1
, s2
));
1171 public static int commonSuffixLength(@NotNull CharSequence s1
, @NotNull CharSequence s2
) {
1172 if (s1
.length() == 0 || s2
.length() == 0) return 0;
1174 for (i
= 0; i
<s1
.length() && i
<s2
.length(); i
++) {
1175 if (s1
.charAt(s1
.length()-i
-1) != s2
.charAt(s2
.length()-i
-1)) {
1182 public static int indexOf(CharSequence s
, char c
) {
1183 return indexOf(s
, c
, 0, s
.length());
1185 public static int indexOf(CharSequence s
, char c
, int start
, int end
) {
1186 for (int i
= start
; i
< end
; i
++) {
1187 if (s
.charAt(i
) == c
) return i
;
1192 public static String
first(@NotNull String text
, final int length
, final boolean appendEllipsis
) {
1193 return text
.length() > length ? text
.substring(0, length
) + (appendEllipsis ?
"..." : "") : text
;
1195 public static CharSequence
first(@NotNull CharSequence text
, final int length
, final boolean appendEllipsis
) {
1196 return text
.length() > length ? text
.subSequence(0, length
) + (appendEllipsis ?
"..." : "") : text
;
1198 public static CharSequence
last(@NotNull CharSequence text
, final int length
, boolean prependEllipsis
) {
1199 return text
.length() > length ?
(prependEllipsis ?
"..." : "") + text
.subSequence(text
.length()-length
, text
.length()) : text
;
1202 public static String
escapeQuotes(@NotNull final String str
) {
1203 int idx
= str
.indexOf('"');
1204 if (idx
< 0) return str
;
1205 StringBuilder buf
= new StringBuilder(str
);
1206 while (idx
< buf
.length()) {
1207 if (buf
.charAt(idx
) == '"') {
1208 buf
.replace(idx
, idx
+ 1, "\\\"");
1215 return buf
.toString();
1218 @NonNls private static final String
[] REPLACES_REFS
= {"<", ">", "&", "'", """};
1219 @NonNls private static final String
[] REPLACES_DISP
= {"<", ">", "&", "'", "\""};
1221 public static String
unescapeXml(final String text
) {
1222 if (text
== null) return null;
1223 return replace(text
, REPLACES_REFS
, REPLACES_DISP
);
1226 public static String
escapeXml(final String text
) {
1227 if (text
== null) return null;
1228 return replace(text
, REPLACES_DISP
, REPLACES_REFS
);
1231 public static String
escapeToRegexp(String text
) {
1232 @NonNls StringBuilder result
= new StringBuilder();
1233 for (int i
= 0; i
< text
.length(); i
++) {
1234 final char c
= text
.charAt(i
);
1235 if (c
== ' ' || Character
.isLetter(c
) || Character
.isDigit(c
)) {
1238 else if (c
== '\n') {
1239 result
.append("\\n");
1242 result
.append('\\').append(c
);
1246 return result
.toString();
1249 public static String
replace(final String text
, final String
[] from
, final String
[] to
) {
1250 final StringBuilder result
= new StringBuilder(text
.length());
1252 for (int i
= 0; i
< text
.length(); i
++) {
1253 for (int j
= 0; j
< from
.length
; j
+= 1) {
1254 String toReplace
= from
[j
];
1255 String replaceWith
= to
[j
];
1257 final int len
= toReplace
.length();
1258 if (text
.regionMatches(i
, toReplace
, 0, len
)) {
1259 result
.append(replaceWith
);
1264 result
.append(text
.charAt(i
));
1266 return result
.toString();
1269 public static String
[] filterEmptyStrings(String
[] strings
) {
1271 for (String string
: strings
) {
1272 if (string
== null || string
.length() == 0) emptyCount
++;
1274 if (emptyCount
== 0) return strings
;
1276 String
[] result
= ArrayUtil
.newStringArray(strings
.length
- emptyCount
);
1278 for (String string
: strings
) {
1279 if (string
== null || string
.length() == 0) continue;
1280 result
[count
++] = string
;
1286 public static int countNewLines(@NotNull CharSequence text
) {
1287 return countChars(text
, '\n');
1290 public static int countChars(@NotNull CharSequence text
, char c
) {
1293 for(int i
= 0; i
< text
.length(); ++i
) {
1294 final char ch
= text
.charAt(i
);
1302 public static String
capitalsOnly(String s
) {
1303 StringBuilder b
= new StringBuilder();
1304 for (int i
= 0; i
< s
.length(); i
++) {
1305 if (Character
.isUpperCase(s
.charAt(i
))) {
1306 b
.append(s
.charAt(i
));
1310 return b
.toString();
1313 // returns null if any of args is null
1315 public static String
joinOrNull(@NotNull String
... args
) {
1316 StringBuilder r
= new StringBuilder();
1317 for (String arg
: args
) {
1318 if (arg
== null) return null;
1321 return r
.toString();
1324 public static String
getPropertyName(@NonNls final String methodName
) {
1325 if (methodName
.startsWith("get")) {
1326 return Introspector
.decapitalize(methodName
.substring(3));
1328 else if (methodName
.startsWith("is")) {
1329 return Introspector
.decapitalize(methodName
.substring(2));
1331 else if (methodName
.startsWith("set")) {
1332 return Introspector
.decapitalize(methodName
.substring(3));
1339 public static boolean isJavaIdentifierStart(char c
) {
1340 return c
>= 'a' && c
<= 'z' || c
>= 'A' && c
<= 'Z' || Character
.isJavaIdentifierStart(c
);
1343 public static boolean isJavaIdentifierPart(char c
) {
1344 return c
>= '0' && c
<= '9' || isJavaIdentifierStart(c
);
1347 public static boolean isJavaIdentifier(String text
) {
1348 int len
= text
.length();
1349 if (len
== 0) return false;
1351 if (!isJavaIdentifierStart(text
.charAt(0))) return false;
1353 for (int i
= 1; i
< len
; i
++) {
1354 if (!isJavaIdentifierPart(text
.charAt(i
))) return false;
1360 public static String
shiftIndentInside(final String initial
, final int i
, boolean shiftEmptyLines
) throws IOException
{
1361 StringBuilder result
= new StringBuilder(initial
.length());
1362 LineReader reader
= new LineReader(new ByteArrayInputStream(initial
.getBytes()));
1363 boolean first
= true;
1364 for (byte[] line
: reader
.readLines()) {
1366 if (!first
) result
.append('\n');
1367 if (line
.length
> 0 || shiftEmptyLines
) {
1368 result
.append(repeatSymbol(' ', i
));
1370 result
.append(new String(line
));
1377 return result
.toString();
1381 * Escape property name or key in property file. Unicode characters are escaped as well.
1383 * @param input an input to escape
1384 * @param isKey if true, they rules for key escaping are applied. The leading space is escaped in that case.
1385 * @return an escaped string
1387 public static String
escapeProperty(final String input
, final boolean isKey
) {
1388 final StringBuilder escaped
= new StringBuilder();
1389 for(int i
=0;i
<input
.length();i
++) {
1390 final char ch
= input
.charAt(i
);
1393 if(isKey
&& i
== 0) {
1394 // only the leading space has to be escaped
1395 escaped
.append('\\');
1397 escaped
.append(' ');
1399 case '\t': escaped
.append("\\t"); break;
1400 case '\r': escaped
.append("\\r"); break;
1401 case '\n': escaped
.append("\\n"); break;
1402 case '\f': escaped
.append("\\f"); break;
1408 escaped
.append('\\'); escaped
.append(ch
);
1411 if(20 < ch
&& ch
< 0x7F ) {
1414 escaped
.append("\\u");
1415 escaped
.append(Character
.forDigit((ch
>> 12) & 0xF, 16));
1416 escaped
.append(Character
.forDigit((ch
>> 8) & 0xF, 16));
1417 escaped
.append(Character
.forDigit((ch
>> 4) & 0xF, 16));
1418 escaped
.append(Character
.forDigit((ch
) & 0xF, 16));
1423 return escaped
.toString();
1426 public static String
getQualifiedName(String packageName
, String className
) {
1427 if (packageName
== null || packageName
.length() == 0) {
1430 return packageName
+ '.' + className
;
1433 public static int compareVersionNumbers(String v1
, String v2
) {
1434 if (v1
== null && v2
== null) {
1437 else if (v1
== null) {
1440 else if (v2
== null) return 1;
1442 String
[] part1
= v1
.split("[\\.\\_\\-]");
1443 String
[] part2
= v2
.split("[\\.\\_\\-]");
1446 for (; idx
< part1
.length
&& idx
< part2
.length
; idx
++) {
1447 String p1
= part1
[idx
];
1448 String p2
= part2
[idx
];
1451 if (p1
.matches("\\d+") && p2
.matches("\\d+")) {
1452 cmp
= new Integer(p1
).compareTo(new Integer(p2
));
1455 cmp
= part1
[idx
].compareTo(part2
[idx
]);
1457 if (cmp
!= 0) return cmp
;
1460 if (part1
.length
== part2
.length
) {
1463 else if (part1
.length
> idx
) {
1471 public static int parseInt(final String string
, final int def
) {
1473 return Integer
.parseInt(string
);
1475 catch (NumberFormatException e
) {
1480 public static int getOccurenceCount(final String text
, final char c
) {
1483 while (i
< text
.length()) {
1484 i
= text
.indexOf(c
, i
);
1495 public static String
fixVariableNameDerivedFromPropertyName(String name
) {
1496 char c
= name
.charAt(0);
1498 return "an" + Character
.toUpperCase(c
) + name
.substring(1);
1500 return "a" + Character
.toUpperCase(c
) + name
.substring(1);
1503 public static void assertValidSeparators(@NotNull CharSequence s
) {
1504 for (int i
= 0; i
< s
.length(); i
++) {
1505 if (s
.charAt(i
) == '\r') {
1506 String context
= String
.valueOf(last(s
.subSequence(0, i
), 10, true)) + first(s
.subSequence(i
, s
.length()), 10, true);
1507 context
= escapeStringCharacters(context
);
1508 LOG
.error("Wrong line separators: '"+context
+"' at offset "+i
);
1513 public static int compare(@Nullable String s1
, @Nullable String s2
) {
1514 if (s1
== s2
) return 0;
1515 if (s1
== null) return 1;
1516 if (s2
== null) return -1;
1517 return s1
.compareTo(s2
);