1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.base;
33
34 import com.google.common.annotations.GwtCompatible;
35 import com.google.common.annotations.GwtIncompatible;
36 import com.google.common.io.CharSource;
37 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
38 import java.io.IOException;
39 import java.io.Reader;
40 import java.io.UncheckedIOException;
41 import static java.lang.Math.min;
42 import java.util.function.IntPredicate;
43 import javax.annotation.Nonnull;
44 import javax.annotation.ParametersAreNonnullByDefault;
45
46
47
48
49
50
51 @GwtCompatible
52 @ParametersAreNonnullByDefault
53 public final class CharSequences {
54
55 private CharSequences() {
56 }
57
58
59
60
61
62
63
64
65
66 @SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
67 public static int distance(final CharSequence s1, final CharSequence s2) {
68 int l1 = s1.length();
69 int l2 = s2.length();
70 if (l1 == 0) {
71 return l2;
72 }
73 if (l2 == 0) {
74 return l1;
75 }
76 int[] prev = new int[l2];
77 char c1 = s1.charAt(0);
78 char cs20 = s2.charAt(0);
79 prev[0] = distance(c1, cs20);
80 for (int j = 1; j < l2; j++) {
81 int pd = prev[j - 1];
82 prev[j] = pd == j ? pd + distance(c1, s2.charAt(j)) : pd + 1;
83 }
84 int[] dist = new int[l2];
85 for (int i = 1; i < l1; i++) {
86 c1 = s1.charAt(i);
87 int pd = prev[0];
88 dist[0] = pd == i ? pd + distance(c1, cs20) : pd + 1;
89 for (int j = 1; j < l2; j++) {
90 dist[j] = min(prev[j - 1] + distance(c1, s2.charAt(j)),
91 min(prev[j] + 1, dist[j - 1] + 1));
92 }
93 int[] tmp = prev;
94 prev = dist;
95 dist = tmp;
96 }
97 return prev[l2 - 1];
98 }
99
100
101 public static int distance(final char c1, final char c2) {
102 return (c1 == c2) ? 0 : 1;
103 }
104
105
106
107
108
109
110
111
112
113 @Deprecated
114 public static int compareTo(final CharSequence s, final CharSequence t) {
115 return compare(s, t);
116 }
117
118 public static int compare(final CharSequence s, final CharSequence t) {
119 return compare(s, 0, s.length(), t, 0, t.length());
120 }
121
122 public static int compare(final CharSequence s, final int sLength,
123 final CharSequence t, final int tLength) {
124 return compare(s, 0, sLength, t, 0, tLength);
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138 public static int compare(final CharSequence s, final int sFrom, final int sLength,
139 final CharSequence t, final int tFrom, final int tLength) {
140
141 int lim = min(sLength, tLength);
142 int i = sFrom;
143 int j = tFrom;
144 int sTo = sFrom + lim;
145 while (i < sTo) {
146 char c1 = s.charAt(i);
147 char c2 = t.charAt(j);
148 if (c1 != c2) {
149 return c1 - c2;
150 }
151 i++;
152 j++;
153 }
154 return sLength - tLength;
155 }
156
157 public static boolean equalsNullables(final CharSequence s, final CharSequence t) {
158 if (s == null) {
159 return null == t;
160 } else if (t == null) {
161 return true;
162 } else {
163 return equals(s, t);
164 }
165 }
166
167 public static boolean equals(final CharSequence s, final CharSequence t) {
168 final int sl = s.length();
169 final int tl = t.length();
170 if (sl != tl) {
171 return false;
172 } else {
173 for (int i = 0; i < sl; i++) {
174 if (s.charAt(i) != t.charAt(i)) {
175 return false;
176 }
177 }
178 return true;
179 }
180 }
181
182 public static int hashcode(final CharSequence cs) {
183 if (cs instanceof String) {
184 return ((String) cs).hashCode();
185 }
186 int h = 0;
187 int len = cs.length();
188 if (len > 0) {
189 int off = 0;
190 for (int i = 0; i < len; i++) {
191 h = 31 * h + cs.charAt(off++);
192 }
193 }
194 return h;
195 }
196
197 public static CharSequence subSequence(final CharSequence seq, final int startIdx, final int endIdx) {
198 if (startIdx == 0 && endIdx == seq.length()) {
199 return seq;
200 } else if (startIdx >= endIdx) {
201 return "";
202 } else {
203 return new SubSequence(seq, endIdx - startIdx, startIdx);
204 }
205 }
206
207 private static final class SubSequence implements CharSequence {
208
209 private final CharSequence underlyingSequence;
210 private final int length;
211 private final int startIdx;
212
213 SubSequence(final CharSequence underlyingSequence, final int length, final int startIdx) {
214 this.underlyingSequence = underlyingSequence;
215 this.length = length;
216 this.startIdx = startIdx;
217 }
218
219 @Override
220 public int length() {
221 return length;
222 }
223
224 @Override
225 public char charAt(final int index) {
226 return underlyingSequence.charAt(startIdx + index);
227 }
228
229 @Override
230 public CharSequence subSequence(final int start, final int end) {
231 return CharSequences.subSequence(underlyingSequence, startIdx + start, startIdx + end);
232 }
233
234 @Override
235 @SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
236 public String toString() {
237 if (underlyingSequence instanceof String) {
238 return ((String) underlyingSequence).substring(startIdx, startIdx + length);
239 } else if (underlyingSequence instanceof StringBuilder) {
240 return ((StringBuilder) underlyingSequence).substring(startIdx, startIdx + length);
241 } else {
242 char[] chars = new char[length];
243 int idx = startIdx;
244 for (int i = 0; i < length; i++, idx++) {
245 chars[i] = underlyingSequence.charAt(idx);
246 }
247 return new String(chars);
248 }
249 }
250
251 }
252
253 public static boolean startsWith(final CharSequence sequence, final CharSequence prefix, final int toffset) {
254 int to = toffset;
255 int po = 0;
256 int pc = prefix.length();
257 int sl = sequence.length();
258 if ((toffset < 0) || (toffset > sl - pc)) {
259 return false;
260 }
261 while (--pc >= 0) {
262 if (sequence.charAt(to++) != prefix.charAt(po++)) {
263 return false;
264 }
265 }
266 return true;
267 }
268
269 public static boolean endsWith(final CharSequence qc, final CharSequence with) {
270 int l = qc.length();
271 int start = l - with.length();
272 if (start >= 0) {
273 for (int i = start, j = 0; i < l; i++, j++) {
274 if (qc.charAt(i) != with.charAt(j)) {
275 return false;
276 }
277 }
278 return true;
279 } else {
280 return false;
281 }
282 }
283
284 public static Appendable lineNumbered(final int startLineNr, final Appendable appendable)
285 throws IOException {
286 return lineNumbered(startLineNr, appendable, IntAppender.CommentNumberAppender.INSTANCE);
287 }
288
289 public static Appendable lineNumbered(final int startLineNr, final Appendable appendable, final IntAppender ia)
290 throws IOException {
291 ia.append(startLineNr, appendable);
292 return new Appendable() {
293 private int lineNr = startLineNr + 1;
294
295 @Override
296 public Appendable append(final CharSequence csq) throws IOException {
297 return append(csq, 0, csq.length());
298 }
299
300 @Override
301 public Appendable append(final CharSequence csq, final int start, final int end) throws IOException {
302 int lastIdx = start;
303 for (int i = start; i < end; i++) {
304 if (csq.charAt(i) == '\n') {
305 int next = i + 1;
306 appendable.append(csq, lastIdx, next);
307 ia.append(lineNr++, appendable);
308 lastIdx = next;
309 }
310 }
311 if (lastIdx < end) {
312 appendable.append(csq, lastIdx, end);
313 }
314 return this;
315 }
316
317 @Override
318 public Appendable append(final char c) throws IOException {
319 appendable.append(c);
320 if (c == '\n') {
321 ia.append(lineNr++, appendable);
322 }
323 return this;
324 }
325 };
326 }
327
328 public static CharSequence toLineNumbered(final int startLineNr, final CharSequence source) {
329 return toLineNumbered(startLineNr, source, IntAppender.CommentNumberAppender.INSTANCE);
330 }
331
332 public static CharSequence toLineNumbered(final int startLineNr, final CharSequence source, final IntAppender ia) {
333 int length = source.length();
334 StringBuilder destination = new StringBuilder(length + 6 * length / 80);
335 try {
336 lineNumbered(startLineNr, destination, ia).append(source);
337 } catch (IOException ex) {
338 throw new UncheckedIOException(ex);
339 }
340 return destination;
341 }
342
343
344
345
346
347
348 public static int parseInt(@Nonnull final CharSequence s) {
349 return parseInt(s, 10);
350 }
351
352
353
354
355
356
357 public static int parseInt(@Nonnull final CharSequence cs, final int radix) {
358
359 if (radix < Character.MIN_RADIX) {
360 throw new NumberFormatException("radix " + radix
361 + " less than Character.MIN_RADIX");
362 }
363
364 if (radix > Character.MAX_RADIX) {
365 throw new NumberFormatException("radix " + radix
366 + " greater than Character.MAX_RADIX");
367 }
368
369 int result = 0;
370 boolean negative = false;
371 int len = cs.length();
372
373 if (len > 0) {
374 int i = 0;
375 int limit = -Integer.MAX_VALUE;
376 int multmin;
377 int digit;
378 char firstChar = cs.charAt(0);
379 if (firstChar < '0') {
380 if (firstChar == '-') {
381 negative = true;
382 limit = Integer.MIN_VALUE;
383 } else if (firstChar != '+') {
384 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
385 }
386
387 if (len == 1) {
388 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
389 }
390 i++;
391 }
392 multmin = limit / radix;
393 while (i < len) {
394
395 digit = Character.digit(cs.charAt(i++), radix);
396 if (digit < 0) {
397 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
398 }
399 if (result < multmin) {
400 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
401 }
402 result *= radix;
403 if (result < limit + digit) {
404 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
405 }
406 result -= digit;
407 }
408 } else {
409 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
410 }
411 return negative ? result : -result;
412 }
413
414
415
416
417
418
419
420
421
422 @SuppressWarnings("checkstyle:InnerAssignment")
423 public static int parseUnsignedInt(@Nonnull final CharSequence cs, final int radix, final int idxFrom) {
424 return parseUnsignedInt(cs, radix, idxFrom, cs.length());
425 }
426
427
428
429
430
431
432
433
434
435
436 @SuppressWarnings("checkstyle:InnerAssignment")
437 public static int parseUnsignedInt(@Nonnull final CharSequence cs, final int radix,
438 final int idxFrom, final int idxTo) {
439 if (radix < Character.MIN_RADIX) {
440 throw new NumberFormatException("radix " + radix
441 + " less than Character.MIN_RADIX");
442 }
443 if (radix > Character.MAX_RADIX) {
444 throw new NumberFormatException("radix " + radix
445 + " greater than Character.MAX_RADIX");
446 }
447
448 int result = 0;
449 int i = idxFrom;
450 int limit = -Integer.MAX_VALUE;
451 int multmin = limit / radix;
452 int digit;
453 while (i < idxTo && (digit = Character.digit(cs.charAt(i), radix)) >= 0) {
454 if (result < multmin) {
455 throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
456 }
457 result *= radix;
458 if (result < limit + digit) {
459 throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
460 }
461 result -= digit;
462 i++;
463 }
464 if (i == idxFrom) {
465 throw new NumberFormatException("No number in \"" + cs + "\" at " + idxFrom);
466 }
467 return -result;
468 }
469
470
471 @SuppressWarnings("checkstyle:InnerAssignment")
472 public static long parseUnsignedLong(@Nonnull final CharSequence cs, final int radix, final int idxFrom) {
473 return parseUnsignedLong(cs, radix, idxFrom, cs.length(), false);
474 }
475
476 @SuppressWarnings("checkstyle:InnerAssignment")
477 public static long parseUnsignedLong(@Nonnull final CharSequence cs, final int radix,
478 final int idxFrom, final int idxTo, final boolean strict) {
479 if (radix < Character.MIN_RADIX) {
480 throw new NumberFormatException("radix " + radix
481 + " less than Character.MIN_RADIX");
482 }
483 if (radix > Character.MAX_RADIX) {
484 throw new NumberFormatException("radix " + radix
485 + " greater than Character.MAX_RADIX");
486 }
487
488 long result = 0;
489 int i = idxFrom;
490 long limit = -Long.MAX_VALUE;
491 long multmin = limit / radix;
492 int digit;
493 while (i < idxTo && (digit = Character.digit(cs.charAt(i), radix)) >= 0) {
494 if (result < multmin) {
495 throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
496 }
497 result *= radix;
498 if (result < limit + digit) {
499 throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
500 }
501 result -= digit;
502 i++;
503 }
504 if (i == idxFrom) {
505 throw new NumberFormatException("No number in " + cs + " at " + idxFrom);
506 }
507 if (strict && i < idxTo) {
508 throw new NumberFormatException("No valid number in " + cs + " at " + idxFrom + " to " + idxTo);
509 }
510 return -result;
511 }
512
513
514
515
516
517
518 public static long parseLong(@Nonnull final CharSequence cs) {
519 return parseLong(cs, 10);
520 }
521
522
523
524
525
526
527 public static long parseLong(@Nonnull final CharSequence cs, final int radix) {
528
529 if (radix < Character.MIN_RADIX) {
530 throw new NumberFormatException("radix " + radix
531 + " less than Character.MIN_RADIX");
532 }
533 if (radix > Character.MAX_RADIX) {
534 throw new NumberFormatException("radix " + radix
535 + " greater than Character.MAX_RADIX");
536 }
537
538 long result = 0;
539 boolean negative = false;
540 int len = cs.length();
541
542 if (len > 0) {
543 int i = 0;
544 long limit = -Long.MAX_VALUE;
545 long multmin;
546 int digit;
547 char firstChar = cs.charAt(0);
548 if (firstChar < '0') {
549 if (firstChar == '-') {
550 negative = true;
551 limit = Long.MIN_VALUE;
552 } else if (firstChar != '+') {
553 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
554 }
555
556 if (len == 1) {
557 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
558 }
559 i++;
560 }
561 multmin = limit / radix;
562 while (i < len) {
563
564 digit = Character.digit(cs.charAt(i++), radix);
565 if (digit < 0) {
566 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
567 }
568 if (result < multmin) {
569 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
570 }
571 result *= radix;
572 if (result < limit + digit) {
573 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
574 }
575 result -= digit;
576 }
577 } else {
578 throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
579 }
580 return negative ? result : -result;
581 }
582
583 public static boolean containsAnyChar(final CharSequence string, final char... chars) {
584 return containsAnyChar(string, 0, string.length(), chars);
585 }
586
587 public static boolean containsAnyChar(final CharSequence string,
588 final int start, final int end,
589 final char... chars) {
590 for (int i = start; i < end; i++) {
591 char c = string.charAt(i);
592 if (Arrays.search(chars, c) >= 0) {
593 return true;
594 }
595 }
596 return false;
597 }
598
599 public static boolean isValidJavaId(final CharSequence name) {
600 int length = name.length();
601 if (length == 0) {
602 return false;
603 }
604 char first = name.charAt(0);
605 if (Character.isLetter(first) || first == '_') {
606 for (int i = 1; i < length; i++) {
607 char c = name.charAt(i);
608 if (c != '_' && !Character.isLetterOrDigit(c)) {
609 return false;
610 }
611 }
612 return true;
613 } else {
614 return false;
615 }
616 }
617
618
619 public static boolean isValidFileName(@Nonnull final CharSequence fileName) {
620 return !containsAnyChar(fileName, '/', '\\');
621 }
622
623 public static <T extends CharSequence> T validatedFileName(@Nonnull final T fileName) {
624 if (!isValidFileName(fileName)) {
625 throw new IllegalArgumentException("Invalid file name: " + fileName);
626 }
627 return fileName;
628 }
629
630
631
632
633 public static boolean regionMatches(final CharSequence t, final int toffset,
634 final CharSequence other, final int ooffset, final int plen) {
635 int to = toffset;
636 int po = ooffset;
637
638 if ((ooffset < 0) || (toffset < 0) || (toffset > (long) t.length() - plen)
639 || (ooffset > (long) other.length() - plen)) {
640 return false;
641 }
642 int len = plen;
643 while (len-- > 0) {
644 if (t.charAt(to++) != other.charAt(po++)) {
645 return false;
646 }
647 }
648 return true;
649 }
650
651
652
653
654 public static boolean regionMatchesIgnoreCase(final CharSequence ta, final int toffset,
655 final CharSequence pa, final int ooffset, final int plen) {
656 int to = toffset;
657 int po = ooffset;
658
659 if ((ooffset < 0) || (toffset < 0)
660 || (toffset > (long) ta.length() - plen)
661 || (ooffset > (long) pa.length() - plen)) {
662 return false;
663 }
664 int len = plen;
665 while (len-- > 0) {
666 char c1 = ta.charAt(to++);
667 char c2 = pa.charAt(po++);
668 if (c1 == c2) {
669 continue;
670 }
671
672
673
674
675 char u1 = Character.toUpperCase(c1);
676 char u2 = Character.toUpperCase(c2);
677 if (u1 == u2) {
678 continue;
679 }
680
681
682
683
684 if (Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
685 return false;
686 }
687 }
688 return true;
689 }
690
691
692
693
694
695
696
697
698 public static boolean match(final CharSequence wildcard, final CharSequence cs2Match) {
699 int i = 0;
700 int j = 0;
701 final int length = wildcard.length();
702 for (; i < length; i++, j++) {
703 final char some2 = wildcard.charAt(i);
704 if (some2 != cs2Match.charAt(j)) {
705 if (some2 == '*') {
706 i++;
707 if (i == length) {
708 return true;
709 }
710 final char some = wildcard.charAt(i);
711 while (some != cs2Match.charAt(j)) {
712 ++j;
713 }
714 j--;
715 } else if (some2 != '?') {
716 return false;
717 }
718 }
719 }
720 return j == cs2Match.length();
721 }
722
723
724
725
726
727
728
729
730 public CharSequence getJavaRegexpStr(final CharSequence wildcard) {
731 final int length = wildcard.length();
732 final StringBuilder buff = new StringBuilder(length + 4);
733 for (int i = 0; i < length; i++) {
734 final char c = wildcard.charAt(i);
735 switch (c) {
736 case '*':
737 buff.append(".*");
738 break;
739 case '?':
740 buff.append('.');
741 break;
742 case '[':
743 case ']':
744 case '(':
745 case ')':
746 case '{':
747 case '}':
748 case '.':
749 buff.append('\\').append(c);
750 break;
751 default:
752 buff.append(c);
753 }
754 }
755 return buff;
756 }
757
758 public static int indexOf(final CharSequence cs, final int from, final int to, final char c) {
759 for (int i = from; i < to; i++) {
760 if (c == cs.charAt(i)) {
761 return i;
762 }
763 }
764 return -1;
765 }
766
767 public static int indexOf(final CharSequence cs, final int from, final int to, final IntPredicate cp) {
768 for (int i = from; i < to; i++) {
769 if (cp.test(cs.charAt(i))) {
770 return i;
771 }
772 }
773 return -1;
774 }
775
776 public static int indexOf(final CharSequence cs, final int from, final int to, final char... chars) {
777 for (int i = from; i < to; i++) {
778 char charAt = cs.charAt(i);
779 for (char c : chars) {
780 if (c == charAt) {
781 return i;
782 }
783 }
784 }
785 return -1;
786 }
787
788 public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
789 return lastIndexOfIgnoreCase(str, searchStr) >= 0;
790 }
791
792 public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
793 final int length = searchStr.length();
794 if (length == 0) {
795 return 0;
796 }
797 for (int i = str.length() - length; i >= 0; i--) {
798 if (regionMatchesIgnoreCase(str, i, searchStr, 0, length)) {
799 return i;
800 }
801 }
802 return -1;
803 }
804
805 public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, final int idxStart) {
806 final int sLen = searchStr.length();
807 if (sLen == 0) {
808 return 0;
809 }
810 for (int i = idxStart, l = str.length() - sLen; i <= l; i++) {
811 if (regionMatchesIgnoreCase(str, i, searchStr, 0, sLen)) {
812 return i;
813 }
814 }
815 return -1;
816 }
817
818
819 public static int countIgnoreCase(final CharSequence str, final CharSequence searchStr) {
820 int result = 0;
821 int sLen = searchStr.length();
822 if (sLen == 0) {
823 return 0;
824 }
825 int from = 0;
826 int idx;
827 while ((idx = indexOfIgnoreCase(str, searchStr, from)) >= 0) {
828 result++;
829 from = idx + sLen;
830 }
831 return result;
832 }
833
834
835 @GwtIncompatible
836 public static Reader reader(final CharSequence cs) {
837 try {
838 return CharSource.wrap(cs).openStream();
839 } catch (IOException ex) {
840 throw new UncheckedIOException(ex);
841 }
842 }
843
844
845 }