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 simpleClassName(Object o) {
319 if (o == null) {
320 return "null_object";
321 } else {
322 return simpleClassName(o.getClass());
323 }
324 }
325
326
327
328
329
330 public static String simpleClassName(Class<?> clazz) {
331 String className = checkNotNull(clazz, "clazz").getName();
332 final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
333 if (lastDotIdx > -1) {
334 return className.substring(lastDotIdx + 1);
335 }
336 return className;
337 }
338
339
340
341
342
343
344
345
346
347 public static CharSequence escapeCsv(CharSequence value) {
348 return escapeCsv(value, false);
349 }
350
351
352
353
354
355
356
357
358
359
360
361 public static CharSequence escapeCsv(CharSequence value, boolean trimWhiteSpace) {
362 int length = checkNotNull(value, "value").length();
363 int start;
364 int last;
365 if (trimWhiteSpace) {
366 start = indexOfFirstNonOwsChar(value, length);
367 last = indexOfLastNonOwsChar(value, start, length);
368 } else {
369 start = 0;
370 last = length - 1;
371 }
372 if (start > last) {
373 return EMPTY_STRING;
374 }
375
376 int firstUnescapedSpecial = -1;
377 boolean quoted = false;
378 if (isDoubleQuote(value.charAt(start))) {
379 quoted = isDoubleQuote(value.charAt(last)) && last > start;
380 if (quoted) {
381 start++;
382 last--;
383 } else {
384 firstUnescapedSpecial = start;
385 }
386 }
387
388 if (firstUnescapedSpecial < 0) {
389 if (quoted) {
390 for (int i = start; i <= last; i++) {
391 if (isDoubleQuote(value.charAt(i))) {
392 if (i == last || !isDoubleQuote(value.charAt(i + 1))) {
393 firstUnescapedSpecial = i;
394 break;
395 }
396 i++;
397 }
398 }
399 } else {
400 for (int i = start; i <= last; i++) {
401 char c = value.charAt(i);
402 if (c == LINE_FEED || c == CARRIAGE_RETURN || c == COMMA) {
403 firstUnescapedSpecial = i;
404 break;
405 }
406 if (isDoubleQuote(c)) {
407 if (i == last || !isDoubleQuote(value.charAt(i + 1))) {
408 firstUnescapedSpecial = i;
409 break;
410 }
411 i++;
412 }
413 }
414 }
415
416 if (firstUnescapedSpecial < 0) {
417
418
419
420 return quoted? value.subSequence(start - 1, last + 2) : value.subSequence(start, last + 1);
421 }
422 }
423
424 StringBuilder result = new StringBuilder(last - start + 1 + CSV_NUMBER_ESCAPE_CHARACTERS);
425 result.append(DOUBLE_QUOTE).append(value, start, firstUnescapedSpecial);
426 for (int i = firstUnescapedSpecial; i <= last; i++) {
427 char c = value.charAt(i);
428 if (isDoubleQuote(c)) {
429 result.append(DOUBLE_QUOTE);
430 if (i < last && isDoubleQuote(value.charAt(i + 1))) {
431 i++;
432 }
433 }
434 result.append(c);
435 }
436 return result.append(DOUBLE_QUOTE);
437 }
438
439
440
441
442
443
444
445
446
447 public static CharSequence unescapeCsv(CharSequence value) {
448 int length = checkNotNull(value, "value").length();
449 if (length == 0) {
450 return value;
451 }
452 int last = length - 1;
453 boolean quoted = isDoubleQuote(value.charAt(0)) && isDoubleQuote(value.charAt(last)) && length != 1;
454 if (!quoted) {
455 validateCsvFormat(value);
456 return value;
457 }
458 StringBuilder unescaped = InternalThreadLocalMap.get().stringBuilder();
459 for (int i = 1; i < last; i++) {
460 char current = value.charAt(i);
461 if (current == DOUBLE_QUOTE) {
462 if (isDoubleQuote(value.charAt(i + 1)) && (i + 1) != last) {
463
464
465 i++;
466 } else {
467
468 throw newInvalidEscapedCsvFieldException(value, i);
469 }
470 }
471 unescaped.append(current);
472 }
473 return unescaped.toString();
474 }
475
476
477
478
479
480
481
482
483
484 public static List<CharSequence> unescapeCsvFields(CharSequence value) {
485 List<CharSequence> unescaped = new ArrayList<CharSequence>(2);
486 StringBuilder current = InternalThreadLocalMap.get().stringBuilder();
487 boolean quoted = false;
488 int last = value.length() - 1;
489 for (int i = 0; i <= last; i++) {
490 char c = value.charAt(i);
491 if (quoted) {
492 switch (c) {
493 case DOUBLE_QUOTE:
494 if (i == last) {
495
496 unescaped.add(current.toString());
497 return unescaped;
498 }
499 char next = value.charAt(++i);
500 if (next == DOUBLE_QUOTE) {
501
502 current.append(DOUBLE_QUOTE);
503 break;
504 }
505 if (next == COMMA) {
506
507 quoted = false;
508 unescaped.add(current.toString());
509 current.setLength(0);
510 break;
511 }
512
513 throw newInvalidEscapedCsvFieldException(value, i - 1);
514 default:
515 current.append(c);
516 }
517 } else {
518 switch (c) {
519 case COMMA:
520
521 unescaped.add(current.toString());
522 current.setLength(0);
523 break;
524 case DOUBLE_QUOTE:
525 if (current.length() == 0) {
526 quoted = true;
527 break;
528 }
529
530
531 case LINE_FEED:
532
533 case CARRIAGE_RETURN:
534
535 throw newInvalidEscapedCsvFieldException(value, i);
536 default:
537 current.append(c);
538 }
539 }
540 }
541 if (quoted) {
542 throw newInvalidEscapedCsvFieldException(value, last);
543 }
544 unescaped.add(current.toString());
545 return unescaped;
546 }
547
548
549
550
551
552
553 private static void validateCsvFormat(CharSequence value) {
554 int length = value.length();
555 for (int i = 0; i < length; i++) {
556 switch (value.charAt(i)) {
557 case DOUBLE_QUOTE:
558 case LINE_FEED:
559 case CARRIAGE_RETURN:
560 case COMMA:
561
562 throw newInvalidEscapedCsvFieldException(value, i);
563 default:
564 }
565 }
566 }
567
568 private static IllegalArgumentException newInvalidEscapedCsvFieldException(CharSequence value, int index) {
569 return new IllegalArgumentException("invalid escaped CSV field: " + value + " index: " + index);
570 }
571
572
573
574
575 public static int length(String s) {
576 return s == null ? 0 : s.length();
577 }
578
579
580
581
582 public static boolean isNullOrEmpty(String s) {
583 return s == null || s.isEmpty();
584 }
585
586
587
588
589
590
591
592
593 public static int indexOfNonWhiteSpace(CharSequence seq, int offset) {
594 for (; offset < seq.length(); ++offset) {
595 if (!Character.isWhitespace(seq.charAt(offset))) {
596 return offset;
597 }
598 }
599 return -1;
600 }
601
602
603
604
605
606
607
608
609 public static int indexOfWhiteSpace(CharSequence seq, int offset) {
610 for (; offset < seq.length(); ++offset) {
611 if (Character.isWhitespace(seq.charAt(offset))) {
612 return offset;
613 }
614 }
615 return -1;
616 }
617
618
619
620
621
622
623
624
625
626 public static boolean isSurrogate(char c) {
627 return c >= '\uD800' && c <= '\uDFFF';
628 }
629
630 private static boolean isDoubleQuote(char c) {
631 return c == DOUBLE_QUOTE;
632 }
633
634
635
636
637
638
639
640
641 public static boolean endsWith(CharSequence s, char c) {
642 int len = s.length();
643 return len > 0 && s.charAt(len - 1) == c;
644 }
645
646
647
648
649
650
651
652
653 public static CharSequence trimOws(CharSequence value) {
654 final int length = value.length();
655 if (length == 0) {
656 return value;
657 }
658 int start = indexOfFirstNonOwsChar(value, length);
659 int end = indexOfLastNonOwsChar(value, start, length);
660 return start == 0 && end == length - 1 ? value : value.subSequence(start, end + 1);
661 }
662
663
664
665
666
667
668
669
670
671 public static CharSequence join(CharSequence separator, Iterable<? extends CharSequence> elements) {
672 ObjectUtil.checkNotNull(separator, "separator");
673 ObjectUtil.checkNotNull(elements, "elements");
674
675 Iterator<? extends CharSequence> iterator = elements.iterator();
676 if (!iterator.hasNext()) {
677 return EMPTY_STRING;
678 }
679
680 CharSequence firstElement = iterator.next();
681 if (!iterator.hasNext()) {
682 return firstElement;
683 }
684
685 StringBuilder builder = new StringBuilder(firstElement);
686 do {
687 builder.append(separator).append(iterator.next());
688 } while (iterator.hasNext());
689
690 return builder;
691 }
692
693
694
695
696 private static int indexOfFirstNonOwsChar(CharSequence value, int length) {
697 int i = 0;
698 while (i < length && isOws(value.charAt(i))) {
699 i++;
700 }
701 return i;
702 }
703
704
705
706
707 private static int indexOfLastNonOwsChar(CharSequence value, int start, int length) {
708 int i = length - 1;
709 while (i > start && isOws(value.charAt(i))) {
710 i--;
711 }
712 return i;
713 }
714
715 private static boolean isOws(char c) {
716 return c == SPACE || c == TAB;
717 }
718
719 }