1 /*
2 * Copyright 2014 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.util;
17
18 import io.netty.util.internal.EmptyArrays;
19 import io.netty.util.internal.InternalThreadLocalMap;
20 import io.netty.util.internal.ObjectUtil;
21 import io.netty.util.internal.PlatformDependent;
22
23 import java.nio.ByteBuffer;
24 import java.nio.CharBuffer;
25 import java.nio.charset.Charset;
26 import java.nio.charset.CharsetEncoder;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32
33 import static io.netty.util.internal.MathUtil.isOutOfBounds;
34 import static io.netty.util.internal.ObjectUtil.checkNotNull;
35
36 /**
37 * A string which has been encoded into a character encoding whose character always takes a single byte, similarly to
38 * ASCII. It internally keeps its content in a byte array unlike {@link String}, which uses a character array, for
39 * reduced memory footprint and faster data transfer from/to byte-based data structures such as a byte array and
40 * {@link ByteBuffer}. It is often used in conjunction with {@code Headers} that require a {@link CharSequence}.
41 * <p>
42 * This class was designed to provide an immutable array of bytes, and caches some internal state based upon the value
43 * of this array. However underlying access to this byte array is provided via not copying the array on construction or
44 * {@link #array()}. If any changes are made to the underlying byte array it is the user's responsibility to call
45 * {@link #arrayChanged()} so the state of this class can be reset.
46 */
47 public final class AsciiString implements CharSequence, Comparable<CharSequence> {
48 public static final AsciiString EMPTY_STRING = cached("");
49 private static final char MAX_CHAR_VALUE = 255;
50
51 public static final int INDEX_NOT_FOUND = -1;
52
53 /**
54 * If this value is modified outside the constructor then call {@link #arrayChanged()}.
55 */
56 private final byte[] value;
57 /**
58 * Offset into {@link #value} that all operations should use when acting upon {@link #value}.
59 */
60 private final int offset;
61 /**
62 * Length in bytes for {@link #value} that we care about. This is independent from {@code value.length}
63 * because we may be looking at a subsection of the array.
64 */
65 private final int length;
66 /**
67 * The hash code is cached after it is first computed. It can be reset with {@link #arrayChanged()}.
68 */
69 private int hash;
70 /**
71 * Used to cache the {@link #toString()} value.
72 */
73 private String string;
74
75 /**
76 * Initialize this byte string based upon a byte array. A copy will be made.
77 */
78 public AsciiString(byte[] value) {
79 this(value, true);
80 }
81
82 /**
83 * Initialize this byte string based upon a byte array.
84 * {@code copy} determines if a copy is made or the array is shared.
85 */
86 public AsciiString(byte[] value, boolean copy) {
87 this(value, 0, value.length, copy);
88 }
89
90 /**
91 * Construct a new instance from a {@code byte[]} array.
92 * @param copy {@code true} then a copy of the memory will be made. {@code false} the underlying memory
93 * will be shared.
94 */
95 public AsciiString(byte[] value, int start, int length, boolean copy) {
96 if (copy) {
97 this.value = Arrays.copyOfRange(value, start, start + length);
98 this.offset = 0;
99 } else {
100 if (isOutOfBounds(start, length, value.length)) {
101 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" +
102 length + ") <= " + "value.length(" + value.length + ')');
103 }
104 this.value = value;
105 this.offset = start;
106 }
107 this.length = length;
108 }
109
110 /**
111 * Create a copy of the underlying storage from {@code value}.
112 * The copy will start at {@link ByteBuffer#position()} and copy {@link ByteBuffer#remaining()} bytes.
113 */
114 public AsciiString(ByteBuffer value) {
115 this(value, true);
116 }
117
118 /**
119 * Initialize an instance based upon the underlying storage from {@code value}.
120 * There is a potential to share the underlying array storage if {@link ByteBuffer#hasArray()} is {@code true}.
121 * if {@code copy} is {@code true} a copy will be made of the memory.
122 * if {@code copy} is {@code false} the underlying storage will be shared, if possible.
123 */
124 public AsciiString(ByteBuffer value, boolean copy) {
125 this(value, value.position(), value.remaining(), copy);
126 }
127
128 /**
129 * Initialize an {@link AsciiString} based upon the underlying storage from {@code value}.
130 * There is a potential to share the underlying array storage if {@link ByteBuffer#hasArray()} is {@code true}.
131 * if {@code copy} is {@code true} a copy will be made of the memory.
132 * if {@code copy} is {@code false} the underlying storage will be shared, if possible.
133 */
134 public AsciiString(ByteBuffer value, int start, int length, boolean copy) {
135 if (isOutOfBounds(start, length, value.capacity())) {
136 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
137 + ") <= " + "value.capacity(" + value.capacity() + ')');
138 }
139
140 if (value.hasArray()) {
141 if (copy) {
142 final int bufferOffset = value.arrayOffset() + start;
143 this.value = Arrays.copyOfRange(value.array(), bufferOffset, bufferOffset + length);
144 offset = 0;
145 } else {
146 this.value = value.array();
147 this.offset = start;
148 }
149 } else {
150 this.value = PlatformDependent.allocateUninitializedArray(length);
151 int oldPos = value.position();
152 value.get(this.value, 0, length);
153 value.position(oldPos);
154 this.offset = 0;
155 }
156 this.length = length;
157 }
158
159 /**
160 * Create a copy of {@code value} into this instance assuming ASCII encoding.
161 */
162 public AsciiString(char[] value) {
163 this(value, 0, value.length);
164 }
165
166 /**
167 * Create a copy of {@code value} into this instance assuming ASCII encoding.
168 * The copy will start at index {@code start} and copy {@code length} bytes.
169 */
170 public AsciiString(char[] value, int start, int length) {
171 if (isOutOfBounds(start, length, value.length)) {
172 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
173 + ") <= " + "value.length(" + value.length + ')');
174 }
175
176 this.value = PlatformDependent.allocateUninitializedArray(length);
177 for (int i = 0, j = start; i < length; i++, j++) {
178 this.value[i] = c2b(value[j]);
179 }
180 this.offset = 0;
181 this.length = length;
182 }
183
184 /**
185 * Create a copy of {@code value} into this instance using the encoding type of {@code charset}.
186 */
187 public AsciiString(char[] value, Charset charset) {
188 this(value, charset, 0, value.length);
189 }
190
191 /**
192 * Create a copy of {@code value} into a this instance using the encoding type of {@code charset}.
193 * The copy will start at index {@code start} and copy {@code length} bytes.
194 */
195 public AsciiString(char[] value, Charset charset, int start, int length) {
196 CharBuffer cbuf = CharBuffer.wrap(value, start, length);
197 CharsetEncoder encoder = CharsetUtil.encoder(charset);
198 ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
199 encoder.encode(cbuf, nativeBuffer, true);
200 final int bufferOffset = nativeBuffer.arrayOffset();
201 this.value = Arrays.copyOfRange(nativeBuffer.array(), bufferOffset, bufferOffset + nativeBuffer.position());
202 this.offset = 0;
203 this.length = this.value.length;
204 }
205
206 /**
207 * Create a copy of {@code value} into this instance assuming ASCII encoding.
208 */
209 public AsciiString(CharSequence value) {
210 this(value, 0, value.length());
211 }
212
213 /**
214 * Create a copy of {@code value} into this instance assuming ASCII encoding.
215 * The copy will start at index {@code start} and copy {@code length} bytes.
216 */
217 public AsciiString(CharSequence value, int start, int length) {
218 if (isOutOfBounds(start, length, value.length())) {
219 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
220 + ") <= " + "value.length(" + value.length() + ')');
221 }
222
223 this.value = PlatformDependent.allocateUninitializedArray(length);
224 for (int i = 0, j = start; i < length; i++, j++) {
225 this.value[i] = c2b(value.charAt(j));
226 }
227 this.offset = 0;
228 this.length = length;
229 }
230
231 /**
232 * Create a copy of {@code value} into this instance using the encoding type of {@code charset}.
233 */
234 public AsciiString(CharSequence value, Charset charset) {
235 this(value, charset, 0, value.length());
236 }
237
238 /**
239 * Create a copy of {@code value} into this instance using the encoding type of {@code charset}.
240 * The copy will start at index {@code start} and copy {@code length} bytes.
241 */
242 public AsciiString(CharSequence value, Charset charset, int start, int length) {
243 CharBuffer cbuf = CharBuffer.wrap(value, start, start + length);
244 CharsetEncoder encoder = CharsetUtil.encoder(charset);
245 ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
246 encoder.encode(cbuf, nativeBuffer, true);
247 final int offset = nativeBuffer.arrayOffset();
248 this.value = Arrays.copyOfRange(nativeBuffer.array(), offset, offset + nativeBuffer.position());
249 this.offset = 0;
250 this.length = this.value.length;
251 }
252
253 /**
254 * Iterates over the readable bytes of this buffer with the specified {@code processor} in ascending order.
255 *
256 * @return {@code -1} if the processor iterated to or beyond the end of the readable bytes.
257 * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
258 */
259 public int forEachByte(ByteProcessor visitor) throws Exception {
260 return forEachByte0(0, length(), visitor);
261 }
262
263 /**
264 * Iterates over the specified area of this buffer with the specified {@code processor} in ascending order.
265 * (i.e. {@code index}, {@code (index + 1)}, .. {@code (index + length - 1)}).
266 *
267 * @return {@code -1} if the processor iterated to or beyond the end of the specified area.
268 * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
269 */
270 public int forEachByte(int index, int length, ByteProcessor visitor) throws Exception {
271 if (isOutOfBounds(index, length, length())) {
272 throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
273 + ") <= " + "length(" + length() + ')');
274 }
275 return forEachByte0(index, length, visitor);
276 }
277
278 private int forEachByte0(int index, int length, ByteProcessor visitor) throws Exception {
279 final int len = offset + index + length;
280 for (int i = offset + index; i < len; ++i) {
281 if (!visitor.process(value[i])) {
282 return i - offset;
283 }
284 }
285 return -1;
286 }
287
288 /**
289 * Iterates over the readable bytes of this buffer with the specified {@code processor} in descending order.
290 *
291 * @return {@code -1} if the processor iterated to or beyond the beginning of the readable bytes.
292 * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
293 */
294 public int forEachByteDesc(ByteProcessor visitor) throws Exception {
295 return forEachByteDesc0(0, length(), visitor);
296 }
297
298 /**
299 * Iterates over the specified area of this buffer with the specified {@code processor} in descending order.
300 * (i.e. {@code (index + length - 1)}, {@code (index + length - 2)}, ... {@code index}).
301 *
302 * @return {@code -1} if the processor iterated to or beyond the beginning of the specified area.
303 * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
304 */
305 public int forEachByteDesc(int index, int length, ByteProcessor visitor) throws Exception {
306 if (isOutOfBounds(index, length, length())) {
307 throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
308 + ") <= " + "length(" + length() + ')');
309 }
310 return forEachByteDesc0(index, length, visitor);
311 }
312
313 private int forEachByteDesc0(int index, int length, ByteProcessor visitor) throws Exception {
314 final int end = offset + index;
315 for (int i = offset + index + length - 1; i >= end; --i) {
316 if (!visitor.process(value[i])) {
317 return i - offset;
318 }
319 }
320 return -1;
321 }
322
323 public byte byteAt(int index) {
324 // We must do a range check here to enforce the access does not go outside our sub region of the array.
325 // We rely on the array access itself to pick up the array out of bounds conditions
326 if (index < 0 || index >= length) {
327 throw new IndexOutOfBoundsException("index: " + index + " must be in the range [0," + length + ")");
328 }
329 // Try to use unsafe to avoid double checking the index bounds
330 if (PlatformDependent.hasUnsafe()) {
331 return PlatformDependent.getByte(value, index + offset);
332 }
333 return value[index + offset];
334 }
335
336 /**
337 * Determine if this instance has 0 length.
338 */
339 public boolean isEmpty() {
340 return length == 0;
341 }
342
343 /**
344 * The length in bytes of this instance.
345 */
346 @Override
347 public int length() {
348 return length;
349 }
350
351 /**
352 * During normal use cases the {@link AsciiString} should be immutable, but if the underlying array is shared,
353 * and changes then this needs to be called.
354 */
355 public void arrayChanged() {
356 string = null;
357 hash = 0;
358 }
359
360 /**
361 * This gives direct access to the underlying storage array.
362 * The {@link #toByteArray()} should be preferred over this method.
363 * If the return value is changed then {@link #arrayChanged()} must be called.
364 * @see #arrayOffset()
365 * @see #isEntireArrayUsed()
366 */
367 public byte[] array() {
368 return value;
369 }
370
371 /**
372 * The offset into {@link #array()} for which data for this ByteString begins.
373 * @see #array()
374 * @see #isEntireArrayUsed()
375 */
376 public int arrayOffset() {
377 return offset;
378 }
379
380 /**
381 * Determine if the storage represented by {@link #array()} is entirely used.
382 * @see #array()
383 */
384 public boolean isEntireArrayUsed() {
385 return offset == 0 && length == value.length;
386 }
387
388 /**
389 * Converts this string to a byte array.
390 */
391 public byte[] toByteArray() {
392 return toByteArray(0, length());
393 }
394
395 /**
396 * Converts a subset of this string to a byte array.
397 * The subset is defined by the range [{@code start}, {@code end}).
398 */
399 public byte[] toByteArray(int start, int end) {
400 return Arrays.copyOfRange(value, start + offset, end + offset);
401 }
402
403 /**
404 * Copies the content of this string to a byte array.
405 *
406 * @param srcIdx the starting offset of characters to copy.
407 * @param dst the destination byte array.
408 * @param dstIdx the starting offset in the destination byte array.
409 * @param length the number of characters to copy.
410 */
411 public void copy(int srcIdx, byte[] dst, int dstIdx, int length) {
412 if (isOutOfBounds(srcIdx, length, length())) {
413 throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
414 + length + ") <= srcLen(" + length() + ')');
415 }
416
417 System.arraycopy(value, srcIdx + offset, checkNotNull(dst, "dst"), dstIdx, length);
418 }
419
420 @Override
421 public char charAt(int index) {
422 return b2c(byteAt(index));
423 }
424
425 /**
426 * Determines if this {@code String} contains the sequence of characters in the {@code CharSequence} passed.
427 *
428 * @param cs the character sequence to search for.
429 * @return {@code true} if the sequence of characters are contained in this string, otherwise {@code false}.
430 */
431 public boolean contains(CharSequence cs) {
432 return indexOf(cs) >= 0;
433 }
434
435 /**
436 * Compares the specified string to this string using the ASCII values of the characters. Returns 0 if the strings
437 * contain the same characters in the same order. Returns a negative integer if the first non-equal character in
438 * this string has an ASCII value which is less than the ASCII value of the character at the same position in the
439 * specified string, or if this string is a prefix of the specified string. Returns a positive integer if the first
440 * non-equal character in this string has a ASCII value which is greater than the ASCII value of the character at
441 * the same position in the specified string, or if the specified string is a prefix of this string.
442 *
443 * @param string the string to compare.
444 * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a
445 * positive integer if this string is after the specified string.
446 * @throws NullPointerException if {@code string} is {@code null}.
447 */
448 @Override
449 public int compareTo(CharSequence string) {
450 if (this == string) {
451 return 0;
452 }
453
454 int result;
455 int length1 = length();
456 int length2 = string.length();
457 int minLength = Math.min(length1, length2);
458 for (int i = 0, j = arrayOffset(); i < minLength; i++, j++) {
459 result = b2c(value[j]) - string.charAt(i);
460 if (result != 0) {
461 return result;
462 }
463 }
464
465 return length1 - length2;
466 }
467
468 /**
469 * Concatenates this string and the specified string.
470 *
471 * @param string the string to concatenate
472 * @return a new string which is the concatenation of this string and the specified string.
473 */
474 public AsciiString concat(CharSequence string) {
475 int thisLen = length();
476 int thatLen = string.length();
477 if (thatLen == 0) {
478 return this;
479 }
480
481 if (string instanceof AsciiString) {
482 AsciiString that = (AsciiString) string;
483 if (isEmpty()) {
484 return that;
485 }
486
487 byte[] newValue = PlatformDependent.allocateUninitializedArray(thisLen + thatLen);
488 System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
489 System.arraycopy(that.value, that.arrayOffset(), newValue, thisLen, thatLen);
490 return new AsciiString(newValue, false);
491 }
492
493 if (isEmpty()) {
494 return new AsciiString(string);
495 }
496
497 byte[] newValue = PlatformDependent.allocateUninitializedArray(thisLen + thatLen);
498 System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
499 for (int i = thisLen, j = 0; i < newValue.length; i++, j++) {
500 newValue[i] = c2b(string.charAt(j));
501 }
502
503 return new AsciiString(newValue, false);
504 }
505
506 /**
507 * Compares the specified string to this string to determine if the specified string is a suffix.
508 *
509 * @param suffix the suffix to look for.
510 * @return {@code true} if the specified string is a suffix of this string, {@code false} otherwise.
511 * @throws NullPointerException if {@code suffix} is {@code null}.
512 */
513 public boolean endsWith(CharSequence suffix) {
514 int suffixLen = suffix.length();
515 return regionMatches(length() - suffixLen, suffix, 0, suffixLen);
516 }
517
518 /**
519 * Compares the specified string to this string ignoring the case of the characters and returns true if they are
520 * equal.
521 *
522 * @param string the string to compare.
523 * @return {@code true} if the specified string is equal to this string, {@code false} otherwise.
524 */
525 public boolean contentEqualsIgnoreCase(CharSequence string) {
526 if (this == string) {
527 return true;
528 }
529
530 if (string == null || string.length() != length()) {
531 return false;
532 }
533
534 if (string instanceof AsciiString) {
535 AsciiString rhs = (AsciiString) string;
536 for (int i = arrayOffset(), j = rhs.arrayOffset(), end = i + length(); i < end; ++i, ++j) {
537 if (!equalsIgnoreCase(value[i], rhs.value[j])) {
538 return false;
539 }
540 }
541 return true;
542 }
543
544 for (int i = arrayOffset(), j = 0, end = length(); j < end; ++i, ++j) {
545 if (!equalsIgnoreCase(b2c(value[i]), string.charAt(j))) {
546 return false;
547 }
548 }
549 return true;
550 }
551
552 /**
553 * Copies the characters in this string to a character array.
554 *
555 * @return a character array containing the characters of this string.
556 */
557 public char[] toCharArray() {
558 return toCharArray(0, length());
559 }
560
561 /**
562 * Copies the characters in this string to a character array.
563 *
564 * @return a character array containing the characters of this string.
565 */
566 public char[] toCharArray(int start, int end) {
567 int length = end - start;
568 if (length == 0) {
569 return EmptyArrays.EMPTY_CHARS;
570 }
571
572 if (isOutOfBounds(start, length, length())) {
573 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= srcIdx + length("
574 + length + ") <= srcLen(" + length() + ')');
575 }
576
577 final char[] buffer = new char[length];
578 for (int i = 0, j = start + arrayOffset(); i < length; i++, j++) {
579 buffer[i] = b2c(value[j]);
580 }
581 return buffer;
582 }
583
584 /**
585 * Copied the content of this string to a character array.
586 *
587 * @param srcIdx the starting offset of characters to copy.
588 * @param dst the destination character array.
589 * @param dstIdx the starting offset in the destination byte array.
590 * @param length the number of characters to copy.
591 */
592 public void copy(int srcIdx, char[] dst, int dstIdx, int length) {
593 ObjectUtil.checkNotNull(dst, "dst");
594
595 if (isOutOfBounds(srcIdx, length, length())) {
596 throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
597 + length + ") <= srcLen(" + length() + ')');
598 }
599
600 final int dstEnd = dstIdx + length;
601 for (int i = dstIdx, j = srcIdx + arrayOffset(); i < dstEnd; i++, j++) {
602 dst[i] = b2c(value[j]);
603 }
604 }
605
606 /**
607 * Copies a range of characters into a new string.
608 * @param start the offset of the first character (inclusive).
609 * @return a new string containing the characters from start to the end of the string.
610 * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
611 */
612 public AsciiString subSequence(int start) {
613 return subSequence(start, length());
614 }
615
616 /**
617 * Copies a range of characters into a new string.
618 * @param start the offset of the first character (inclusive).
619 * @param end The index to stop at (exclusive).
620 * @return a new string containing the characters from start to the end of the string.
621 * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
622 */
623 @Override
624 public AsciiString subSequence(int start, int end) {
625 return subSequence(start, end, true);
626 }
627
628 /**
629 * Either copy or share a subset of underlying sub-sequence of bytes.
630 * @param start the offset of the first character (inclusive).
631 * @param end The index to stop at (exclusive).
632 * @param copy If {@code true} then a copy of the underlying storage will be made.
633 * If {@code false} then the underlying storage will be shared.
634 * @return a new string containing the characters from start to the end of the string.
635 * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
636 */
637 public AsciiString subSequence(int start, int end, boolean copy) {
638 if (isOutOfBounds(start, end - start, length())) {
639 throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length("
640 + length() + ')');
641 }
642
643 if (start == 0 && end == length()) {
644 return this;
645 }
646
647 if (end == start) {
648 return EMPTY_STRING;
649 }
650
651 return new AsciiString(value, start + offset, end - start, copy);
652 }
653
654 /**
655 * Searches in this string for the first index of the specified string. The search for the string starts at the
656 * beginning and moves towards the end of this string.
657 *
658 * @param string the string to find.
659 * @return the index of the first character of the specified string in this string, -1 if the specified string is
660 * not a substring.
661 * @throws NullPointerException if {@code string} is {@code null}.
662 */
663 public int indexOf(CharSequence string) {
664 return indexOf(string, 0);
665 }
666
667 /**
668 * Searches in this string for the index of the specified string. The search for the string starts at the specified
669 * offset and moves towards the end of this string.
670 *
671 * @param subString the string to find.
672 * @param start the starting offset.
673 * @return the index of the first character of the specified string in this string, -1 if the specified string is
674 * not a substring.
675 * @throws NullPointerException if {@code subString} is {@code null}.
676 */
677 public int indexOf(CharSequence subString, int start) {
678 final int subCount = subString.length();
679 if (start < 0) {
680 start = 0;
681 }
682 if (subCount <= 0) {
683 return start < length ? start : length;
684 }
685 if (subCount > length - start) {
686 return INDEX_NOT_FOUND;
687 }
688
689 final char firstChar = subString.charAt(0);
690 if (firstChar > MAX_CHAR_VALUE) {
691 return INDEX_NOT_FOUND;
692 }
693 final byte firstCharAsByte = c2b0(firstChar);
694 final int len = offset + length - subCount;
695 for (int i = start + offset; i <= len; ++i) {
696 if (value[i] == firstCharAsByte) {
697 int o1 = i, o2 = 0;
698 while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) {
699 // Intentionally empty
700 }
701 if (o2 == subCount) {
702 return i - offset;
703 }
704 }
705 }
706 return INDEX_NOT_FOUND;
707 }
708
709 /**
710 * Searches in this string for the index of the specified char {@code ch}.
711 * The search for the char starts at the specified offset {@code start} and moves towards the end of this string.
712 *
713 * @param ch the char to find.
714 * @param start the starting offset.
715 * @return the index of the first occurrence of the specified char {@code ch} in this string,
716 * -1 if found no occurrence.
717 */
718 public int indexOf(char ch, int start) {
719 if (ch > MAX_CHAR_VALUE) {
720 return INDEX_NOT_FOUND;
721 }
722
723 if (start < 0) {
724 start = 0;
725 }
726
727 final byte chAsByte = c2b0(ch);
728 final int len = offset + length;
729 for (int i = start + offset; i < len; ++i) {
730 if (value[i] == chAsByte) {
731 return i - offset;
732 }
733 }
734 return INDEX_NOT_FOUND;
735 }
736
737 /**
738 * Searches in this string for the last index of the specified string. The search for the string starts at the end
739 * and moves towards the beginning of this string.
740 *
741 * @param string the string to find.
742 * @return the index of the first character of the specified string in this string, -1 if the specified string is
743 * not a substring.
744 * @throws NullPointerException if {@code string} is {@code null}.
745 */
746 public int lastIndexOf(CharSequence string) {
747 // Use count instead of count - 1 so lastIndexOf("") answers count
748 return lastIndexOf(string, length);
749 }
750
751 /**
752 * Searches in this string for the index of the specified string. The search for the string starts at the specified
753 * offset and moves towards the beginning of this string.
754 *
755 * @param subString the string to find.
756 * @param start the starting offset.
757 * @return the index of the first character of the specified string in this string , -1 if the specified string is
758 * not a substring.
759 * @throws NullPointerException if {@code subString} is {@code null}.
760 */
761 public int lastIndexOf(CharSequence subString, int start) {
762 final int subCount = subString.length();
763 start = Math.min(start, length - subCount);
764 if (start < 0) {
765 return INDEX_NOT_FOUND;
766 }
767 if (subCount == 0) {
768 return start;
769 }
770
771 final char firstChar = subString.charAt(0);
772 if (firstChar > MAX_CHAR_VALUE) {
773 return INDEX_NOT_FOUND;
774 }
775 final byte firstCharAsByte = c2b0(firstChar);
776 for (int i = offset + start; i >= 0; --i) {
777 if (value[i] == firstCharAsByte) {
778 int o1 = i, o2 = 0;
779 while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) {
780 // Intentionally empty
781 }
782 if (o2 == subCount) {
783 return i - offset;
784 }
785 }
786 }
787 return INDEX_NOT_FOUND;
788 }
789
790 /**
791 * Compares the specified string to this string and compares the specified range of characters to determine if they
792 * are the same.
793 *
794 * @param thisStart the starting offset in this string.
795 * @param string the string to compare.
796 * @param start the starting offset in the specified string.
797 * @param length the number of characters to compare.
798 * @return {@code true} if the ranges of characters are equal, {@code false} otherwise
799 * @throws NullPointerException if {@code string} is {@code null}.
800 */
801 public boolean regionMatches(int thisStart, CharSequence string, int start, int length) {
802 ObjectUtil.checkNotNull(string, "string");
803
804 if (start < 0 || string.length() - start < length) {
805 return false;
806 }
807
808 final int thisLen = length();
809 if (thisStart < 0 || thisLen - thisStart < length) {
810 return false;
811 }
812
813 if (length <= 0) {
814 return true;
815 }
816
817 final int thatEnd = start + length;
818 for (int i = start, j = thisStart + arrayOffset(); i < thatEnd; i++, j++) {
819 if (b2c(value[j]) != string.charAt(i)) {
820 return false;
821 }
822 }
823 return true;
824 }
825
826 /**
827 * Compares the specified string to this string and compares the specified range of characters to determine if they
828 * are the same. When ignoreCase is true, the case of the characters is ignored during the comparison.
829 *
830 * @param ignoreCase specifies if case should be ignored.
831 * @param thisStart the starting offset in this string.
832 * @param string the string to compare.
833 * @param start the starting offset in the specified string.
834 * @param length the number of characters to compare.
835 * @return {@code true} if the ranges of characters are equal, {@code false} otherwise.
836 * @throws NullPointerException if {@code string} is {@code null}.
837 */
838 public boolean regionMatches(boolean ignoreCase, int thisStart, CharSequence string, int start, int length) {
839 if (!ignoreCase) {
840 return regionMatches(thisStart, string, start, length);
841 }
842
843 ObjectUtil.checkNotNull(string, "string");
844
845 final int thisLen = length();
846 if (thisStart < 0 || length > thisLen - thisStart) {
847 return false;
848 }
849 if (start < 0 || length > string.length() - start) {
850 return false;
851 }
852
853 thisStart += arrayOffset();
854 final int thisEnd = thisStart + length;
855 while (thisStart < thisEnd) {
856 if (!equalsIgnoreCase(b2c(value[thisStart++]), string.charAt(start++))) {
857 return false;
858 }
859 }
860 return true;
861 }
862
863 /**
864 * Copies this string replacing occurrences of the specified character with another character.
865 *
866 * @param oldChar the character to replace.
867 * @param newChar the replacement character.
868 * @return a new string with occurrences of oldChar replaced by newChar.
869 */
870 public AsciiString replace(char oldChar, char newChar) {
871 if (oldChar > MAX_CHAR_VALUE) {
872 return this;
873 }
874
875 final byte oldCharAsByte = c2b0(oldChar);
876 final byte newCharAsByte = c2b(newChar);
877 final int len = offset + length;
878 for (int i = offset; i < len; ++i) {
879 if (value[i] == oldCharAsByte) {
880 byte[] buffer = PlatformDependent.allocateUninitializedArray(length());
881 System.arraycopy(value, offset, buffer, 0, i - offset);
882 buffer[i - offset] = newCharAsByte;
883 ++i;
884 for (; i < len; ++i) {
885 byte oldValue = value[i];
886 buffer[i - offset] = oldValue != oldCharAsByte ? oldValue : newCharAsByte;
887 }
888 return new AsciiString(buffer, false);
889 }
890 }
891 return this;
892 }
893
894 /**
895 * Compares the specified string to this string to determine if the specified string is a prefix.
896 *
897 * @param prefix the string to look for.
898 * @return {@code true} if the specified string is a prefix of this string, {@code false} otherwise
899 * @throws NullPointerException if {@code prefix} is {@code null}.
900 */
901 public boolean startsWith(CharSequence prefix) {
902 return startsWith(prefix, 0);
903 }
904
905 /**
906 * Compares the specified string to this string, starting at the specified offset, to determine if the specified
907 * string is a prefix.
908 *
909 * @param prefix the string to look for.
910 * @param start the starting offset.
911 * @return {@code true} if the specified string occurs in this string at the specified offset, {@code false}
912 * otherwise.
913 * @throws NullPointerException if {@code prefix} is {@code null}.
914 */
915 public boolean startsWith(CharSequence prefix, int start) {
916 return regionMatches(start, prefix, 0, prefix.length());
917 }
918
919 /**
920 * Converts the characters in this string to lowercase, using the default Locale.
921 *
922 * @return a new string containing the lowercase characters equivalent to the characters in this string.
923 */
924 public AsciiString toLowerCase() {
925 boolean lowercased = true;
926 int i, j;
927 final int len = length() + arrayOffset();
928 for (i = arrayOffset(); i < len; ++i) {
929 byte b = value[i];
930 if (b >= 'A' && b <= 'Z') {
931 lowercased = false;
932 break;
933 }
934 }
935
936 // Check if this string does not contain any uppercase characters.
937 if (lowercased) {
938 return this;
939 }
940
941 final byte[] newValue = PlatformDependent.allocateUninitializedArray(length());
942 for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) {
943 newValue[i] = toLowerCase(value[j]);
944 }
945
946 return new AsciiString(newValue, false);
947 }
948
949 /**
950 * Converts the characters in this string to uppercase, using the default Locale.
951 *
952 * @return a new string containing the uppercase characters equivalent to the characters in this string.
953 */
954 public AsciiString toUpperCase() {
955 boolean uppercased = true;
956 int i, j;
957 final int len = length() + arrayOffset();
958 for (i = arrayOffset(); i < len; ++i) {
959 byte b = value[i];
960 if (b >= 'a' && b <= 'z') {
961 uppercased = false;
962 break;
963 }
964 }
965
966 // Check if this string does not contain any lowercase characters.
967 if (uppercased) {
968 return this;
969 }
970
971 final byte[] newValue = PlatformDependent.allocateUninitializedArray(length());
972 for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) {
973 newValue[i] = toUpperCase(value[j]);
974 }
975
976 return new AsciiString(newValue, false);
977 }
978
979 /**
980 * Copies this string removing white space characters from the beginning and end of the string, and tries not to
981 * copy if possible.
982 *
983 * @param c The {@link CharSequence} to trim.
984 * @return a new string with characters {@code <= \\u0020} removed from the beginning and the end.
985 */
986 public static CharSequence trim(CharSequence c) {
987 if (c instanceof AsciiString) {
988 return ((AsciiString) c).trim();
989 }
990 if (c instanceof String) {
991 return ((String) c).trim();
992 }
993 int start = 0, last = c.length() - 1;
994 int end = last;
995 while (start <= end && c.charAt(start) <= ' ') {
996 start++;
997 }
998 while (end >= start && c.charAt(end) <= ' ') {
999 end--;
1000 }
1001 if (start == 0 && end == last) {
1002 return c;
1003 }
1004 return c.subSequence(start, end);
1005 }
1006
1007 /**
1008 * Duplicates this string removing white space characters from the beginning and end of the
1009 * string, without copying.
1010 *
1011 * @return a new string with characters {@code <= \\u0020} removed from the beginning and the end.
1012 */
1013 public AsciiString trim() {
1014 int start = arrayOffset(), last = arrayOffset() + length() - 1;
1015 int end = last;
1016 while (start <= end && value[start] <= ' ') {
1017 start++;
1018 }
1019 while (end >= start && value[end] <= ' ') {
1020 end--;
1021 }
1022 if (start == 0 && end == last) {
1023 return this;
1024 }
1025 return new AsciiString(value, start, end - start + 1, false);
1026 }
1027
1028 /**
1029 * Compares a {@code CharSequence} to this {@code String} to determine if their contents are equal.
1030 *
1031 * @param a the character sequence to compare to.
1032 * @return {@code true} if equal, otherwise {@code false}
1033 */
1034 public boolean contentEquals(CharSequence a) {
1035 if (this == a) {
1036 return true;
1037 }
1038
1039 if (a == null || a.length() != length()) {
1040 return false;
1041 }
1042 if (a instanceof AsciiString) {
1043 return equals(a);
1044 }
1045
1046 for (int i = arrayOffset(), j = 0; j < a.length(); ++i, ++j) {
1047 if (b2c(value[i]) != a.charAt(j)) {
1048 return false;
1049 }
1050 }
1051 return true;
1052 }
1053
1054 /**
1055 * Determines whether this string matches a given regular expression.
1056 *
1057 * @param expr the regular expression to be matched.
1058 * @return {@code true} if the expression matches, otherwise {@code false}.
1059 * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid.
1060 * @throws NullPointerException if {@code expr} is {@code null}.
1061 */
1062 public boolean matches(String expr) {
1063 return Pattern.matches(expr, this);
1064 }
1065
1066 /**
1067 * Splits this string using the supplied regular expression {@code expr}. The parameter {@code max} controls the
1068 * behavior how many times the pattern is applied to the string.
1069 *
1070 * @param expr the regular expression used to divide the string.
1071 * @param max the number of entries in the resulting array.
1072 * @return an array of Strings created by separating the string along matches of the regular expression.
1073 * @throws NullPointerException if {@code expr} is {@code null}.
1074 * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid.
1075 * @see Pattern#split(CharSequence, int)
1076 */
1077 public AsciiString[] split(String expr, int max) {
1078 return toAsciiStringArray(Pattern.compile(expr).split(this, max));
1079 }
1080
1081 /**
1082 * Splits the specified {@link String} with the specified delimiter..
1083 */
1084 public AsciiString[] split(char delim) {
1085 final List<AsciiString> res = InternalThreadLocalMap.get().arrayList();
1086
1087 int start = 0;
1088 final int length = length();
1089 for (int i = start; i < length; i++) {
1090 if (charAt(i) == delim) {
1091 if (start == i) {
1092 res.add(EMPTY_STRING);
1093 } else {
1094 res.add(new AsciiString(value, start + arrayOffset(), i - start, false));
1095 }
1096 start = i + 1;
1097 }
1098 }
1099
1100 if (start == 0) { // If no delimiter was found in the value
1101 res.add(this);
1102 } else {
1103 if (start != length) {
1104 // Add the last element if it's not empty.
1105 res.add(new AsciiString(value, start + arrayOffset(), length - start, false));
1106 } else {
1107 // Truncate trailing empty elements.
1108 for (int i = res.size() - 1; i >= 0; i--) {
1109 if (res.get(i).isEmpty()) {
1110 res.remove(i);
1111 } else {
1112 break;
1113 }
1114 }
1115 }
1116 }
1117
1118 return res.toArray(new AsciiString[0]);
1119 }
1120
1121 /**
1122 * {@inheritDoc}
1123 * <p>
1124 * Provides a case-insensitive hash code for Ascii like byte strings.
1125 */
1126 @Override
1127 public int hashCode() {
1128 int h = hash;
1129 if (h == 0) {
1130 h = PlatformDependent.hashCodeAscii(value, offset, length);
1131 hash = h;
1132 }
1133 return h;
1134 }
1135
1136 @Override
1137 public boolean equals(Object obj) {
1138 if (obj == null || obj.getClass() != AsciiString.class) {
1139 return false;
1140 }
1141 if (this == obj) {
1142 return true;
1143 }
1144
1145 AsciiString other = (AsciiString) obj;
1146 return length() == other.length() &&
1147 hashCode() == other.hashCode() &&
1148 PlatformDependent.equals(array(), arrayOffset(), other.array(), other.arrayOffset(), length());
1149 }
1150
1151 /**
1152 * Translates the entire byte string to a {@link String}.
1153 * @see #toString(int)
1154 */
1155 @Override
1156 public String toString() {
1157 String cache = string;
1158 if (cache == null) {
1159 cache = toString(0);
1160 string = cache;
1161 }
1162 return cache;
1163 }
1164
1165 /**
1166 * Translates the entire byte string to a {@link String} using the {@code charset} encoding.
1167 * @see #toString(int, int)
1168 */
1169 public String toString(int start) {
1170 return toString(start, length());
1171 }
1172
1173 /**
1174 * Translates the [{@code start}, {@code end}) range of this byte string to a {@link String}.
1175 */
1176 public String toString(int start, int end) {
1177 int length = end - start;
1178 if (length == 0) {
1179 return "";
1180 }
1181
1182 if (isOutOfBounds(start, length, length())) {
1183 throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= srcIdx + length("
1184 + length + ") <= srcLen(" + length() + ')');
1185 }
1186
1187 @SuppressWarnings("deprecation")
1188 final String str = new String(value, 0, start + offset, length);
1189 return str;
1190 }
1191
1192 public boolean parseBoolean() {
1193 return length >= 1 && value[offset] != 0;
1194 }
1195
1196 public char parseChar() {
1197 return parseChar(0);
1198 }
1199
1200 public char parseChar(int start) {
1201 if (start + 1 >= length()) {
1202 throw new IndexOutOfBoundsException("2 bytes required to convert to character. index " +
1203 start + " would go out of bounds.");
1204 }
1205 final int startWithOffset = start + offset;
1206 return (char) ((b2c(value[startWithOffset]) << 8) | b2c(value[startWithOffset + 1]));
1207 }
1208
1209 public short parseShort() {
1210 return parseShort(0, length(), 10);
1211 }
1212
1213 public short parseShort(int radix) {
1214 return parseShort(0, length(), radix);
1215 }
1216
1217 public short parseShort(int start, int end) {
1218 return parseShort(start, end, 10);
1219 }
1220
1221 public short parseShort(int start, int end, int radix) {
1222 int intValue = parseInt(start, end, radix);
1223 short result = (short) intValue;
1224 if (result != intValue) {
1225 throw new NumberFormatException(subSequence(start, end, false).toString());
1226 }
1227 return result;
1228 }
1229
1230 public int parseInt() {
1231 return parseInt(0, length(), 10);
1232 }
1233
1234 public int parseInt(int radix) {
1235 return parseInt(0, length(), radix);
1236 }
1237
1238 public int parseInt(int start, int end) {
1239 return parseInt(start, end, 10);
1240 }
1241
1242 public int parseInt(int start, int end, int radix) {
1243 if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
1244 throw new NumberFormatException();
1245 }
1246
1247 if (start == end) {
1248 throw new NumberFormatException();
1249 }
1250
1251 int i = start;
1252 boolean negative = byteAt(i) == '-';
1253 if (negative && ++i == end) {
1254 throw new NumberFormatException(subSequence(start, end, false).toString());
1255 }
1256
1257 return parseInt(i, end, radix, negative);
1258 }
1259
1260 private int parseInt(int start, int end, int radix, boolean negative) {
1261 int max = Integer.MIN_VALUE / radix;
1262 int result = 0;
1263 int currOffset = start;
1264 while (currOffset < end) {
1265 int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
1266 if (digit == -1) {
1267 throw new NumberFormatException(subSequence(start, end, false).toString());
1268 }
1269 if (max > result) {
1270 throw new NumberFormatException(subSequence(start, end, false).toString());
1271 }
1272 int next = result * radix - digit;
1273 if (next > result) {
1274 throw new NumberFormatException(subSequence(start, end, false).toString());
1275 }
1276 result = next;
1277 }
1278 if (!negative) {
1279 result = -result;
1280 if (result < 0) {
1281 throw new NumberFormatException(subSequence(start, end, false).toString());
1282 }
1283 }
1284 return result;
1285 }
1286
1287 public long parseLong() {
1288 return parseLong(0, length(), 10);
1289 }
1290
1291 public long parseLong(int radix) {
1292 return parseLong(0, length(), radix);
1293 }
1294
1295 public long parseLong(int start, int end) {
1296 return parseLong(start, end, 10);
1297 }
1298
1299 public long parseLong(int start, int end, int radix) {
1300 if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
1301 throw new NumberFormatException();
1302 }
1303
1304 if (start == end) {
1305 throw new NumberFormatException();
1306 }
1307
1308 int i = start;
1309 boolean negative = byteAt(i) == '-';
1310 if (negative && ++i == end) {
1311 throw new NumberFormatException(subSequence(start, end, false).toString());
1312 }
1313
1314 return parseLong(i, end, radix, negative);
1315 }
1316
1317 private long parseLong(int start, int end, int radix, boolean negative) {
1318 long max = Long.MIN_VALUE / radix;
1319 long result = 0;
1320 int currOffset = start;
1321 while (currOffset < end) {
1322 int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
1323 if (digit == -1) {
1324 throw new NumberFormatException(subSequence(start, end, false).toString());
1325 }
1326 if (max > result) {
1327 throw new NumberFormatException(subSequence(start, end, false).toString());
1328 }
1329 long next = result * radix - digit;
1330 if (next > result) {
1331 throw new NumberFormatException(subSequence(start, end, false).toString());
1332 }
1333 result = next;
1334 }
1335 if (!negative) {
1336 result = -result;
1337 if (result < 0) {
1338 throw new NumberFormatException(subSequence(start, end, false).toString());
1339 }
1340 }
1341 return result;
1342 }
1343
1344 public float parseFloat() {
1345 return parseFloat(0, length());
1346 }
1347
1348 public float parseFloat(int start, int end) {
1349 return Float.parseFloat(toString(start, end));
1350 }
1351
1352 public double parseDouble() {
1353 return parseDouble(0, length());
1354 }
1355
1356 public double parseDouble(int start, int end) {
1357 return Double.parseDouble(toString(start, end));
1358 }
1359
1360 public static final HashingStrategy<CharSequence> CASE_INSENSITIVE_HASHER =
1361 new HashingStrategy<CharSequence>() {
1362 @Override
1363 public int hashCode(CharSequence o) {
1364 return AsciiString.hashCode(o);
1365 }
1366
1367 @Override
1368 public boolean equals(CharSequence a, CharSequence b) {
1369 return AsciiString.contentEqualsIgnoreCase(a, b);
1370 }
1371 };
1372
1373 public static final HashingStrategy<CharSequence> CASE_SENSITIVE_HASHER =
1374 new HashingStrategy<CharSequence>() {
1375 @Override
1376 public int hashCode(CharSequence o) {
1377 return AsciiString.hashCode(o);
1378 }
1379
1380 @Override
1381 public boolean equals(CharSequence a, CharSequence b) {
1382 return AsciiString.contentEquals(a, b);
1383 }
1384 };
1385
1386 /**
1387 * Returns an {@link AsciiString} containing the given character sequence. If the given string is already a
1388 * {@link AsciiString}, just returns the same instance.
1389 */
1390 public static AsciiString of(CharSequence string) {
1391 return string instanceof AsciiString ? (AsciiString) string : new AsciiString(string);
1392 }
1393
1394 /**
1395 * Returns an {@link AsciiString} containing the given string and retains/caches the input
1396 * string for later use in {@link #toString()}.
1397 * Used for the constants (which already stored in the JVM's string table) and in cases
1398 * where the guaranteed use of the {@link #toString()} method.
1399 */
1400 public static AsciiString cached(String string) {
1401 AsciiString asciiString = new AsciiString(string);
1402 asciiString.string = string;
1403 return asciiString;
1404 }
1405
1406 /**
1407 * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing
1408 * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary
1409 * {@link CharSequence}s into the same headers.
1410 */
1411 public static int hashCode(CharSequence value) {
1412 if (value == null) {
1413 return 0;
1414 }
1415 if (value instanceof AsciiString) {
1416 return value.hashCode();
1417 }
1418
1419 return PlatformDependent.hashCodeAscii(value);
1420 }
1421
1422 /**
1423 * Determine if {@code a} contains {@code b} in a case sensitive manner.
1424 */
1425 public static boolean contains(CharSequence a, CharSequence b) {
1426 return contains(a, b, DefaultCharEqualityComparator.INSTANCE);
1427 }
1428
1429 /**
1430 * Determine if {@code a} contains {@code b} in a case insensitive manner.
1431 */
1432 public static boolean containsIgnoreCase(CharSequence a, CharSequence b) {
1433 return contains(a, b, AsciiCaseInsensitiveCharEqualityComparator.INSTANCE);
1434 }
1435
1436 /**
1437 * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit
1438 * ASCII.
1439 */
1440 public static boolean contentEqualsIgnoreCase(CharSequence a, CharSequence b) {
1441 if (a == null || b == null) {
1442 return a == b;
1443 }
1444
1445 if (a instanceof AsciiString) {
1446 return ((AsciiString) a).contentEqualsIgnoreCase(b);
1447 }
1448 if (b instanceof AsciiString) {
1449 return ((AsciiString) b).contentEqualsIgnoreCase(a);
1450 }
1451
1452 if (a.length() != b.length()) {
1453 return false;
1454 }
1455 for (int i = 0; i < a.length(); ++i) {
1456 if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) {
1457 return false;
1458 }
1459 }
1460 return true;
1461 }
1462
1463 /**
1464 * Determine if {@code collection} contains {@code value} and using
1465 * {@link #contentEqualsIgnoreCase(CharSequence, CharSequence)} to compare values.
1466 * @param collection The collection to look for and equivalent element as {@code value}.
1467 * @param value The value to look for in {@code collection}.
1468 * @return {@code true} if {@code collection} contains {@code value} according to
1469 * {@link #contentEqualsIgnoreCase(CharSequence, CharSequence)}. {@code false} otherwise.
1470 * @see #contentEqualsIgnoreCase(CharSequence, CharSequence)
1471 */
1472 public static boolean containsContentEqualsIgnoreCase(Collection<CharSequence> collection, CharSequence value) {
1473 for (CharSequence v : collection) {
1474 if (contentEqualsIgnoreCase(value, v)) {
1475 return true;
1476 }
1477 }
1478 return false;
1479 }
1480
1481 /**
1482 * Determine if {@code a} contains all of the values in {@code b} using
1483 * {@link #contentEqualsIgnoreCase(CharSequence, CharSequence)} to compare values.
1484 * @param a The collection under test.
1485 * @param b The values to test for.
1486 * @return {@code true} if {@code a} contains all of the values in {@code b} using
1487 * {@link #contentEqualsIgnoreCase(CharSequence, CharSequence)} to compare values. {@code false} otherwise.
1488 * @see #contentEqualsIgnoreCase(CharSequence, CharSequence)
1489 */
1490 public static boolean containsAllContentEqualsIgnoreCase(Collection<CharSequence> a, Collection<CharSequence> b) {
1491 for (CharSequence v : b) {
1492 if (!containsContentEqualsIgnoreCase(a, v)) {
1493 return false;
1494 }
1495 }
1496 return true;
1497 }
1498
1499 /**
1500 * Returns {@code true} if the content of both {@link CharSequence}'s are equals. This only supports 8-bit ASCII.
1501 */
1502 public static boolean contentEquals(CharSequence a, CharSequence b) {
1503 if (a == null || b == null) {
1504 return a == b;
1505 }
1506
1507 if (a instanceof AsciiString) {
1508 return ((AsciiString) a).contentEquals(b);
1509 }
1510
1511 if (b instanceof AsciiString) {
1512 return ((AsciiString) b).contentEquals(a);
1513 }
1514
1515 if (a.length() != b.length()) {
1516 return false;
1517 }
1518 for (int i = 0; i < a.length(); ++i) {
1519 if (a.charAt(i) != b.charAt(i)) {
1520 return false;
1521 }
1522 }
1523 return true;
1524 }
1525
1526 private static AsciiString[] toAsciiStringArray(String[] jdkResult) {
1527 AsciiString[] res = new AsciiString[jdkResult.length];
1528 for (int i = 0; i < jdkResult.length; i++) {
1529 res[i] = new AsciiString(jdkResult[i]);
1530 }
1531 return res;
1532 }
1533
1534 private interface CharEqualityComparator {
1535 boolean equals(char a, char b);
1536 }
1537
1538 private static final class DefaultCharEqualityComparator implements CharEqualityComparator {
1539 static final DefaultCharEqualityComparator INSTANCE = new DefaultCharEqualityComparator();
1540 private DefaultCharEqualityComparator() { }
1541
1542 @Override
1543 public boolean equals(char a, char b) {
1544 return a == b;
1545 }
1546 }
1547
1548 private static final class AsciiCaseInsensitiveCharEqualityComparator implements CharEqualityComparator {
1549 static final AsciiCaseInsensitiveCharEqualityComparator
1550 INSTANCE = new AsciiCaseInsensitiveCharEqualityComparator();
1551 private AsciiCaseInsensitiveCharEqualityComparator() { }
1552
1553 @Override
1554 public boolean equals(char a, char b) {
1555 return equalsIgnoreCase(a, b);
1556 }
1557 }
1558
1559 private static final class GeneralCaseInsensitiveCharEqualityComparator implements CharEqualityComparator {
1560 static final GeneralCaseInsensitiveCharEqualityComparator
1561 INSTANCE = new GeneralCaseInsensitiveCharEqualityComparator();
1562 private GeneralCaseInsensitiveCharEqualityComparator() { }
1563
1564 @Override
1565 public boolean equals(char a, char b) {
1566 //For motivation, why we need two checks, see comment in String#regionMatches
1567 return Character.toUpperCase(a) == Character.toUpperCase(b) ||
1568 Character.toLowerCase(a) == Character.toLowerCase(b);
1569 }
1570 }
1571
1572 private static boolean contains(CharSequence a, CharSequence b, CharEqualityComparator cmp) {
1573 if (a == null || b == null || a.length() < b.length()) {
1574 return false;
1575 }
1576 if (b.length() == 0) {
1577 return true;
1578 }
1579 int bStart = 0;
1580 for (int i = 0; i < a.length(); ++i) {
1581 if (cmp.equals(b.charAt(bStart), a.charAt(i))) {
1582 // If b is consumed then true.
1583 if (++bStart == b.length()) {
1584 return true;
1585 }
1586 } else if (a.length() - i < b.length()) {
1587 // If there are not enough characters left in a for b to be contained, then false.
1588 return false;
1589 } else {
1590 bStart = 0;
1591 }
1592 }
1593 return false;
1594 }
1595
1596 private static boolean regionMatchesCharSequences(final CharSequence cs, final int csStart,
1597 final CharSequence string, final int start, final int length,
1598 CharEqualityComparator charEqualityComparator) {
1599 //general purpose implementation for CharSequences
1600 if (csStart < 0 || length > cs.length() - csStart) {
1601 return false;
1602 }
1603 if (start < 0 || length > string.length() - start) {
1604 return false;
1605 }
1606
1607 int csIndex = csStart;
1608 int csEnd = csIndex + length;
1609 int stringIndex = start;
1610
1611 while (csIndex < csEnd) {
1612 char c1 = cs.charAt(csIndex++);
1613 char c2 = string.charAt(stringIndex++);
1614
1615 if (!charEqualityComparator.equals(c1, c2)) {
1616 return false;
1617 }
1618 }
1619 return true;
1620 }
1621
1622 /**
1623 * This methods make regionMatches operation correctly for any chars in strings
1624 * @param cs the {@code CharSequence} to be processed
1625 * @param ignoreCase specifies if case should be ignored.
1626 * @param csStart the starting offset in the {@code cs} CharSequence
1627 * @param string the {@code CharSequence} to compare.
1628 * @param start the starting offset in the specified {@code string}.
1629 * @param length the number of characters to compare.
1630 * @return {@code true} if the ranges of characters are equal, {@code false} otherwise.
1631 */
1632 public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int csStart,
1633 final CharSequence string, final int start, final int length) {
1634 if (cs == null || string == null) {
1635 return false;
1636 }
1637
1638 if (cs instanceof String && string instanceof String) {
1639 return ((String) cs).regionMatches(ignoreCase, csStart, (String) string, start, length);
1640 }
1641
1642 if (cs instanceof AsciiString) {
1643 return ((AsciiString) cs).regionMatches(ignoreCase, csStart, string, start, length);
1644 }
1645
1646 return regionMatchesCharSequences(cs, csStart, string, start, length,
1647 ignoreCase ? GeneralCaseInsensitiveCharEqualityComparator.INSTANCE :
1648 DefaultCharEqualityComparator.INSTANCE);
1649 }
1650
1651 /**
1652 * This is optimized version of regionMatches for string with ASCII chars only
1653 * @param cs the {@code CharSequence} to be processed
1654 * @param ignoreCase specifies if case should be ignored.
1655 * @param csStart the starting offset in the {@code cs} CharSequence
1656 * @param string the {@code CharSequence} to compare.
1657 * @param start the starting offset in the specified {@code string}.
1658 * @param length the number of characters to compare.
1659 * @return {@code true} if the ranges of characters are equal, {@code false} otherwise.
1660 */
1661 public static boolean regionMatchesAscii(final CharSequence cs, final boolean ignoreCase, final int csStart,
1662 final CharSequence string, final int start, final int length) {
1663 if (cs == null || string == null) {
1664 return false;
1665 }
1666
1667 if (!ignoreCase && cs instanceof String && string instanceof String) {
1668 //we don't call regionMatches from String for ignoreCase==true. It's a general purpose method,
1669 //which make complex comparison in case of ignoreCase==true, which is useless for ASCII-only strings.
1670 //To avoid applying this complex ignore-case comparison, we will use regionMatchesCharSequences
1671 return ((String) cs).regionMatches(false, csStart, (String) string, start, length);
1672 }
1673
1674 if (cs instanceof AsciiString) {
1675 return ((AsciiString) cs).regionMatches(ignoreCase, csStart, string, start, length);
1676 }
1677
1678 return regionMatchesCharSequences(cs, csStart, string, start, length,
1679 ignoreCase ? AsciiCaseInsensitiveCharEqualityComparator.INSTANCE :
1680 DefaultCharEqualityComparator.INSTANCE);
1681 }
1682
1683 /**
1684 * <p>Case in-sensitive find of the first index within a CharSequence
1685 * from the specified position.</p>
1686 *
1687 * <p>A {@code null} CharSequence will return {@code -1}.
1688 * A negative start position is treated as zero.
1689 * An empty ("") search CharSequence always matches.
1690 * A start position greater than the string length only matches
1691 * an empty search CharSequence.</p>
1692 *
1693 * <pre>
1694 * AsciiString.indexOfIgnoreCase(null, *, *) = -1
1695 * AsciiString.indexOfIgnoreCase(*, null, *) = -1
1696 * AsciiString.indexOfIgnoreCase("", "", 0) = 0
1697 * AsciiString.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
1698 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
1699 * AsciiString.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
1700 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
1701 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
1702 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
1703 * AsciiString.indexOfIgnoreCase("aabaabaa", "", 2) = 2
1704 * AsciiString.indexOfIgnoreCase("abc", "", 9) = -1
1705 * </pre>
1706 *
1707 * @param str the CharSequence to check, may be null
1708 * @param searchStr the CharSequence to find, may be null
1709 * @param startPos the start position, negative treated as zero
1710 * @return the first index of the search CharSequence (always ≥ startPos),
1711 * -1 if no match or {@code null} string input
1712 */
1713 public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) {
1714 if (str == null || searchStr == null) {
1715 return INDEX_NOT_FOUND;
1716 }
1717 if (startPos < 0) {
1718 startPos = 0;
1719 }
1720 int searchStrLen = searchStr.length();
1721 final int endLimit = str.length() - searchStrLen + 1;
1722 if (startPos > endLimit) {
1723 return INDEX_NOT_FOUND;
1724 }
1725 if (searchStrLen == 0) {
1726 return startPos;
1727 }
1728 for (int i = startPos; i < endLimit; i++) {
1729 if (regionMatches(str, true, i, searchStr, 0, searchStrLen)) {
1730 return i;
1731 }
1732 }
1733 return INDEX_NOT_FOUND;
1734 }
1735
1736 /**
1737 * <p>Case in-sensitive find of the first index within a CharSequence
1738 * from the specified position. This method optimized and works correctly for ASCII CharSequences only</p>
1739 *
1740 * <p>A {@code null} CharSequence will return {@code -1}.
1741 * A negative start position is treated as zero.
1742 * An empty ("") search CharSequence always matches.
1743 * A start position greater than the string length only matches
1744 * an empty search CharSequence.</p>
1745 *
1746 * <pre>
1747 * AsciiString.indexOfIgnoreCase(null, *, *) = -1
1748 * AsciiString.indexOfIgnoreCase(*, null, *) = -1
1749 * AsciiString.indexOfIgnoreCase("", "", 0) = 0
1750 * AsciiString.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
1751 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
1752 * AsciiString.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
1753 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
1754 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
1755 * AsciiString.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
1756 * AsciiString.indexOfIgnoreCase("aabaabaa", "", 2) = 2
1757 * AsciiString.indexOfIgnoreCase("abc", "", 9) = -1
1758 * </pre>
1759 *
1760 * @param str the CharSequence to check, may be null
1761 * @param searchStr the CharSequence to find, may be null
1762 * @param startPos the start position, negative treated as zero
1763 * @return the first index of the search CharSequence (always ≥ startPos),
1764 * -1 if no match or {@code null} string input
1765 */
1766 public static int indexOfIgnoreCaseAscii(final CharSequence str, final CharSequence searchStr, int startPos) {
1767 if (str == null || searchStr == null) {
1768 return INDEX_NOT_FOUND;
1769 }
1770 if (startPos < 0) {
1771 startPos = 0;
1772 }
1773 int searchStrLen = searchStr.length();
1774 final int endLimit = str.length() - searchStrLen + 1;
1775 if (startPos > endLimit) {
1776 return INDEX_NOT_FOUND;
1777 }
1778 if (searchStrLen == 0) {
1779 return startPos;
1780 }
1781 for (int i = startPos; i < endLimit; i++) {
1782 if (regionMatchesAscii(str, true, i, searchStr, 0, searchStrLen)) {
1783 return i;
1784 }
1785 }
1786 return INDEX_NOT_FOUND;
1787 }
1788
1789 /**
1790 * <p>Finds the first index in the {@code CharSequence} that matches the
1791 * specified character.</p>
1792 *
1793 * @param cs the {@code CharSequence} to be processed, not null
1794 * @param searchChar the char to be searched for
1795 * @param start the start index, negative starts at the string start
1796 * @return the index where the search char was found,
1797 * -1 if char {@code searchChar} is not found or {@code cs == null}
1798 */
1799 //-----------------------------------------------------------------------
1800 public static int indexOf(final CharSequence cs, final char searchChar, int start) {
1801 if (cs instanceof String) {
1802 return ((String) cs).indexOf(searchChar, start);
1803 } else if (cs instanceof AsciiString) {
1804 return ((AsciiString) cs).indexOf(searchChar, start);
1805 }
1806 if (cs == null) {
1807 return INDEX_NOT_FOUND;
1808 }
1809 final int sz = cs.length();
1810 for (int i = start < 0 ? 0 : start; i < sz; i++) {
1811 if (cs.charAt(i) == searchChar) {
1812 return i;
1813 }
1814 }
1815 return INDEX_NOT_FOUND;
1816 }
1817
1818 private static boolean equalsIgnoreCase(byte a, byte b) {
1819 return a == b || toLowerCase(a) == toLowerCase(b);
1820 }
1821
1822 private static boolean equalsIgnoreCase(char a, char b) {
1823 return a == b || toLowerCase(a) == toLowerCase(b);
1824 }
1825
1826 private static byte toLowerCase(byte b) {
1827 return isUpperCase(b) ? (byte) (b + 32) : b;
1828 }
1829
1830 /**
1831 * If the character is uppercase - converts the character to lowercase,
1832 * otherwise returns the character as it is. Only for ASCII characters.
1833 *
1834 * @return lowercase ASCII character equivalent
1835 */
1836 public static char toLowerCase(char c) {
1837 return isUpperCase(c) ? (char) (c + 32) : c;
1838 }
1839
1840 private static byte toUpperCase(byte b) {
1841 return isLowerCase(b) ? (byte) (b - 32) : b;
1842 }
1843
1844 private static boolean isLowerCase(byte value) {
1845 return value >= 'a' && value <= 'z';
1846 }
1847
1848 public static boolean isUpperCase(byte value) {
1849 return value >= 'A' && value <= 'Z';
1850 }
1851
1852 public static boolean isUpperCase(char value) {
1853 return value >= 'A' && value <= 'Z';
1854 }
1855
1856 public static byte c2b(char c) {
1857 return (byte) ((c > MAX_CHAR_VALUE) ? '?' : c);
1858 }
1859
1860 private static byte c2b0(char c) {
1861 return (byte) c;
1862 }
1863
1864 public static char b2c(byte b) {
1865 return (char) (b & 0xFF);
1866 }
1867 }
1868