accessor name convention
[fedora-idea.git] / platform / util / src / com / intellij / openapi / util / text / StringUtil.java
blob8de7b5992d8081784497197dacd293cddc4c6c8e
1 /*
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;
35 import java.util.*;
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++) {
51 char c = buffer[i];
52 if (c == oldChar) {
53 buffer[i] = newChar;
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();
64 int i = 0;
65 while (i < text1.length()) {
66 int i1 = text1.indexOf(oldS1, i);
67 if (i1 < 0) {
68 if (i == 0) return text;
69 newText.append(text, i, text.length());
70 break;
72 else {
73 if (newS == null) return null;
74 newText.append(text, i, i1);
75 newText.append(newS);
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());
90 /**
91 * Implementation copied from {@link String#indexOf(String, int)} except character comparisons made case insensitive
93 * @param where
94 * @param what
95 * @param fromIndex
96 * @return
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;
106 if (fromIndex < 0) {
107 fromIndex = 0;
110 if (targetCount == 0) {
111 return fromIndex;
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 */
124 if (i <= max) {
125 int j = i + 1;
126 int end = j + targetCount - 1;
127 for (int k = 1; j < end && charsEqualIgnoreCase(where.charAt(j), what.charAt(k)); j++, k++) ;
129 if (j == end) {
130 /* Found whole string. */
131 return i;
136 return -1;
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) {
160 if (a < 'a') {
161 return 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') {
171 return a;
174 if (a >= 'A' && a <= 'Z') {
175 return (char)(a + ('a' - 'A'));
178 return Character.toLowerCase(a);
181 @Nullable
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);
191 return fqName;
194 @NotNull
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);
204 return "";
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);
224 if (c == '\n') {
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') {
243 i++;
244 shiftOffsets(offsetsToKeep, buffer.length(), 2, newSeparator.length());
246 else {
247 shiftOffsets(offsetsToKeep, buffer.length(), 1, newSeparator.length());
250 else {
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) {
265 offsets[i] += shift;
270 public static int getLineBreakCount(@NotNull CharSequence text) {
271 int count = 0;
272 for (int i = 0; i < text.length(); i++) {
273 char c = text.charAt(i);
274 if (c == '\n') {
275 count++;
277 else if (c == '\r') {
278 if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
279 i++;
280 count++;
282 else {
283 count++;
287 return count;
290 public static int lineColToOffset(@NotNull CharSequence text, int line, int col) {
291 int curLine = 0;
292 int offset = 0;
293 while (line != curLine) {
294 if (offset == text.length()) return -1;
295 char c = text.charAt(offset);
296 if (c == '\n') {
297 curLine++;
299 else if (c == '\r') {
300 curLine++;
301 if (offset < text.length() - 1 && text.charAt(offset + 1) == '\n') {
302 offset++;
305 offset++;
307 return offset + col;
310 public static int offsetToLineNumber(@NotNull CharSequence text, int offset) {
311 int curLine = 0;
312 int curOffset = 0;
313 while (curOffset < offset) {
314 if (curOffset == text.length()) return -1;
315 char c = text.charAt(curOffset);
316 if (c == '\n') {
317 curLine++;
319 else if (c == '\r') {
320 curLine++;
321 if (curOffset < text.length() - 1 && text.charAt(curOffset + 1) == '\n') {
322 curOffset++;
325 curOffset++;
327 return curLine;
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++) {
337 a[i][0] = i;
340 for (int j = 0; j < s2.length(); j++) {
341 a[0][j] = 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);
358 @NotNull
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)) {
371 int j = i;
372 for (; j < s.length(); j++) {
373 if (!Character.isLetterOrDigit(s.charAt(j))) {
374 break;
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) {
388 return s;
390 else {
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()) {
406 found = true;
407 for (int j = 0; j < preposition.length(); j++) {
408 if (!(toLowerCase(s.charAt(firstChar + j)) == preposition.charAt(j))) {
409 found = false;
413 if (found) {
414 return true;
417 return false;
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);
423 switch (ch) {
424 case'\b':
425 buffer.append("\\b");
426 break;
428 case'\t':
429 buffer.append("\\t");
430 break;
432 case'\n':
433 buffer.append("\\n");
434 break;
436 case'\f':
437 buffer.append("\\f");
438 break;
440 case'\r':
441 buffer.append("\\r");
442 break;
444 case'\"':
445 buffer.append("\\\"");
446 break;
448 case'\\':
449 buffer.append("\\\\");
450 break;
452 default:
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) {
458 buffer.append(0);
460 buffer.append(hexCode);
462 else {
463 buffer.append(ch);
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) != '"') {
484 return s;
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);
493 if (!escaped) {
494 if (ch == '\\') {
495 escaped = true;
497 else {
498 buffer.append(ch);
501 else {
502 switch (ch) {
503 case'n':
504 buffer.append('\n');
505 break;
507 case'r':
508 buffer.append('\r');
509 break;
511 case'b':
512 buffer.append('\b');
513 break;
515 case't':
516 buffer.append('\t');
517 break;
519 case'f':
520 buffer.append('\f');
521 break;
523 case'\'':
524 buffer.append('\'');
525 break;
527 case'\"':
528 buffer.append('\"');
529 break;
531 case'\\':
532 buffer.append('\\');
533 break;
535 case'u':
536 if (idx + 4 < length) {
537 try {
538 int code = Integer.valueOf(s.substring(idx + 1, idx + 5), 16).intValue();
539 idx += 4;
540 buffer.append((char)code);
542 catch (NumberFormatException e) {
543 buffer.append("\\u");
546 else {
547 buffer.append("\\u");
549 break;
551 default:
552 buffer.append(ch);
553 break;
555 escaped = false;
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);
582 String out = "";
583 String delim = "";
584 boolean toCapitalize = true;
585 while (tokenizer.hasMoreTokens()) {
586 String word = tokenizer.nextToken();
587 out += delim + (toCapitalize ? capitalize(word) : word);
588 delim = " ";
589 if (!allWords) {
590 toCapitalize = false;
593 return out;
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();
608 // Optimization
609 if (Character.isUpperCase(s.charAt(0)) ) return s;
610 return toUpperCase(s.charAt(0)) + s.substring(1);
613 @NotNull
614 public static String capitalizeWithJavaBeanConvention(@NotNull String s) {
615 if (s.length() > 1 && Character.isUpperCase(s.charAt(1))) {
616 return s;
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();
626 int h = 0;
627 int to = chars.length();
628 for (int off = 0; off < to; off++) {
629 h = 31 * h + chars.charAt(off);
631 return h;
634 public static int stringHashCode(CharSequence chars, int from, int to) {
635 int h = 0;
636 for (int off = from; off < to; off++) {
637 h = 31 * h + chars.charAt(off);
639 return h;
642 public static int stringHashCode(char[] chars, int from, int to) {
643 int h = 0;
644 for (int off = from; off < to; off++) {
645 h = 31 * h + chars[off];
647 return h;
650 public static int stringHashCodeInsensitive(char[] chars, int from, int to) {
651 int h = 0;
652 for (int off = from; off < to; off++) {
653 h = 31 * h + toLowerCase(chars[off]);
655 return h;
658 public static int stringHashCodeInsensitive(CharSequence chars, int from, int to) {
659 int h = 0;
660 for (int off = from; off < to; off++) {
661 h = 31 * h + toLowerCase(chars.charAt(off));
663 return h;
666 public static int stringHashCodeInsensitive(@NotNull CharSequence chars) {
667 int h = 0;
668 final int len = chars.length();
669 for (int i = 0; i < len; i++) {
670 h = 31 * h + toLowerCase(chars.charAt(i));
672 return h;
676 * Equivalent to testee.startsWith(firstPrefix + secondPrefix) but avoids creating an object for concatenation.
677 * @param testee
678 * @param firstPrefix
679 * @param secondPrefix
680 * @return
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);
689 @NotNull
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));
694 return s;
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());
709 return s;
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) {
718 try {
719 for (int i = 0; i < times; i++) {
720 buffer.append(symbol);
723 catch (IOException e) {
724 LOG.error(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;
736 @NotNull
737 public static String notNullize(final String s) {
738 return notNullize(s, "");
741 @NotNull
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) {
765 if (x != null) {
766 if (!skipping && x.startsWith(skipPattern)) skipping = true;
767 else if (skipping && !x.startsWith(prefix)) skipping = false;
769 if (skipping) return;
770 super.println(x);
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) {
783 e = e.getCause();
784 result = e.getMessage();
787 if (result != null) {
788 result = extractMessage(result, exceptionPattern);
789 result = extractMessage(result, errorPattern);
792 return result;
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());
799 return result;
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();
808 @NotNull
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);
820 continue;
823 if ((c == '"' || c == '\'') && !(i > 0 && s.charAt(i - 1) == '\\')) {
824 inQuotes = !inQuotes;
826 builder.append(c);
829 if (builder.length() > 0) {
830 result.add(builder.toString());
832 return result;
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>();
841 int pos = 0;
842 while (true) {
843 int index = s.indexOf(separator, pos);
844 if (index == -1) break;
845 String token = s.substring(pos, index);
846 if (token.length() != 0) {
847 result.add(token);
849 pos = index + separator.length();
851 if (pos < s.length()) {
852 result.add(s.substring(pos, s.length()));
854 return result;
857 @NotNull
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();
879 @NotNull
880 public static List<String> getWordsIn(@NotNull String text) {
881 List<String> result = new SmartList<String>();
882 int start = -1;
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) {
887 start = i;
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));
894 start = -1;
897 return result;
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];
921 return result;
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;
930 return result;
933 @NotNull
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);
938 @NotNull
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);
944 @NotNull
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);
980 return text;
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.
993 * @since 5.0.1
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);
1010 @NotNull
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"})
1026 @Nullable
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) {
1042 return result;
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";
1053 return null;
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);
1061 return null;
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;
1068 return false;
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;
1076 return false;
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)) {
1102 result.append(ch);
1105 return result.toString();
1109 * Find position of the first charachter accepted by given filter
1110 * @param s the string to search
1111 * @param filter
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)) {
1118 return i;
1121 return -1;
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;
1137 return true;
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;
1148 return true;
1151 @NotNull
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) {
1157 int i;
1158 for (i = 0; i < s1.length() && i < s2.length(); i++) {
1159 if (s1.charAt(i) != s2.charAt(i)) {
1160 break;
1163 return i;
1166 @NotNull
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;
1173 int i;
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)) {
1176 break;
1179 return i;
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;
1189 return -1;
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, "\\\"");
1209 idx += 2;
1211 else {
1212 idx += 1;
1215 return buf.toString();
1218 @NonNls private static final String[] REPLACES_REFS = {"&lt;", "&gt;", "&amp;", "&apos;", "&quot;"};
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)) {
1236 result.append(c);
1238 else if (c == '\n') {
1239 result.append("\\n");
1241 else {
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());
1251 replace:
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);
1260 i += len - 1;
1261 continue replace;
1264 result.append(text.charAt(i));
1266 return result.toString();
1269 public static String[] filterEmptyStrings(String[] strings) {
1270 int emptyCount = 0;
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);
1277 int count = 0;
1278 for (String string : strings) {
1279 if (string == null || string.length() == 0) continue;
1280 result[count++] = string;
1283 return result;
1286 public static int countNewLines(@NotNull CharSequence text) {
1287 return countChars(text, '\n');
1290 public static int countChars(@NotNull CharSequence text, char c) {
1291 int count = 0;
1293 for(int i = 0; i < text.length(); ++i) {
1294 final char ch = text.charAt(i);
1295 if (ch == c) {
1296 ++count;
1299 return count;
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
1314 @Nullable
1315 public static String joinOrNull(@NotNull String... args) {
1316 StringBuilder r = new StringBuilder();
1317 for (String arg : args) {
1318 if (arg == null) return null;
1319 r.append(arg);
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));
1334 else {
1335 return null;
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;
1357 return true;
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()) {
1365 try {
1366 if (!first) result.append('\n');
1367 if (line.length > 0 || shiftEmptyLines) {
1368 result.append(repeatSymbol(' ', i));
1370 result.append(new String(line));
1372 finally {
1373 first = false;
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);
1391 switch(ch) {
1392 case ' ':
1393 if(isKey && i == 0) {
1394 // only the leading space has to be escaped
1395 escaped.append('\\');
1397 escaped.append(' ');
1398 break;
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;
1403 case '\\':
1404 case '#':
1405 case '!':
1406 case ':':
1407 case '=':
1408 escaped.append('\\'); escaped.append(ch);
1409 break;
1410 default:
1411 if(20 < ch && ch < 0x7F ) {
1412 escaped.append(ch);
1413 } else {
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));
1420 break;
1423 return escaped.toString();
1426 public static String getQualifiedName(String packageName, String className) {
1427 if (packageName == null || packageName.length() == 0) {
1428 return className;
1430 return packageName + '.' + className;
1433 public static int compareVersionNumbers(String v1, String v2) {
1434 if (v1 == null && v2 == null) {
1435 return 0;
1437 else if (v1 == null) {
1438 return -1;
1440 else if (v2 == null) return 1;
1442 String[] part1 = v1.split("[\\.\\_\\-]");
1443 String[] part2 = v2.split("[\\.\\_\\-]");
1445 int idx = 0;
1446 for (; idx < part1.length && idx < part2.length; idx++) {
1447 String p1 = part1[idx];
1448 String p2 = part2[idx];
1450 int cmp;
1451 if (p1.matches("\\d+") && p2.matches("\\d+")) {
1452 cmp = new Integer(p1).compareTo(new Integer(p2));
1454 else {
1455 cmp = part1[idx].compareTo(part2[idx]);
1457 if (cmp != 0) return cmp;
1460 if (part1.length == part2.length) {
1461 return 0;
1463 else if (part1.length > idx) {
1464 return 1;
1466 else {
1467 return -1;
1471 public static int parseInt(final String string, final int def) {
1472 try {
1473 return Integer.parseInt(string);
1475 catch (NumberFormatException e) {
1476 return def;
1480 public static int getOccurenceCount(final String text, final char c) {
1481 int res = 0;
1482 int i = 0;
1483 while (i < text.length()) {
1484 i = text.indexOf(c, i);
1485 if (i >= 0) {
1486 res++;
1487 i++;
1488 } else {
1489 break;
1492 return res;
1495 public static String fixVariableNameDerivedFromPropertyName(String name) {
1496 char c = name.charAt(0);
1497 if (isVowel(c)) {
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);