1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.base;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.annotations.Beta;
23 import com.google.common.annotations.GwtCompatible;
24 import com.google.common.annotations.GwtIncompatible;
25
26 import java.util.Arrays;
27 import java.util.BitSet;
28
29 import javax.annotation.CheckReturnValue;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 @Beta
56 @GwtCompatible(emulated = true)
57 public abstract class CharMatcher implements Predicate<Character> {
58
59
60
61
62
63
64
65
66
67 public static final CharMatcher BREAKING_WHITESPACE = new CharMatcher() {
68 @Override
69 public boolean matches(char c) {
70 switch (c) {
71 case '\t':
72 case '\n':
73 case '\013':
74 case '\f':
75 case '\r':
76 case ' ':
77 case '\u0085':
78 case '\u1680':
79 case '\u2028':
80 case '\u2029':
81 case '\u205f':
82 case '\u3000':
83 return true;
84 case '\u2007':
85 return false;
86 default:
87 return c >= '\u2000' && c <= '\u200a';
88 }
89 }
90
91 @Override
92 public String toString() {
93 return "CharMatcher.BREAKING_WHITESPACE";
94 }
95 };
96
97
98
99
100 public static final CharMatcher ASCII = inRange('\0', '\u007f', "CharMatcher.ASCII");
101
102 private static class RangesMatcher extends CharMatcher {
103 private final char[] rangeStarts;
104 private final char[] rangeEnds;
105
106 RangesMatcher(String description, char[] rangeStarts, char[] rangeEnds) {
107 super(description);
108 this.rangeStarts = rangeStarts;
109 this.rangeEnds = rangeEnds;
110 checkArgument(rangeStarts.length == rangeEnds.length);
111 for (int i = 0; i < rangeStarts.length; i++) {
112 checkArgument(rangeStarts[i] <= rangeEnds[i]);
113 if (i + 1 < rangeStarts.length) {
114 checkArgument(rangeEnds[i] < rangeStarts[i + 1]);
115 }
116 }
117 }
118
119 @Override
120 public boolean matches(char c) {
121 int index = Arrays.binarySearch(rangeStarts, c);
122 if (index >= 0) {
123 return true;
124 } else {
125 index = ~index - 1;
126 return index >= 0 && c <= rangeEnds[index];
127 }
128 }
129 }
130
131
132 private static final String ZEROES = "0\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6"
133 + "\u0c66\u0ce6\u0d66\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u1810\u1946\u19d0\u1b50\u1bb0"
134 + "\u1c40\u1c50\ua620\ua8d0\ua900\uaa50\uff10";
135
136 private static final String NINES;
137 static {
138 StringBuilder builder = new StringBuilder(ZEROES.length());
139 for (int i = 0; i < ZEROES.length(); i++) {
140 builder.append((char) (ZEROES.charAt(i) + 9));
141 }
142 NINES = builder.toString();
143 }
144
145
146
147
148
149
150 public static final CharMatcher DIGIT = new RangesMatcher(
151 "CharMatcher.DIGIT", ZEROES.toCharArray(), NINES.toCharArray());
152
153
154
155
156
157
158 public static final CharMatcher JAVA_DIGIT = new CharMatcher("CharMatcher.JAVA_DIGIT") {
159 @Override public boolean matches(char c) {
160 return Character.isDigit(c);
161 }
162 };
163
164
165
166
167
168
169 public static final CharMatcher JAVA_LETTER = new CharMatcher("CharMatcher.JAVA_LETTER") {
170 @Override public boolean matches(char c) {
171 return Character.isLetter(c);
172 }
173 };
174
175
176
177
178
179 public static final CharMatcher JAVA_LETTER_OR_DIGIT =
180 new CharMatcher("CharMatcher.JAVA_LETTER_OR_DIGIT") {
181 @Override public boolean matches(char c) {
182 return Character.isLetterOrDigit(c);
183 }
184 };
185
186
187
188
189
190 public static final CharMatcher JAVA_UPPER_CASE =
191 new CharMatcher("CharMatcher.JAVA_UPPER_CASE") {
192 @Override public boolean matches(char c) {
193 return Character.isUpperCase(c);
194 }
195 };
196
197
198
199
200
201 public static final CharMatcher JAVA_LOWER_CASE =
202 new CharMatcher("CharMatcher.JAVA_LOWER_CASE") {
203 @Override public boolean matches(char c) {
204 return Character.isLowerCase(c);
205 }
206 };
207
208
209
210
211
212 public static final CharMatcher JAVA_ISO_CONTROL =
213 inRange('\u0000', '\u001f')
214 .or(inRange('\u007f', '\u009f'))
215 .withToString("CharMatcher.JAVA_ISO_CONTROL");
216
217
218
219
220
221
222 public static final CharMatcher INVISIBLE = new RangesMatcher("CharMatcher.INVISIBLE", (
223 "\u0000\u007f\u00ad\u0600\u061c\u06dd\u070f\u1680\u180e\u2000\u2028\u205f\u2066\u2067\u2068"
224 + "\u2069\u206a\u3000\ud800\ufeff\ufff9\ufffa").toCharArray(), (
225 "\u0020\u00a0\u00ad\u0604\u061c\u06dd\u070f\u1680\u180e\u200f\u202f\u2064\u2066\u2067\u2068"
226 + "\u2069\u206f\u3000\uf8ff\ufeff\ufff9\ufffb").toCharArray());
227
228 private static String showCharacter(char c) {
229 String hex = "0123456789ABCDEF";
230 char[] tmp = {'\\', 'u', '\0', '\0', '\0', '\0'};
231 for (int i = 0; i < 4; i++) {
232 tmp[5 - i] = hex.charAt(c & 0xF);
233 c >>= 4;
234 }
235 return String.copyValueOf(tmp);
236
237 }
238
239
240
241
242
243
244
245
246
247 public static final CharMatcher SINGLE_WIDTH = new RangesMatcher("CharMatcher.SINGLE_WIDTH",
248 "\u0000\u05be\u05d0\u05f3\u0600\u0750\u0e00\u1e00\u2100\ufb50\ufe70\uff61".toCharArray(),
249 "\u04f9\u05be\u05ea\u05f4\u06ff\u077f\u0e7f\u20af\u213a\ufdff\ufeff\uffdc".toCharArray());
250
251
252 public static final CharMatcher ANY =
253 new FastMatcher("CharMatcher.ANY") {
254 @Override public boolean matches(char c) {
255 return true;
256 }
257
258 @Override public int indexIn(CharSequence sequence) {
259 return (sequence.length() == 0) ? -1 : 0;
260 }
261
262 @Override public int indexIn(CharSequence sequence, int start) {
263 int length = sequence.length();
264 Preconditions.checkPositionIndex(start, length);
265 return (start == length) ? -1 : start;
266 }
267
268 @Override public int lastIndexIn(CharSequence sequence) {
269 return sequence.length() - 1;
270 }
271
272 @Override public boolean matchesAllOf(CharSequence sequence) {
273 checkNotNull(sequence);
274 return true;
275 }
276
277 @Override public boolean matchesNoneOf(CharSequence sequence) {
278 return sequence.length() == 0;
279 }
280
281 @Override public String removeFrom(CharSequence sequence) {
282 checkNotNull(sequence);
283 return "";
284 }
285
286 @Override public String replaceFrom(CharSequence sequence, char replacement) {
287 char[] array = new char[sequence.length()];
288 Arrays.fill(array, replacement);
289 return new String(array);
290 }
291
292 @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) {
293 StringBuilder retval = new StringBuilder(sequence.length() * replacement.length());
294 for (int i = 0; i < sequence.length(); i++) {
295 retval.append(replacement);
296 }
297 return retval.toString();
298 }
299
300 @Override public String collapseFrom(CharSequence sequence, char replacement) {
301 return (sequence.length() == 0) ? "" : String.valueOf(replacement);
302 }
303
304 @Override public String trimFrom(CharSequence sequence) {
305 checkNotNull(sequence);
306 return "";
307 }
308
309 @Override public int countIn(CharSequence sequence) {
310 return sequence.length();
311 }
312
313 @Override public CharMatcher and(CharMatcher other) {
314 return checkNotNull(other);
315 }
316
317 @Override public CharMatcher or(CharMatcher other) {
318 checkNotNull(other);
319 return this;
320 }
321
322 @Override public CharMatcher negate() {
323 return NONE;
324 }
325 };
326
327
328 public static final CharMatcher NONE =
329 new FastMatcher("CharMatcher.NONE") {
330 @Override public boolean matches(char c) {
331 return false;
332 }
333
334 @Override public int indexIn(CharSequence sequence) {
335 checkNotNull(sequence);
336 return -1;
337 }
338
339 @Override public int indexIn(CharSequence sequence, int start) {
340 int length = sequence.length();
341 Preconditions.checkPositionIndex(start, length);
342 return -1;
343 }
344
345 @Override public int lastIndexIn(CharSequence sequence) {
346 checkNotNull(sequence);
347 return -1;
348 }
349
350 @Override public boolean matchesAllOf(CharSequence sequence) {
351 return sequence.length() == 0;
352 }
353
354 @Override public boolean matchesNoneOf(CharSequence sequence) {
355 checkNotNull(sequence);
356 return true;
357 }
358
359 @Override public String removeFrom(CharSequence sequence) {
360 return sequence.toString();
361 }
362
363 @Override public String replaceFrom(CharSequence sequence, char replacement) {
364 return sequence.toString();
365 }
366
367 @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) {
368 checkNotNull(replacement);
369 return sequence.toString();
370 }
371
372 @Override public String collapseFrom(CharSequence sequence, char replacement) {
373 return sequence.toString();
374 }
375
376 @Override public String trimFrom(CharSequence sequence) {
377 return sequence.toString();
378 }
379
380 @Override
381 public String trimLeadingFrom(CharSequence sequence) {
382 return sequence.toString();
383 }
384
385 @Override
386 public String trimTrailingFrom(CharSequence sequence) {
387 return sequence.toString();
388 }
389
390 @Override public int countIn(CharSequence sequence) {
391 checkNotNull(sequence);
392 return 0;
393 }
394
395 @Override public CharMatcher and(CharMatcher other) {
396 checkNotNull(other);
397 return this;
398 }
399
400 @Override public CharMatcher or(CharMatcher other) {
401 return checkNotNull(other);
402 }
403
404 @Override public CharMatcher negate() {
405 return ANY;
406 }
407 };
408
409
410
411
412
413
414 public static CharMatcher is(final char match) {
415 String description = "CharMatcher.is('" + showCharacter(match) + "')";
416 return new FastMatcher(description) {
417 @Override public boolean matches(char c) {
418 return c == match;
419 }
420
421 @Override public String replaceFrom(CharSequence sequence, char replacement) {
422 return sequence.toString().replace(match, replacement);
423 }
424
425 @Override public CharMatcher and(CharMatcher other) {
426 return other.matches(match) ? this : NONE;
427 }
428
429 @Override public CharMatcher or(CharMatcher other) {
430 return other.matches(match) ? other : super.or(other);
431 }
432
433 @Override public CharMatcher negate() {
434 return isNot(match);
435 }
436
437 @GwtIncompatible("java.util.BitSet")
438 @Override
439 void setBits(BitSet table) {
440 table.set(match);
441 }
442 };
443 }
444
445
446
447
448
449
450 public static CharMatcher isNot(final char match) {
451 String description = "CharMatcher.isNot('" + showCharacter(match) + "')";
452 return new FastMatcher(description) {
453 @Override public boolean matches(char c) {
454 return c != match;
455 }
456
457 @Override public CharMatcher and(CharMatcher other) {
458 return other.matches(match) ? super.and(other) : other;
459 }
460
461 @Override public CharMatcher or(CharMatcher other) {
462 return other.matches(match) ? ANY : this;
463 }
464
465 @GwtIncompatible("java.util.BitSet")
466 @Override
467 void setBits(BitSet table) {
468 table.set(0, match);
469 table.set(match + 1, Character.MAX_VALUE + 1);
470 }
471
472 @Override public CharMatcher negate() {
473 return is(match);
474 }
475 };
476 }
477
478
479
480
481
482 public static CharMatcher anyOf(final CharSequence sequence) {
483 switch (sequence.length()) {
484 case 0:
485 return NONE;
486 case 1:
487 return is(sequence.charAt(0));
488 case 2:
489 return isEither(sequence.charAt(0), sequence.charAt(1));
490 default:
491
492 }
493
494 final char[] chars = sequence.toString().toCharArray();
495 Arrays.sort(chars);
496 StringBuilder description = new StringBuilder("CharMatcher.anyOf(\"");
497 for (char c : chars) {
498 description.append(showCharacter(c));
499 }
500 description.append("\")");
501 return new CharMatcher(description.toString()) {
502 @Override public boolean matches(char c) {
503 return Arrays.binarySearch(chars, c) >= 0;
504 }
505
506 @Override
507 @GwtIncompatible("java.util.BitSet")
508 void setBits(BitSet table) {
509 for (char c : chars) {
510 table.set(c);
511 }
512 }
513 };
514 }
515
516 private static CharMatcher isEither(
517 final char match1,
518 final char match2) {
519 String description = "CharMatcher.anyOf(\"" +
520 showCharacter(match1) + showCharacter(match2) + "\")";
521 return new FastMatcher(description) {
522 @Override public boolean matches(char c) {
523 return c == match1 || c == match2;
524 }
525
526 @GwtIncompatible("java.util.BitSet")
527 @Override void setBits(BitSet table) {
528 table.set(match1);
529 table.set(match2);
530 }
531 };
532 }
533
534
535
536
537
538 public static CharMatcher noneOf(CharSequence sequence) {
539 return anyOf(sequence).negate();
540 }
541
542
543
544
545
546
547
548
549 public static CharMatcher inRange(final char startInclusive, final char endInclusive) {
550 checkArgument(endInclusive >= startInclusive);
551 String description = "CharMatcher.inRange('" +
552 showCharacter(startInclusive) + "', '" +
553 showCharacter(endInclusive) + "')";
554 return inRange(startInclusive, endInclusive, description);
555 }
556
557 static CharMatcher inRange(final char startInclusive, final char endInclusive,
558 String description) {
559 return new FastMatcher(description) {
560 @Override public boolean matches(char c) {
561 return startInclusive <= c && c <= endInclusive;
562 }
563
564 @GwtIncompatible("java.util.BitSet")
565 @Override void setBits(BitSet table) {
566 table.set(startInclusive, endInclusive + 1);
567 }
568 };
569 }
570
571
572
573
574
575 public static CharMatcher forPredicate(final Predicate<? super Character> predicate) {
576 checkNotNull(predicate);
577 if (predicate instanceof CharMatcher) {
578 return (CharMatcher) predicate;
579 }
580 String description = "CharMatcher.forPredicate(" + predicate + ")";
581 return new CharMatcher(description) {
582 @Override public boolean matches(char c) {
583 return predicate.apply(c);
584 }
585
586 @Override public boolean apply(Character character) {
587 return predicate.apply(checkNotNull(character));
588 }
589 };
590 }
591
592
593 final String description;
594
595
596
597
598
599
600 CharMatcher(String description) {
601 this.description = description;
602 }
603
604
605
606
607
608 protected CharMatcher() {
609 description = super.toString();
610 }
611
612
613
614
615 public abstract boolean matches(char c);
616
617
618
619
620
621
622 public CharMatcher negate() {
623 return new NegatedMatcher(this);
624 }
625
626 private static class NegatedMatcher extends CharMatcher {
627 final CharMatcher original;
628
629 NegatedMatcher(String toString, CharMatcher original) {
630 super(toString);
631 this.original = original;
632 }
633
634 NegatedMatcher(CharMatcher original) {
635 this(original + ".negate()", original);
636 }
637
638 @Override public boolean matches(char c) {
639 return !original.matches(c);
640 }
641
642 @Override public boolean matchesAllOf(CharSequence sequence) {
643 return original.matchesNoneOf(sequence);
644 }
645
646 @Override public boolean matchesNoneOf(CharSequence sequence) {
647 return original.matchesAllOf(sequence);
648 }
649
650 @Override public int countIn(CharSequence sequence) {
651 return sequence.length() - original.countIn(sequence);
652 }
653
654 @GwtIncompatible("java.util.BitSet")
655 @Override
656 void setBits(BitSet table) {
657 BitSet tmp = new BitSet();
658 original.setBits(tmp);
659 tmp.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1);
660 table.or(tmp);
661 }
662
663 @Override public CharMatcher negate() {
664 return original;
665 }
666
667 @Override
668 CharMatcher withToString(String description) {
669 return new NegatedMatcher(description, original);
670 }
671 }
672
673
674
675
676 public CharMatcher and(CharMatcher other) {
677 return new And(this, checkNotNull(other));
678 }
679
680 private static class And extends CharMatcher {
681 final CharMatcher first;
682 final CharMatcher second;
683
684 And(CharMatcher a, CharMatcher b) {
685 this(a, b, "CharMatcher.and(" + a + ", " + b + ")");
686 }
687
688 And(CharMatcher a, CharMatcher b, String description) {
689 super(description);
690 first = checkNotNull(a);
691 second = checkNotNull(b);
692 }
693
694 @Override
695 public boolean matches(char c) {
696 return first.matches(c) && second.matches(c);
697 }
698
699 @GwtIncompatible("java.util.BitSet")
700 @Override
701 void setBits(BitSet table) {
702 BitSet tmp1 = new BitSet();
703 first.setBits(tmp1);
704 BitSet tmp2 = new BitSet();
705 second.setBits(tmp2);
706 tmp1.and(tmp2);
707 table.or(tmp1);
708 }
709
710 @Override
711 CharMatcher withToString(String description) {
712 return new And(first, second, description);
713 }
714 }
715
716
717
718
719 public CharMatcher or(CharMatcher other) {
720 return new Or(this, checkNotNull(other));
721 }
722
723 private static class Or extends CharMatcher {
724 final CharMatcher first;
725 final CharMatcher second;
726
727 Or(CharMatcher a, CharMatcher b, String description) {
728 super(description);
729 first = checkNotNull(a);
730 second = checkNotNull(b);
731 }
732
733 Or(CharMatcher a, CharMatcher b) {
734 this(a, b, "CharMatcher.or(" + a + ", " + b + ")");
735 }
736
737 @GwtIncompatible("java.util.BitSet")
738 @Override
739 void setBits(BitSet table) {
740 first.setBits(table);
741 second.setBits(table);
742 }
743
744 @Override
745 public boolean matches(char c) {
746 return first.matches(c) || second.matches(c);
747 }
748
749 @Override
750 CharMatcher withToString(String description) {
751 return new Or(first, second, description);
752 }
753 }
754
755
756
757
758
759
760
761
762
763
764 public CharMatcher precomputed() {
765 return Platform.precomputeCharMatcher(this);
766 }
767
768
769
770
771
772
773
774 CharMatcher withToString(String description) {
775 throw new UnsupportedOperationException();
776 }
777
778 private static final int DISTINCT_CHARS = Character.MAX_VALUE - Character.MIN_VALUE + 1;
779
780
781
782
783
784
785
786
787
788
789
790 @GwtIncompatible("java.util.BitSet")
791 CharMatcher precomputedInternal() {
792 final BitSet table = new BitSet();
793 setBits(table);
794 int totalCharacters = table.cardinality();
795 if (totalCharacters * 2 <= DISTINCT_CHARS) {
796 return precomputedPositive(totalCharacters, table, description);
797 } else {
798
799 table.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1);
800 int negatedCharacters = DISTINCT_CHARS - totalCharacters;
801 String suffix = ".negate()";
802 String negatedDescription = description.endsWith(suffix)
803 ? description.substring(0, description.length() - suffix.length())
804 : description + suffix;
805 return new NegatedFastMatcher(toString(),
806 precomputedPositive(negatedCharacters, table, negatedDescription));
807 }
808 }
809
810
811
812
813 abstract static class FastMatcher extends CharMatcher {
814 FastMatcher() {
815 super();
816 }
817
818 FastMatcher(String description) {
819 super(description);
820 }
821
822 @Override
823 public final CharMatcher precomputed() {
824 return this;
825 }
826
827 @Override
828 public CharMatcher negate() {
829 return new NegatedFastMatcher(this);
830 }
831 }
832
833 static final class NegatedFastMatcher extends NegatedMatcher {
834 NegatedFastMatcher(CharMatcher original) {
835 super(original);
836 }
837
838 NegatedFastMatcher(String toString, CharMatcher original) {
839 super(toString, original);
840 }
841
842 @Override
843 public final CharMatcher precomputed() {
844 return this;
845 }
846
847 @Override
848 CharMatcher withToString(String description) {
849 return new NegatedFastMatcher(description, original);
850 }
851 }
852
853
854
855
856 @GwtIncompatible("java.util.BitSet")
857 private static CharMatcher precomputedPositive(
858 int totalCharacters,
859 BitSet table,
860 String description) {
861 switch (totalCharacters) {
862 case 0:
863 return NONE;
864 case 1:
865 return is((char) table.nextSetBit(0));
866 case 2:
867 char c1 = (char) table.nextSetBit(0);
868 char c2 = (char) table.nextSetBit(c1 + 1);
869 return isEither(c1, c2);
870 default:
871 return isSmall(totalCharacters, table.length())
872 ? SmallCharMatcher.from(table, description)
873 : new BitSetMatcher(table, description);
874 }
875 }
876
877 @GwtIncompatible("SmallCharMatcher")
878 private static boolean isSmall(int totalCharacters, int tableLength) {
879 return totalCharacters <= SmallCharMatcher.MAX_SIZE
880 && tableLength > (totalCharacters * 4 * Character.SIZE);
881
882 }
883
884 @GwtIncompatible("java.util.BitSet")
885 private static class BitSetMatcher extends FastMatcher {
886 private final BitSet table;
887
888 private BitSetMatcher(BitSet table, String description) {
889 super(description);
890 if (table.length() + Long.SIZE < table.size()) {
891 table = (BitSet) table.clone();
892
893 }
894 this.table = table;
895 }
896
897 @Override public boolean matches(char c) {
898 return table.get(c);
899 }
900
901 @Override
902 void setBits(BitSet bitSet) {
903 bitSet.or(table);
904 }
905 }
906
907
908
909
910 @GwtIncompatible("java.util.BitSet")
911 void setBits(BitSet table) {
912 for (int c = Character.MAX_VALUE; c >= Character.MIN_VALUE; c--) {
913 if (matches((char) c)) {
914 table.set(c);
915 }
916 }
917 }
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932 public boolean matchesAnyOf(CharSequence sequence) {
933 return !matchesNoneOf(sequence);
934 }
935
936
937
938
939
940
941
942
943
944
945
946 public boolean matchesAllOf(CharSequence sequence) {
947 for (int i = sequence.length() - 1; i >= 0; i--) {
948 if (!matches(sequence.charAt(i))) {
949 return false;
950 }
951 }
952 return true;
953 }
954
955
956
957
958
959
960
961
962
963
964
965
966 public boolean matchesNoneOf(CharSequence sequence) {
967 return indexIn(sequence) == -1;
968 }
969
970
971
972
973
974
975
976
977
978
979
980 public int indexIn(CharSequence sequence) {
981 int length = sequence.length();
982 for (int i = 0; i < length; i++) {
983 if (matches(sequence.charAt(i))) {
984 return i;
985 }
986 }
987 return -1;
988 }
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005 public int indexIn(CharSequence sequence, int start) {
1006 int length = sequence.length();
1007 Preconditions.checkPositionIndex(start, length);
1008 for (int i = start; i < length; i++) {
1009 if (matches(sequence.charAt(i))) {
1010 return i;
1011 }
1012 }
1013 return -1;
1014 }
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026 public int lastIndexIn(CharSequence sequence) {
1027 for (int i = sequence.length() - 1; i >= 0; i--) {
1028 if (matches(sequence.charAt(i))) {
1029 return i;
1030 }
1031 }
1032 return -1;
1033 }
1034
1035
1036
1037
1038 public int countIn(CharSequence sequence) {
1039 int count = 0;
1040 for (int i = 0; i < sequence.length(); i++) {
1041 if (matches(sequence.charAt(i))) {
1042 count++;
1043 }
1044 }
1045 return count;
1046 }
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056 @CheckReturnValue
1057 public String removeFrom(CharSequence sequence) {
1058 String string = sequence.toString();
1059 int pos = indexIn(string);
1060 if (pos == -1) {
1061 return string;
1062 }
1063
1064 char[] chars = string.toCharArray();
1065 int spread = 1;
1066
1067
1068 OUT: while (true) {
1069 pos++;
1070 while (true) {
1071 if (pos == chars.length) {
1072 break OUT;
1073 }
1074 if (matches(chars[pos])) {
1075 break;
1076 }
1077 chars[pos - spread] = chars[pos];
1078 pos++;
1079 }
1080 spread++;
1081 }
1082 return new String(chars, 0, pos - spread);
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093 @CheckReturnValue
1094 public String retainFrom(CharSequence sequence) {
1095 return negate().removeFrom(sequence);
1096 }
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115 @CheckReturnValue
1116 public String replaceFrom(CharSequence sequence, char replacement) {
1117 String string = sequence.toString();
1118 int pos = indexIn(string);
1119 if (pos == -1) {
1120 return string;
1121 }
1122 char[] chars = string.toCharArray();
1123 chars[pos] = replacement;
1124 for (int i = pos + 1; i < chars.length; i++) {
1125 if (matches(chars[i])) {
1126 chars[i] = replacement;
1127 }
1128 }
1129 return new String(chars);
1130 }
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148 @CheckReturnValue
1149 public String replaceFrom(CharSequence sequence, CharSequence replacement) {
1150 int replacementLen = replacement.length();
1151 if (replacementLen == 0) {
1152 return removeFrom(sequence);
1153 }
1154 if (replacementLen == 1) {
1155 return replaceFrom(sequence, replacement.charAt(0));
1156 }
1157
1158 String string = sequence.toString();
1159 int pos = indexIn(string);
1160 if (pos == -1) {
1161 return string;
1162 }
1163
1164 int len = string.length();
1165 StringBuilder buf = new StringBuilder((len * 3 / 2) + 16);
1166
1167 int oldpos = 0;
1168 do {
1169 buf.append(string, oldpos, pos);
1170 buf.append(replacement);
1171 oldpos = pos + 1;
1172 pos = indexIn(string, oldpos);
1173 } while (pos != -1);
1174
1175 buf.append(string, oldpos, len);
1176 return buf.toString();
1177 }
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193 @CheckReturnValue
1194 public String trimFrom(CharSequence sequence) {
1195 int len = sequence.length();
1196 int first;
1197 int last;
1198
1199 for (first = 0; first < len; first++) {
1200 if (!matches(sequence.charAt(first))) {
1201 break;
1202 }
1203 }
1204 for (last = len - 1; last > first; last--) {
1205 if (!matches(sequence.charAt(last))) {
1206 break;
1207 }
1208 }
1209
1210 return sequence.subSequence(first, last + 1).toString();
1211 }
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221 @CheckReturnValue
1222 public String trimLeadingFrom(CharSequence sequence) {
1223 int len = sequence.length();
1224 for (int first = 0; first < len; first++) {
1225 if (!matches(sequence.charAt(first))) {
1226 return sequence.subSequence(first, len).toString();
1227 }
1228 }
1229 return "";
1230 }
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240 @CheckReturnValue
1241 public String trimTrailingFrom(CharSequence sequence) {
1242 int len = sequence.length();
1243 for (int last = len - 1; last >= 0; last--) {
1244 if (!matches(sequence.charAt(last))) {
1245 return sequence.subSequence(0, last + 1).toString();
1246 }
1247 }
1248 return "";
1249 }
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269 @CheckReturnValue
1270 public String collapseFrom(CharSequence sequence, char replacement) {
1271
1272 int len = sequence.length();
1273 for (int i = 0; i < len; i++) {
1274 char c = sequence.charAt(i);
1275 if (matches(c)) {
1276 if (c == replacement
1277 && (i == len - 1 || !matches(sequence.charAt(i + 1)))) {
1278
1279 i++;
1280 } else {
1281 StringBuilder builder = new StringBuilder(len)
1282 .append(sequence.subSequence(0, i))
1283 .append(replacement);
1284 return finishCollapseFrom(sequence, i + 1, len, replacement, builder, true);
1285 }
1286 }
1287 }
1288
1289 return sequence.toString();
1290 }
1291
1292
1293
1294
1295
1296
1297 @CheckReturnValue
1298 public String trimAndCollapseFrom(CharSequence sequence, char replacement) {
1299
1300 int len = sequence.length();
1301 int first;
1302 int last;
1303
1304 for (first = 0; first < len && matches(sequence.charAt(first)); first++) {}
1305 for (last = len - 1; last > first && matches(sequence.charAt(last)); last--) {}
1306
1307 return (first == 0 && last == len - 1)
1308 ? collapseFrom(sequence, replacement)
1309 : finishCollapseFrom(
1310 sequence, first, last + 1, replacement,
1311 new StringBuilder(last + 1 - first),
1312 false);
1313 }
1314
1315 private String finishCollapseFrom(
1316 CharSequence sequence, int start, int end, char replacement,
1317 StringBuilder builder, boolean inMatchingGroup) {
1318 for (int i = start; i < end; i++) {
1319 char c = sequence.charAt(i);
1320 if (matches(c)) {
1321 if (!inMatchingGroup) {
1322 builder.append(replacement);
1323 inMatchingGroup = true;
1324 }
1325 } else {
1326 builder.append(c);
1327 inMatchingGroup = false;
1328 }
1329 }
1330 return builder.toString();
1331 }
1332
1333
1334
1335
1336
1337 @Deprecated
1338 @Override
1339 public boolean apply(Character character) {
1340 return matches(character);
1341 }
1342
1343
1344
1345
1346
1347 @Override
1348 public String toString() {
1349 return description;
1350 }
1351
1352 static final String WHITESPACE_TABLE = ""
1353 + "\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000"
1354 + "\u2029\u000B\u3000\u2008\u2003\u205F\u3000\u1680"
1355 + "\u0009\u0020\u2006\u2001\u202F\u00A0\u000C\u2009"
1356 + "\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000";
1357 static final int WHITESPACE_MULTIPLIER = 1682554634;
1358 static final int WHITESPACE_SHIFT = Integer.numberOfLeadingZeros(WHITESPACE_TABLE.length() - 1);
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371 public static final CharMatcher WHITESPACE = new FastMatcher("WHITESPACE") {
1372 @Override
1373 public boolean matches(char c) {
1374 return WHITESPACE_TABLE.charAt((WHITESPACE_MULTIPLIER * c) >>> WHITESPACE_SHIFT) == c;
1375 }
1376
1377 @GwtIncompatible("java.util.BitSet")
1378 @Override
1379 void setBits(BitSet table) {
1380 for (int i = 0; i < WHITESPACE_TABLE.length(); i++) {
1381 table.set(WHITESPACE_TABLE.charAt(i));
1382 }
1383 }
1384 };
1385 }