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