1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.util.internal;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Iterator;
22 import java.util.List;
23
24 import static io.netty.util.internal.ObjectUtil.*;
25
26
27
28
29 public final class StringUtil {
30
31 public static final String EMPTY_STRING = "";
32 public static final String NEWLINE = SystemPropertyUtil.get("line.separator", "\n");
33
34 public static final char DOUBLE_QUOTE = '\"';
35 public static final char COMMA = ',';
36 public static final char LINE_FEED = '\n';
37 public static final char CARRIAGE_RETURN = '\r';
38 public static final char TAB = '\t';
39 public static final char SPACE = 0x20;
40
41 private static final String[] BYTE2HEX_PAD = new String[256];
42 private static final String[] BYTE2HEX_NOPAD = new String[256];
43 private static final byte[] HEX2B;
44
45
46
47
48
49 private static final int CSV_NUMBER_ESCAPE_CHARACTERS = 2 + 5;
50 private static final char PACKAGE_SEPARATOR_CHAR = '.';
51
52 static {
53
54 for (int i = 0; i < BYTE2HEX_PAD.length; i++) {
55 String str = Integer.toHexString(i);
56 BYTE2HEX_PAD[i] = i > 0xf ? str : ('0' + str);
57 BYTE2HEX_NOPAD[i] = str;
58 }
59
60
61
62 HEX2B = new byte[Character.MAX_VALUE + 1];
63 Arrays.fill(HEX2B, (byte) -1);
64 HEX2B['0'] = 0;
65 HEX2B['1'] = 1;
66 HEX2B['2'] = 2;
67 HEX2B['3'] = 3;
68 HEX2B['4'] = 4;
69 HEX2B['5'] = 5;
70 HEX2B['6'] = 6;
71 HEX2B['7'] = 7;
72 HEX2B['8'] = 8;
73 HEX2B['9'] = 9;
74 HEX2B['A'] = 10;
75 HEX2B['B'] = 11;
76 HEX2B['C'] = 12;
77 HEX2B['D'] = 13;
78 HEX2B['E'] = 14;
79 HEX2B['F'] = 15;
80 HEX2B['a'] = 10;
81 HEX2B['b'] = 11;
82 HEX2B['c'] = 12;
83 HEX2B['d'] = 13;
84 HEX2B['e'] = 14;
85 HEX2B['f'] = 15;
86 }
87
88 private StringUtil() {
89
90 }
91
92
93
94
95
96
97 public static String substringAfter(String value, char delim) {
98 int pos = value.indexOf(delim);
99 if (pos >= 0) {
100 return value.substring(pos + 1);
101 }
102 return null;
103 }
104
105
106
107
108
109
110 public static String substringBefore(String value, char delim) {
111 int pos = value.indexOf(delim);
112 if (pos >= 0) {
113 return value.substring(0, pos);
114 }
115 return null;
116 }
117
118
119
120
121
122
123
124
125
126 public static boolean commonSuffixOfLength(String s, String p, int len) {
127 return s != null && p != null && len >= 0 && s.regionMatches(s.length() - len, p, p.length() - len, len);
128 }
129
130
131
132
133 public static String byteToHexStringPadded(int value) {
134 return BYTE2HEX_PAD[value & 0xff];
135 }
136
137
138
139
140 public static <T extends Appendable> T byteToHexStringPadded(T buf, int value) {
141 try {
142 buf.append(byteToHexStringPadded(value));
143 } catch (IOException e) {
144 PlatformDependent.throwException(e);
145 }
146 return buf;
147 }
148
149
150
151
152 public static String toHexStringPadded(byte[] src) {
153 return toHexStringPadded(src, 0, src.length);
154 }
155
156
157
158
159 public static String toHexStringPadded(byte[] src, int offset, int length) {
160 return toHexStringPadded(new StringBuilder(length << 1), src, offset, length).toString();
161 }
162
163
164
165
166 public static <T extends Appendable> T toHexStringPadded(T dst, byte[] src) {
167 return toHexStringPadded(dst, src, 0, src.length);
168 }
169
170
171
172
173 public static <T extends Appendable> T toHexStringPadded(T dst, byte[] src, int offset, int length) {
174 final int end = offset + length;
175 for (int i = offset; i < end; i++) {
176 byteToHexStringPadded(dst, src[i]);
177 }
178 return dst;
179 }
180
181
182
183
184 public static String byteToHexString(int value) {
185 return BYTE2HEX_NOPAD[value & 0xff];
186 }
187
188
189
190
191 public static <T extends Appendable> T byteToHexString(T buf, int value) {
192 try {
193 buf.append(byteToHexString(value));
194 } catch (IOException e) {
195 PlatformDependent.throwException(e);
196 }
197 return buf;
198 }
199
200
201
202
203 public static String toHexString(byte[] src) {
204 return toHexString(src, 0, src.length);
205 }
206
207
208
209
210 public static String toHexString(byte[] src, int offset, int length) {
211 return toHexString(new StringBuilder(length << 1), src, offset, length).toString();
212 }
213
214
215
216
217 public static <T extends Appendable> T toHexString(T dst, byte[] src) {
218 return toHexString(dst, src, 0, src.length);
219 }
220
221
222
223
224 public static <T extends Appendable> T toHexString(T dst, byte[] src, int offset, int length) {
225 assert length >= 0;
226 if (length == 0) {
227 return dst;
228 }
229
230 final int end = offset + length;
231 final int endMinusOne = end - 1;
232 int i;
233
234
235 for (i = offset; i < endMinusOne; i++) {
236 if (src[i] != 0) {
237 break;
238 }
239 }
240
241 byteToHexString(dst, src[i++]);
242 int remaining = end - i;
243 toHexStringPadded(dst, src, i, remaining);
244
245 return dst;
246 }
247
248
249
250
251
252
253
254
255 public static int decodeHexNibble(final char c) {
256
257
258 return HEX2B[c];
259 }
260
261
262
263
264
265
266
267
268 public static int decodeHexNibble(final byte b) {
269
270
271 return HEX2B[b];
272 }
273
274
275
276
277 public static byte decodeHexByte(CharSequence s, int pos) {
278 int hi = decodeHexNibble(s.charAt(pos));
279 int lo = decodeHexNibble(s.charAt(pos + 1));
280 if (hi == -1 || lo == -1) {
281 throw new IllegalArgumentException(String.format(
282 "invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s));
283 }
284 return (byte) ((hi << 4) + lo);
285 }
286
287
288
289
290
291
292
293
294 public static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) {
295 if (length < 0 || (length & 1) != 0) {
296 throw new IllegalArgumentException("length: " + length);
297 }
298 if (length == 0) {
299 return EmptyArrays.EMPTY_BYTES;
300 }
301 byte[] bytes = new byte[length >>> 1];
302 for (int i = 0; i < length; i += 2) {
303 bytes[i >>> 1] = decodeHexByte(hexDump, fromIndex + i);
304 }
305 return bytes;
306 }
307
308
309
310
311 public static byte[] decodeHexDump(CharSequence hexDump) {
312 return decodeHexDump(hexDump, 0, hexDump.length());
313 }
314
315
316
317
318 public static String className(Object o) {
319 if (o == null) {
320 return "null_object";
321 } else {
322 return o.getClass().getName();
323 }
324 }
325
326
327
328
329 public static String simpleClassName(Object o) {
330 if (o == null) {
331 return "null_object";
332 } else {
333 return simpleClassName(o.getClass());
334 }
335 }
336
337
338
339
340
341 public static String simpleClassName(Class<?> clazz) {
342 String className = checkNotNull(clazz, "clazz").getName();
343 final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
344 if (lastDotIdx > -1) {
345 return className.substring(lastDotIdx + 1);
346 }
347 return className;
348 }
349
350
351
352
353
354
355
356
357
358 public static CharSequence escapeCsv(CharSequence value) {
359 return escapeCsv(value, false);
360 }
361
362
363
364
365
366
367
368
369
370
371
372 public static CharSequence escapeCsv(CharSequence value, boolean trimWhiteSpace) {
373 int length = checkNotNull(value, "value").length();
374 int start;
375 int last;
376 if (trimWhiteSpace) {
377 start = indexOfFirstNonOwsChar(value, length);
378 last = indexOfLastNonOwsChar(value, start, length);
379 } else {
380 start = 0;
381 last = length - 1;
382 }
383 if (start > last) {
384 return EMPTY_STRING;
385 }
386
387 int firstUnescapedSpecial = -1;
388 boolean quoted = false;
389 if (isDoubleQuote(value.charAt(start))) {
390 quoted = isDoubleQuote(value.charAt(last)) && last > start;
391 if (quoted) {
392 start++;
393 last--;
394 } else {
395 firstUnescapedSpecial = start;
396 }
397 }
398
399 if (firstUnescapedSpecial < 0) {
400 if (quoted) {
401 for (int i = start; i <= last; i++) {
402 if (isDoubleQuote(value.charAt(i))) {
403 if (i == last || !isDoubleQuote(value.charAt(i + 1))) {
404 firstUnescapedSpecial = i;
405 break;
406 }
407 i++;
408 }
409 }
410 } else {
411 for (int i = start; i <= last; i++) {
412 char c = value.charAt(i);
413 if (c == LINE_FEED || c == CARRIAGE_RETURN || c == COMMA) {
414 firstUnescapedSpecial = i;
415 break;
416 }
417 if (isDoubleQuote(c)) {
418 if (i == last || !isDoubleQuote(value.charAt(i + 1))) {
419 firstUnescapedSpecial = i;
420 break;
421 }
422 i++;
423 }
424 }
425 }
426
427 if (firstUnescapedSpecial < 0) {
428
429
430
431 return quoted? value.subSequence(start - 1, last + 2) : value.subSequence(start, last + 1);
432 }
433 }
434
435 StringBuilder result = new StringBuilder(last - start + 1 + CSV_NUMBER_ESCAPE_CHARACTERS);
436 result.append(DOUBLE_QUOTE).append(value, start, firstUnescapedSpecial);
437 for (int i = firstUnescapedSpecial; i <= last; i++) {
438 char c = value.charAt(i);
439 if (isDoubleQuote(c)) {
440 result.append(DOUBLE_QUOTE);
441 if (i < last && isDoubleQuote(value.charAt(i + 1))) {
442 i++;
443 }
444 }
445 result.append(c);
446 }
447 return result.append(DOUBLE_QUOTE);
448 }
449
450
451
452
453
454
455
456
457
458 public static CharSequence unescapeCsv(CharSequence value) {
459 int length = checkNotNull(value, "value").length();
460 if (length == 0) {
461 return value;
462 }
463 int last = length - 1;
464 boolean quoted = isDoubleQuote(value.charAt(0)) && isDoubleQuote(value.charAt(last)) && length != 1;
465 if (!quoted) {
466 validateCsvFormat(value);
467 return value;
468 }
469 StringBuilder unescaped = InternalThreadLocalMap.get().stringBuilder();
470 for (int i = 1; i < last; i++) {
471 char current = value.charAt(i);
472 if (current == DOUBLE_QUOTE) {
473 if (isDoubleQuote(value.charAt(i + 1)) && (i + 1) != last) {
474
475
476 i++;
477 } else {
478
479 throw newInvalidEscapedCsvFieldException(value, i);
480 }
481 }
482 unescaped.append(current);
483 }
484 return unescaped.toString();
485 }
486
487
488
489
490
491
492
493
494
495 public static List<CharSequence> unescapeCsvFields(CharSequence value) {
496 List<CharSequence> unescaped = new ArrayList<CharSequence>(2);
497 StringBuilder current = InternalThreadLocalMap.get().stringBuilder();
498 boolean quoted = false;
499 int last = value.length() - 1;
500 for (int i = 0; i <= last; i++) {
501 char c = value.charAt(i);
502 if (quoted) {
503 switch (c) {
504 case DOUBLE_QUOTE:
505 if (i == last) {
506
507 unescaped.add(current.toString());
508 return unescaped;
509 }
510 char next = value.charAt(++i);
511 if (next == DOUBLE_QUOTE) {
512
513 current.append(DOUBLE_QUOTE);
514 break;
515 }
516 if (next == COMMA) {
517
518 quoted = false;
519 unescaped.add(current.toString());
520 current.setLength(0);
521 break;
522 }
523
524 throw newInvalidEscapedCsvFieldException(value, i - 1);
525 default:
526 current.append(c);
527 }
528 } else {
529 switch (c) {
530 case COMMA:
531
532 unescaped.add(current.toString());
533 current.setLength(0);
534 break;
535 case DOUBLE_QUOTE:
536 if (current.length() == 0) {
537 quoted = true;
538 break;
539 }
540
541
542 case LINE_FEED:
543
544 case CARRIAGE_RETURN:
545
546 throw newInvalidEscapedCsvFieldException(value, i);
547 default:
548 current.append(c);
549 }
550 }
551 }
552 if (quoted) {
553 throw newInvalidEscapedCsvFieldException(value, last);
554 }
555 unescaped.add(current.toString());
556 return unescaped;
557 }
558
559
560
561
562
563
564 private static void validateCsvFormat(CharSequence value) {
565 int length = value.length();
566 for (int i = 0; i < length; i++) {
567 switch (value.charAt(i)) {
568 case DOUBLE_QUOTE:
569 case LINE_FEED:
570 case CARRIAGE_RETURN:
571 case COMMA:
572
573 throw newInvalidEscapedCsvFieldException(value, i);
574 default:
575 }
576 }
577 }
578
579 private static IllegalArgumentException newInvalidEscapedCsvFieldException(CharSequence value, int index) {
580 return new IllegalArgumentException("invalid escaped CSV field: " + value + " index: " + index);
581 }
582
583
584
585
586 public static int length(String s) {
587 return s == null ? 0 : s.length();
588 }
589
590
591
592
593 public static boolean isNullOrEmpty(String s) {
594 return s == null || s.isEmpty();
595 }
596
597
598
599
600
601
602
603
604 public static int indexOfNonWhiteSpace(CharSequence seq, int offset) {
605 for (; offset < seq.length(); ++offset) {
606 if (!Character.isWhitespace(seq.charAt(offset))) {
607 return offset;
608 }
609 }
610 return -1;
611 }
612
613
614
615
616
617
618
619
620 public static int indexOfWhiteSpace(CharSequence seq, int offset) {
621 for (; offset < seq.length(); ++offset) {
622 if (Character.isWhitespace(seq.charAt(offset))) {
623 return offset;
624 }
625 }
626 return -1;
627 }
628
629
630
631
632
633
634
635
636
637 public static boolean isSurrogate(char c) {
638 return c >= '\uD800' && c <= '\uDFFF';
639 }
640
641 private static boolean isDoubleQuote(char c) {
642 return c == DOUBLE_QUOTE;
643 }
644
645
646
647
648
649
650
651
652 public static boolean endsWith(CharSequence s, char c) {
653 int len = s.length();
654 return len > 0 && s.charAt(len - 1) == c;
655 }
656
657
658
659
660
661
662
663
664 public static CharSequence trimOws(CharSequence value) {
665 final int length = value.length();
666 if (length == 0) {
667 return value;
668 }
669 int start = indexOfFirstNonOwsChar(value, length);
670 int end = indexOfLastNonOwsChar(value, start, length);
671 return start == 0 && end == length - 1 ? value : value.subSequence(start, end + 1);
672 }
673
674
675
676
677
678
679
680
681
682 public static CharSequence join(CharSequence separator, Iterable<? extends CharSequence> elements) {
683 ObjectUtil.checkNotNull(separator, "separator");
684 ObjectUtil.checkNotNull(elements, "elements");
685
686 Iterator<? extends CharSequence> iterator = elements.iterator();
687 if (!iterator.hasNext()) {
688 return EMPTY_STRING;
689 }
690
691 CharSequence firstElement = iterator.next();
692 if (!iterator.hasNext()) {
693 return firstElement;
694 }
695
696 StringBuilder builder = new StringBuilder(firstElement);
697 do {
698 builder.append(separator).append(iterator.next());
699 } while (iterator.hasNext());
700
701 return builder;
702 }
703
704
705
706
707 private static int indexOfFirstNonOwsChar(CharSequence value, int length) {
708 int i = 0;
709 while (i < length && isOws(value.charAt(i))) {
710 i++;
711 }
712 return i;
713 }
714
715
716
717
718 private static int indexOfLastNonOwsChar(CharSequence value, int start, int length) {
719 int i = length - 1;
720 while (i > start && isOws(value.charAt(i))) {
721 i--;
722 }
723 return i;
724 }
725
726 private static boolean isOws(char c) {
727 return c == SPACE || c == TAB;
728 }
729
730 }