View Javadoc
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.handler.codec;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.util.internal.EmptyArrays;
21  
22  import java.nio.ByteBuffer;
23  import java.nio.charset.Charset;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Comparator;
27  import java.util.List;
28  import java.util.regex.Pattern;
29  import java.util.regex.PatternSyntaxException;
30  
31  /**
32   * A string which has been encoded into a character encoding whose character always takes a single byte, similarly to
33   * ASCII. It internally keeps its content in a byte array unlike {@link String}, which uses a character array, for
34   * reduced memory footprint and faster data transfer from/to byte-based data structures such as a byte array and
35   * {@link ByteBuf}. It is often used in conjunction with {@link TextHeaders}.
36   */
37  public final class AsciiString implements CharSequence, Comparable<CharSequence> {
38  
39      public static final AsciiString EMPTY_STRING = new AsciiString("");
40      public static final Comparator<AsciiString> CASE_INSENSITIVE_ORDER = new Comparator<AsciiString>() {
41          @Override
42          public int compare(AsciiString o1, AsciiString o2) {
43              return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(o1, o2);
44          }
45      };
46      public static final Comparator<AsciiString> CASE_SENSITIVE_ORDER = new Comparator<AsciiString>() {
47          @Override
48          public int compare(AsciiString o1, AsciiString o2) {
49              return CHARSEQUENCE_CASE_SENSITIVE_ORDER.compare(o1, o2);
50          }
51      };
52  
53      public static final Comparator<CharSequence> CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator<CharSequence>() {
54          @Override
55          public int compare(CharSequence o1, CharSequence o2) {
56              if (o1 == o2) {
57                  return 0;
58              }
59  
60              AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null;
61              AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null;
62  
63              int result;
64              int length1 = o1.length();
65              int length2 = o2.length();
66              int minLength = Math.min(length1, length2);
67              if (a1 != null && a2 != null) {
68                  byte[] thisValue = a1.value;
69                  byte[] thatValue = a2.value;
70                  for (int i = 0; i < minLength; i++) {
71                      byte v1 = thisValue[i];
72                      byte v2 = thatValue[i];
73                      if (v1 == v2) {
74                          continue;
75                      }
76                      int c1 = toLowerCase(v1) & 0xFF;
77                      int c2 = toLowerCase(v2) & 0xFF;
78                      result = c1 - c2;
79                      if (result != 0) {
80                          return result;
81                      }
82                  }
83              } else if (a1 != null) {
84                  byte[] thisValue = a1.value;
85                  for (int i = 0; i < minLength; i++) {
86                      int c1 = toLowerCase(thisValue[i]) & 0xFF;
87                      int c2 = toLowerCase(o2.charAt(i));
88                      result = c1 - c2;
89                      if (result != 0) {
90                          return result;
91                      }
92                  }
93              } else if (a2 != null) {
94                  byte[] thatValue = a2.value;
95                  for (int i = 0; i < minLength; i++) {
96                      int c1 = toLowerCase(o1.charAt(i));
97                      int c2 = toLowerCase(thatValue[i]) & 0xFF;
98                      result = c1 - c2;
99                      if (result != 0) {
100                         return result;
101                     }
102                 }
103             } else {
104                 for (int i = 0; i < minLength; i++) {
105                     int c1 = toLowerCase(o1.charAt(i));
106                     int c2 = toLowerCase(o2.charAt(i));
107                     result = c1 - c2;
108                     if (result != 0) {
109                         return result;
110                     }
111                 }
112             }
113 
114             return length1 - length2;
115         }
116     };
117 
118     public static final Comparator<CharSequence> CHARSEQUENCE_CASE_SENSITIVE_ORDER = new Comparator<CharSequence>() {
119         @Override
120         public int compare(CharSequence o1, CharSequence o2) {
121             if (o1 == o2) {
122                 return 0;
123             }
124 
125             AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null;
126             AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null;
127 
128             int result;
129             int length1 = o1.length();
130             int length2 = o2.length();
131             int minLength = Math.min(length1, length2);
132             if (a1 != null && a2 != null) {
133                 byte[] thisValue = a1.value;
134                 byte[] thatValue = a2.value;
135                 for (int i = 0; i < minLength; i++) {
136                     byte v1 = thisValue[i];
137                     byte v2 = thatValue[i];
138                     result = v1 - v2;
139                     if (result != 0) {
140                         return result;
141                     }
142                 }
143             } else if (a1 != null) {
144                 byte[] thisValue = a1.value;
145                 for (int i = 0; i < minLength; i++) {
146                     int c1 = thisValue[i];
147                     int c2 = o2.charAt(i);
148                     result = c1 - c2;
149                     if (result != 0) {
150                         return result;
151                     }
152                 }
153             } else if (a2 != null) {
154                 byte[] thatValue = a2.value;
155                 for (int i = 0; i < minLength; i++) {
156                     int c1 = o1.charAt(i);
157                     int c2 = thatValue[i];
158                     result = c1 - c2;
159                     if (result != 0) {
160                         return result;
161                     }
162                 }
163             } else {
164                 for (int i = 0; i < minLength; i++) {
165                     int c1 = o1.charAt(i);
166                     int c2 = o2.charAt(i);
167                     result = c1 - c2;
168                     if (result != 0) {
169                         return result;
170                     }
171                 }
172             }
173 
174             return length1 - length2;
175         }
176     };
177 
178     /**
179      * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing
180      * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary
181      * {@link CharSequence}s into the same {@link TextHeaders}.
182      */
183     public static int caseInsensitiveHashCode(CharSequence value) {
184         if (value instanceof AsciiString) {
185             return value.hashCode();
186         }
187 
188         int hash = 0;
189         final int end = value.length();
190         for (int i = 0; i < end; i++) {
191             hash = hash * 31 ^ value.charAt(i) & 31;
192         }
193 
194         return hash;
195     }
196 
197     /**
198      * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit
199      * ASCII.
200      */
201     public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
202         if (a == b) {
203             return true;
204         }
205 
206         if (a instanceof AsciiString) {
207             AsciiString aa = (AsciiString) a;
208             return aa.equalsIgnoreCase(b);
209         }
210 
211         if (b instanceof AsciiString) {
212             AsciiString ab = (AsciiString) b;
213             return ab.equalsIgnoreCase(a);
214         }
215 
216         if (a == null || b == null) {
217             return false;
218         }
219 
220         return a.toString().equalsIgnoreCase(b.toString());
221     }
222 
223     /**
224      * Returns {@code true} if both {@link CharSequence}'s are equals. This only supports 8-bit ASCII.
225      */
226     public static boolean equals(CharSequence a, CharSequence b) {
227         if (a == b) {
228             return true;
229         }
230 
231         if (a instanceof AsciiString) {
232             AsciiString aa = (AsciiString) a;
233             return aa.equals(b);
234         }
235 
236         if (b instanceof AsciiString) {
237             AsciiString ab = (AsciiString) b;
238             return ab.equals(a);
239         }
240 
241         if (a == null || b == null) {
242             return false;
243         }
244 
245         return a.equals(b);
246     }
247 
248     public static byte[] getBytes(CharSequence v, Charset charset) {
249         if (v instanceof AsciiString) {
250             return ((AsciiString) v).array();
251         } else if (v instanceof String) {
252             return ((String) v).getBytes(charset);
253         } else if (v != null) {
254             final ByteBuf buf = Unpooled.copiedBuffer(v, charset);
255             try {
256                 if (buf.hasArray()) {
257                     return buf.array();
258                 } else {
259                     byte[] result = new byte[buf.readableBytes()];
260                     buf.readBytes(result);
261                     return result;
262                 }
263             } finally {
264                 buf.release();
265             }
266         }
267         return null;
268     }
269 
270     /**
271      * Returns an {@link AsciiString} containing the given character sequence. If the given string is already a
272      * {@link AsciiString}, just returns the same instance.
273      */
274     public static AsciiString of(CharSequence string) {
275         return string instanceof AsciiString ? (AsciiString) string : new AsciiString(string);
276     }
277 
278     private final byte[] value;
279     private String string;
280     private int hash;
281 
282     public AsciiString(byte[] value) {
283         this(value, true);
284     }
285 
286     public AsciiString(byte[] value, boolean copy) {
287         checkNull(value);
288         if (copy) {
289             this.value = value.clone();
290         } else {
291             this.value = value;
292         }
293     }
294 
295     public AsciiString(byte[] value, int start, int length) {
296         this(value, start, length, true);
297     }
298 
299     public AsciiString(byte[] value, int start, int length, boolean copy) {
300         checkNull(value);
301         if (start < 0 || start > value.length - length) {
302             throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
303                             + ") <= " + "value.length(" + value.length + ')');
304         }
305 
306         if (copy || start != 0 || length != value.length) {
307             this.value = Arrays.copyOfRange(value, start, start + length);
308         } else {
309             this.value = value;
310         }
311     }
312 
313     public AsciiString(char[] value) {
314         this(checkNull(value), 0, value.length);
315     }
316 
317     public AsciiString(char[] value, int start, int length) {
318         checkNull(value);
319         if (start < 0 || start > value.length - length) {
320             throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
321                             + ") <= " + "value.length(" + value.length + ')');
322         }
323 
324         this.value = new byte[length];
325         for (int i = 0, j = start; i < length; i++, j++) {
326             this.value[i] = c2b(value[j]);
327         }
328     }
329 
330     public AsciiString(CharSequence value) {
331         this(checkNull(value), 0, value.length());
332     }
333 
334     public AsciiString(CharSequence value, int start, int length) {
335         if (value == null) {
336             throw new NullPointerException("value");
337         }
338 
339         if (start < 0 || length < 0 || length > value.length() - start) {
340             throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
341                             + ") <= " + "value.length(" + value.length() + ')');
342         }
343 
344         this.value = new byte[length];
345         for (int i = 0; i < length; i++) {
346             this.value[i] = c2b(value.charAt(start + i));
347         }
348     }
349 
350     public AsciiString(ByteBuffer value) {
351         this(checkNull(value), value.position(), value.remaining());
352     }
353 
354     public AsciiString(ByteBuffer value, int start, int length) {
355         if (value == null) {
356             throw new NullPointerException("value");
357         }
358 
359         if (start < 0 || length > value.capacity() - start) {
360             throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
361                             + ") <= " + "value.capacity(" + value.capacity() + ')');
362         }
363 
364         if (value.hasArray()) {
365             int baseOffset = value.arrayOffset() + start;
366             this.value = Arrays.copyOfRange(value.array(), baseOffset, baseOffset + length);
367         } else {
368             this.value = new byte[length];
369             int oldPos = value.position();
370             value.get(this.value, 0, this.value.length);
371             value.position(oldPos);
372         }
373     }
374 
375     private static <T> T checkNull(T value) {
376         if (value == null) {
377             throw new NullPointerException("value");
378         }
379         return value;
380     }
381 
382     @Override
383     public int length() {
384         return value.length;
385     }
386 
387     @Override
388     public char charAt(int index) {
389         return (char) (byteAt(index) & 0xFF);
390     }
391 
392     public byte byteAt(int index) {
393         return value[index];
394     }
395 
396     public byte[] array() {
397         return value;
398     }
399 
400     public int arrayOffset() {
401         return 0;
402     }
403 
404     private static byte c2b(char c) {
405         if (c > 255) {
406             return '?';
407         }
408         return (byte) c;
409     }
410 
411     private static byte toLowerCase(byte b) {
412         if ('A' <= b && b <= 'Z') {
413             return (byte) (b + 32);
414         }
415         return b;
416     }
417 
418     private static char toLowerCase(char c) {
419         if ('A' <= c && c <= 'Z') {
420             return (char) (c + 32);
421         }
422         return c;
423     }
424 
425     private static byte toUpperCase(byte b) {
426         if ('a' <= b && b <= 'z') {
427             return (byte) (b - 32);
428         }
429         return b;
430     }
431 
432     /**
433      * Copies a range of characters into a new string.
434      *
435      * @param start the offset of the first character.
436      * @return a new string containing the characters from start to the end of the string.
437      * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
438      */
439     public AsciiString subSequence(int start) {
440         return subSequence(start, length());
441     }
442 
443     @Override
444     public AsciiString subSequence(int start, int end) {
445         if (start < 0 || start > end || end > length()) {
446             throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length("
447                             + length() + ')');
448         }
449 
450         final byte[] value = this.value;
451         if (start == 0 && end == value.length) {
452             return this;
453         }
454 
455         if (end == start) {
456             return EMPTY_STRING;
457         }
458 
459         return new AsciiString(value, start, end - start, false);
460     }
461 
462     @Override
463     public int hashCode() {
464         int hash = this.hash;
465         final byte[] value = this.value;
466         if (hash != 0 || value.length == 0) {
467             return hash;
468         }
469 
470         for (int i = 0; i < value.length; ++i) {
471             hash = hash * 31 ^ value[i] & 31;
472         }
473 
474         return this.hash = hash;
475     }
476 
477     @Override
478     public boolean equals(Object obj) {
479         if (!(obj instanceof AsciiString)) {
480             return false;
481         }
482 
483         if (this == obj) {
484             return true;
485         }
486 
487         AsciiString that = (AsciiString) obj;
488         int thisHash = hashCode();
489         int thatHash = that.hashCode();
490         if (thisHash != thatHash || length() != that.length()) {
491             return false;
492         }
493 
494         byte[] thisValue = value;
495         byte[] thatValue = that.value;
496         int end = thisValue.length;
497         for (int i = 0, j = 0; i < end; i++, j++) {
498             if (thisValue[i] != thatValue[j]) {
499                 return false;
500             }
501         }
502 
503         return true;
504     }
505 
506     @Override
507     @SuppressWarnings("deprecation")
508     public String toString() {
509         String string = this.string;
510         if (string != null) {
511             return string;
512         }
513 
514         final byte[] value = this.value;
515         return this.string = new String(value, 0, 0, value.length);
516     }
517 
518     @SuppressWarnings("deprecation")
519     public String toString(int start, int end) {
520         final byte[] value = this.value;
521         if (start == 0 && end == value.length) {
522             return toString();
523         }
524 
525         int length = end - start;
526         if (length == 0) {
527             return "";
528         }
529 
530         return new String(value, 0, start, length);
531     }
532 
533     /**
534      * Compares the specified string to this string using the ASCII values of the characters. Returns 0 if the strings
535      * contain the same characters in the same order. Returns a negative integer if the first non-equal character in
536      * this string has an ASCII value which is less than the ASCII value of the character at the same position in the
537      * specified string, or if this string is a prefix of the specified string. Returns a positive integer if the first
538      * non-equal character in this string has a ASCII value which is greater than the ASCII value of the character at
539      * the same position in the specified string, or if the specified string is a prefix of this string.
540      *
541      * @param string the string to compare.
542      * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a
543      *         positive integer if this string is after the specified string.
544      * @throws NullPointerException if {@code string} is {@code null}.
545      */
546     @Override
547     public int compareTo(CharSequence string) {
548         if (this == string) {
549             return 0;
550         }
551 
552         int result;
553         int length1 = length();
554         int length2 = string.length();
555         int minLength = Math.min(length1, length2);
556         byte[] value = this.value;
557         for (int i = 0, j = 0; j < minLength; i++, j++) {
558             result = (value[i] & 0xFF) - string.charAt(j);
559             if (result != 0) {
560                 return result;
561             }
562         }
563 
564         return length1 - length2;
565     }
566 
567     /**
568      * Compares the specified string to this string using the ASCII values of the characters, ignoring case differences.
569      * Returns 0 if the strings contain the same characters in the same order. Returns a negative integer if the first
570      * non-equal character in this string has an ASCII value which is less than the ASCII value of the character at the
571      * same position in the specified string, or if this string is a prefix of the specified string. Returns a positive
572      * integer if the first non-equal character in this string has an ASCII value which is greater than the ASCII value
573      * of the character at the same position in the specified string, or if the specified string is a prefix of this
574      * string.
575      *
576      * @param string the string to compare.
577      * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a
578      *         positive integer if this string is after the specified string.
579      * @throws NullPointerException if {@code string} is {@code null}.
580      */
581     public int compareToIgnoreCase(CharSequence string) {
582         return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(this, string);
583     }
584 
585     /**
586      * Concatenates this string and the specified string.
587      *
588      * @param string the string to concatenate
589      * @return a new string which is the concatenation of this string and the specified string.
590      */
591     public AsciiString concat(CharSequence string) {
592         int thisLen = length();
593         int thatLen = string.length();
594         if (thatLen == 0) {
595             return this;
596         }
597 
598         if (string instanceof AsciiString) {
599             AsciiString that = (AsciiString) string;
600             if (isEmpty()) {
601                 return that;
602             }
603 
604             byte[] newValue = Arrays.copyOf(value, thisLen + thatLen);
605             System.arraycopy(that.value, 0, newValue, thisLen, thatLen);
606 
607             return new AsciiString(newValue, false);
608         }
609 
610         if (isEmpty()) {
611             return new AsciiString(string);
612         }
613 
614         int newLen = thisLen + thatLen;
615         byte[] newValue = Arrays.copyOf(value, newLen);
616         for (int i = thisLen, j = 0; i < newLen; i++, j++) {
617             newValue[i] = c2b(string.charAt(j));
618         }
619 
620         return new AsciiString(newValue, false);
621     }
622 
623     /**
624      * Compares the specified string to this string to determine if the specified string is a suffix.
625      *
626      * @param suffix the suffix to look for.
627      * @return {@code true} if the specified string is a suffix of this string, {@code false} otherwise.
628      * @throws NullPointerException if {@code suffix} is {@code null}.
629      */
630     public boolean endsWith(CharSequence suffix) {
631         int suffixLen = suffix.length();
632         return regionMatches(length() - suffixLen, suffix, 0, suffixLen);
633     }
634 
635     /**
636      * Compares the specified string to this string ignoring the case of the characters and returns true if they are
637      * equal.
638      *
639      * @param string the string to compare.
640      * @return {@code true} if the specified string is equal to this string, {@code false} otherwise.
641      */
642     public boolean equalsIgnoreCase(CharSequence string) {
643         if (string == this) {
644             return true;
645         }
646 
647         if (string == null) {
648             return false;
649         }
650 
651         final byte[] value = this.value;
652         final int thisLen = value.length;
653         final int thatLen = string.length();
654         if (thisLen != thatLen) {
655             return false;
656         }
657 
658         for (int i = 0; i < thisLen; i++) {
659             char c1 = (char) (value[i] & 0xFF);
660             char c2 = string.charAt(i);
661             if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
662                 return false;
663             }
664         }
665         return true;
666     }
667 
668     /**
669      * Converts this string to a byte array using the ASCII encoding.
670      *
671      * @return the byte array encoding of this string.
672      */
673     public byte[] toByteArray() {
674         return toByteArray(0, length());
675     }
676 
677     /**
678      * Converts this string to a byte array using the ASCII encoding.
679      *
680      * @return the byte array encoding of this string.
681      */
682     public byte[] toByteArray(int start, int end) {
683         return Arrays.copyOfRange(value, start, end);
684     }
685 
686     /**
687      * Copies the characters in this string to a character array.
688      *
689      * @return a character array containing the characters of this string.
690      */
691     public char[] toCharArray() {
692         return toCharArray(0, length());
693     }
694 
695     /**
696      * Copies the characters in this string to a character array.
697      *
698      * @return a character array containing the characters of this string.
699      */
700     public char[] toCharArray(int start, int end) {
701         int length = end - start;
702         if (length == 0) {
703             return EmptyArrays.EMPTY_CHARS;
704         }
705 
706         final byte[] value = this.value;
707         final char[] buffer = new char[length];
708         for (int i = 0, j = start; i < length; i++, j++) {
709             buffer[i] = (char) (value[j] & 0xFF);
710         }
711         return buffer;
712     }
713 
714     /**
715      * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}.
716      *
717      * @param srcIdx the starting offset of characters to copy.
718      * @param dst the destination byte array.
719      * @param dstIdx the starting offset in the destination byte array.
720      * @param length the number of characters to copy.
721      */
722     public void copy(int srcIdx, ByteBuf dst, int dstIdx, int length) {
723         if (dst == null) {
724             throw new NullPointerException("dst");
725         }
726 
727         final byte[] value = this.value;
728         final int thisLen = value.length;
729 
730         if (srcIdx < 0 || length > thisLen - srcIdx) {
731             throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
732                             + length + ") <= srcLen(" + thisLen + ')');
733         }
734 
735         dst.setBytes(dstIdx, value, srcIdx, length);
736     }
737 
738     /**
739      * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}.
740      *
741      * @param srcIdx the starting offset of characters to copy.
742      * @param dst the destination byte array.
743      * @param length the number of characters to copy.
744      */
745     public void copy(int srcIdx, ByteBuf dst, int length) {
746         if (dst == null) {
747             throw new NullPointerException("dst");
748         }
749 
750         final byte[] value = this.value;
751         final int thisLen = value.length;
752 
753         if (srcIdx < 0 || length > thisLen - srcIdx) {
754             throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
755                             + length + ") <= srcLen(" + thisLen + ')');
756         }
757 
758         dst.writeBytes(value, srcIdx, length);
759     }
760 
761     /**
762      * Copies the content of this string to a byte array.
763      *
764      * @param srcIdx the starting offset of characters to copy.
765      * @param dst the destination byte array.
766      * @param dstIdx the starting offset in the destination byte array.
767      * @param length the number of characters to copy.
768      */
769     public void copy(int srcIdx, byte[] dst, int dstIdx, int length) {
770         if (dst == null) {
771             throw new NullPointerException("dst");
772         }
773 
774         final byte[] value = this.value;
775         final int thisLen = value.length;
776 
777         if (srcIdx < 0 || length > thisLen - srcIdx) {
778             throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
779                             + length + ") <= srcLen(" + thisLen + ')');
780         }
781 
782         System.arraycopy(value, srcIdx, dst, dstIdx, length);
783     }
784 
785     /**
786      * Copied the content of this string to a character array.
787      *
788      * @param srcIdx the starting offset of characters to copy.
789      * @param dst the destination character array.
790      * @param dstIdx the starting offset in the destination byte array.
791      * @param length the number of characters to copy.
792      */
793     public void copy(int srcIdx, char[] dst, int dstIdx, int length) {
794         if (dst == null) {
795             throw new NullPointerException("dst");
796         }
797 
798         final byte[] value = this.value;
799         final int thisLen = value.length;
800 
801         if (srcIdx < 0 || length > thisLen - srcIdx) {
802             throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
803                             + length + ") <= srcLen(" + thisLen + ')');
804         }
805 
806         final int dstEnd = dstIdx + length;
807         for (int i = srcIdx, j = dstIdx; j < dstEnd; i++, j++) {
808             dst[j] = (char) (value[i] & 0xFF);
809         }
810     }
811 
812     /**
813      * Searches in this string for the first index of the specified character. The search for the character starts at
814      * the beginning and moves towards the end of this string.
815      *
816      * @param c the character to find.
817      * @return the index in this string of the specified character, -1 if the character isn't found.
818      */
819     public int indexOf(int c) {
820         return indexOf(c, 0);
821     }
822 
823     /**
824      * Searches in this string for the index of the specified character. The search for the character starts at the
825      * specified offset and moves towards the end of this string.
826      *
827      * @param c the character to find.
828      * @param start the starting offset.
829      * @return the index in this string of the specified character, -1 if the character isn't found.
830      */
831     public int indexOf(int c, int start) {
832         final byte[] value = this.value;
833         final int length = value.length;
834         if (start < length) {
835             if (start < 0) {
836                 start = 0;
837             }
838 
839             for (int i = start; i < length; i++) {
840                 if ((value[i] & 0xFF) == c) {
841                     return i;
842                 }
843             }
844         }
845         return -1;
846     }
847 
848     /**
849      * Searches in this string for the first index of the specified string. The search for the string starts at the
850      * beginning and moves towards the end of this string.
851      *
852      * @param string the string to find.
853      * @return the index of the first character of the specified string in this string, -1 if the specified string is
854      *         not a substring.
855      * @throws NullPointerException if {@code string} is {@code null}.
856      */
857     public int indexOf(CharSequence string) {
858         return indexOf(string, 0);
859     }
860 
861     /**
862      * Searches in this string for the index of the specified string. The search for the string starts at the specified
863      * offset and moves towards the end of this string.
864      *
865      * @param subString the string to find.
866      * @param start the starting offset.
867      * @return the index of the first character of the specified string in this string, -1 if the specified string is
868      *         not a substring.
869      * @throws NullPointerException if {@code subString} is {@code null}.
870      */
871     public int indexOf(CharSequence subString, int start) {
872         if (start < 0) {
873             start = 0;
874         }
875 
876         final byte[] value = this.value;
877         final int thisLen = value.length;
878 
879         int subCount = subString.length();
880         if (subCount <= 0) {
881             return start < thisLen ? start : thisLen;
882         }
883         if (subCount > thisLen - start) {
884             return -1;
885         }
886 
887         char firstChar = subString.charAt(0);
888         for (;;) {
889             int i = indexOf(firstChar, start);
890             if (i == -1 || subCount + i > thisLen) {
891                 return -1; // handles subCount > count || start >= count
892             }
893             int o1 = i, o2 = 0;
894             while (++o2 < subCount && (value[++o1] & 0xFF) == subString.charAt(o2)) {
895                 // Intentionally empty
896             }
897             if (o2 == subCount) {
898                 return i;
899             }
900             start = i + 1;
901         }
902     }
903 
904     /**
905      * Searches in this string for the last index of the specified character. The search for the character starts at the
906      * end and moves towards the beginning of this string.
907      *
908      * @param c the character to find.
909      * @return the index in this string of the specified character, -1 if the character isn't found.
910      */
911     public int lastIndexOf(int c) {
912         return lastIndexOf(c, length() - 1);
913     }
914 
915     /**
916      * Searches in this string for the index of the specified character. The search for the character starts at the
917      * specified offset and moves towards the beginning of this string.
918      *
919      * @param c the character to find.
920      * @param start the starting offset.
921      * @return the index in this string of the specified character, -1 if the character isn't found.
922      */
923     public int lastIndexOf(int c, int start) {
924         if (start >= 0) {
925             final byte[] value = this.value;
926             final int length = value.length;
927             if (start >= length) {
928                 start = length - 1;
929             }
930             for (int i = start; i >= 0; --i) {
931                 if ((value[i] & 0xFF) == c) {
932                     return i;
933                 }
934             }
935         }
936         return -1;
937     }
938 
939     /**
940      * Searches in this string for the last index of the specified string. The search for the string starts at the end
941      * and moves towards the beginning of this string.
942      *
943      * @param string the string to find.
944      * @return the index of the first character of the specified string in this string, -1 if the specified string is
945      *         not a substring.
946      * @throws NullPointerException if {@code string} is {@code null}.
947      */
948     public int lastIndexOf(CharSequence string) {
949         // Use count instead of count - 1 so lastIndexOf("") answers count
950         return lastIndexOf(string, length());
951     }
952 
953     /**
954      * Searches in this string for the index of the specified string. The search for the string starts at the specified
955      * offset and moves towards the beginning of this string.
956      *
957      * @param subString the string to find.
958      * @param start the starting offset.
959      * @return the index of the first character of the specified string in this string , -1 if the specified string is
960      *         not a substring.
961      * @throws NullPointerException if {@code subString} is {@code null}.
962      */
963     public int lastIndexOf(CharSequence subString, int start) {
964         final byte[] value = this.value;
965         final int thisLen = value.length;
966         final int subCount = subString.length();
967 
968         if (subCount > thisLen || start < 0) {
969             return -1;
970         }
971 
972         if (subCount <= 0) {
973             return start < thisLen ? start : thisLen;
974         }
975 
976         start = Math.min(start, thisLen - subCount);
977 
978         // count and subCount are both >= 1
979         char firstChar = subString.charAt(0);
980         for (;;) {
981             int i = lastIndexOf(firstChar, start);
982             if (i == -1) {
983                 return -1;
984             }
985             int o1 = i, o2 = 0;
986             while (++o2 < subCount && (value[++o1] & 0xFF) == subString.charAt(o2)) {
987                 // Intentionally empty
988             }
989             if (o2 == subCount) {
990                 return i;
991             }
992             start = i - 1;
993         }
994     }
995 
996     /**
997      * Answers if the size of this String is zero.
998      *
999      * @return true if the size of this String is zero, false otherwise
1000      */
1001     public boolean isEmpty() {
1002         return value.length == 0;
1003     }
1004 
1005     /**
1006      * Compares the specified string to this string and compares the specified range of characters to determine if they
1007      * are the same.
1008      *
1009      * @param thisStart the starting offset in this string.
1010      * @param string the string to compare.
1011      * @param start the starting offset in the specified string.
1012      * @param length the number of characters to compare.
1013      * @return {@code true} if the ranges of characters are equal, {@code false} otherwise
1014      * @throws NullPointerException if {@code string} is {@code null}.
1015      */
1016     public boolean regionMatches(int thisStart, CharSequence string, int start, int length) {
1017         if (string == null) {
1018             throw new NullPointerException("string");
1019         }
1020 
1021         if (start < 0 || string.length() - start < length) {
1022             return false;
1023         }
1024 
1025         final byte[] value = this.value;
1026         final int thisLen = value.length;
1027         if (thisStart < 0 || thisLen - thisStart < length) {
1028             return false;
1029         }
1030 
1031         if (length <= 0) {
1032             return true;
1033         }
1034 
1035         final int thisEnd = thisStart + length;
1036         for (int i = thisStart, j = start; i < thisEnd; i++, j++) {
1037             if ((value[i] & 0xFF) != string.charAt(j)) {
1038                 return false;
1039             }
1040         }
1041         return true;
1042     }
1043 
1044     /**
1045      * Compares the specified string to this string and compares the specified range of characters to determine if they
1046      * are the same. When ignoreCase is true, the case of the characters is ignored during the comparison.
1047      *
1048      * @param ignoreCase specifies if case should be ignored.
1049      * @param thisStart the starting offset in this string.
1050      * @param string the string to compare.
1051      * @param start the starting offset in the specified string.
1052      * @param length the number of characters to compare.
1053      * @return {@code true} if the ranges of characters are equal, {@code false} otherwise.
1054      * @throws NullPointerException if {@code string} is {@code null}.
1055      */
1056     public boolean regionMatches(boolean ignoreCase, int thisStart, CharSequence string, int start, int length) {
1057         if (!ignoreCase) {
1058             return regionMatches(thisStart, string, start, length);
1059         }
1060 
1061         if (string == null) {
1062             throw new NullPointerException("string");
1063         }
1064 
1065         final byte[] value = this.value;
1066         final int thisLen = value.length;
1067         if (thisStart < 0 || length > thisLen - thisStart) {
1068             return false;
1069         }
1070         if (start < 0 || length > string.length() - start) {
1071             return false;
1072         }
1073 
1074         int thisEnd = thisStart + length;
1075         while (thisStart < thisEnd) {
1076             char c1 = (char) (value[thisStart++] & 0xFF);
1077             char c2 = string.charAt(start++);
1078             if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
1079                 return false;
1080             }
1081         }
1082         return true;
1083     }
1084 
1085     /**
1086      * Copies this string replacing occurrences of the specified character with another character.
1087      *
1088      * @param oldChar the character to replace.
1089      * @param newChar the replacement character.
1090      * @return a new string with occurrences of oldChar replaced by newChar.
1091      */
1092     public AsciiString replace(char oldChar, char newChar) {
1093         int index = indexOf(oldChar, 0);
1094         if (index == -1) {
1095             return this;
1096         }
1097 
1098         final byte[] value = this.value;
1099         final int count = value.length;
1100         byte[] buffer = new byte[count];
1101         for (int i = 0, j = 0; i < value.length; i++, j++) {
1102             byte b = value[i];
1103             if ((char) (b & 0xFF) == oldChar) {
1104                 b = (byte) newChar;
1105             }
1106             buffer[j] = b;
1107         }
1108 
1109         return new AsciiString(buffer, false);
1110     }
1111 
1112     /**
1113      * Compares the specified string to this string to determine if the specified string is a prefix.
1114      *
1115      * @param prefix the string to look for.
1116      * @return {@code true} if the specified string is a prefix of this string, {@code false} otherwise
1117      * @throws NullPointerException if {@code prefix} is {@code null}.
1118      */
1119     public boolean startsWith(CharSequence prefix) {
1120         return startsWith(prefix, 0);
1121     }
1122 
1123     /**
1124      * Compares the specified string to this string, starting at the specified offset, to determine if the specified
1125      * string is a prefix.
1126      *
1127      * @param prefix the string to look for.
1128      * @param start the starting offset.
1129      * @return {@code true} if the specified string occurs in this string at the specified offset, {@code false}
1130      *         otherwise.
1131      * @throws NullPointerException if {@code prefix} is {@code null}.
1132      */
1133     public boolean startsWith(CharSequence prefix, int start) {
1134         return regionMatches(start, prefix, 0, prefix.length());
1135     }
1136 
1137     /**
1138      * Converts the characters in this string to lowercase, using the default Locale.
1139      *
1140      * @return a new string containing the lowercase characters equivalent to the characters in this string.
1141      */
1142     public AsciiString toLowerCase() {
1143         boolean lowercased = true;
1144         final byte[] value = this.value;
1145         int i, j;
1146         for (i = 0; i < value.length; ++i) {
1147             byte b = value[i];
1148             if (b >= 'A' && b <= 'Z') {
1149                 lowercased = false;
1150                 break;
1151             }
1152         }
1153 
1154         // Check if this string does not contain any uppercase characters.
1155         if (lowercased) {
1156             return this;
1157         }
1158 
1159         final int length = value.length;
1160         final byte[] newValue = new byte[length];
1161         for (i = 0, j = 0; i < length; ++i, ++j) {
1162             newValue[i] = toLowerCase(value[j]);
1163         }
1164 
1165         return new AsciiString(newValue, false);
1166     }
1167 
1168     /**
1169      * Converts the characters in this string to uppercase, using the default Locale.
1170      *
1171      * @return a new string containing the uppercase characters equivalent to the characters in this string.
1172      */
1173     public AsciiString toUpperCase() {
1174         final byte[] value = this.value;
1175         boolean uppercased = true;
1176         int i, j;
1177         for (i = 0; i < value.length; ++i) {
1178             byte b = value[i];
1179             if (b >= 'a' && b <= 'z') {
1180                 uppercased = false;
1181                 break;
1182             }
1183         }
1184 
1185         // Check if this string does not contain any lowercase characters.
1186         if (uppercased) {
1187             return this;
1188         }
1189 
1190         final int length = value.length;
1191         final byte[] newValue = new byte[length];
1192         for (i = 0, j = 0; i < length; ++i, ++j) {
1193             newValue[i] = toUpperCase(value[j]);
1194         }
1195 
1196         return new AsciiString(newValue, false);
1197     }
1198 
1199     /**
1200      * Copies this string removing white space characters from the beginning and end of the string.
1201      *
1202      * @return a new string with characters {@code <= \\u0020} removed from the beginning and the end.
1203      */
1204     public AsciiString trim() {
1205         final byte[] value = this.value;
1206         int start = 0, last = value.length;
1207         int end = last;
1208         while (start <= end && value[start] <= ' ') {
1209             start++;
1210         }
1211         while (end >= start && value[end] <= ' ') {
1212             end--;
1213         }
1214         if (start == 0 && end == last) {
1215             return this;
1216         }
1217         return new AsciiString(value, start, end - start + 1, false);
1218     }
1219 
1220     /**
1221      * Compares a {@code CharSequence} to this {@code String} to determine if their contents are equal.
1222      *
1223      * @param cs the character sequence to compare to.
1224      * @return {@code true} if equal, otherwise {@code false}
1225      */
1226     public boolean contentEquals(CharSequence cs) {
1227         if (cs == null) {
1228             throw new NullPointerException();
1229         }
1230 
1231         int length1 = length();
1232         int length2 = cs.length();
1233         if (length1 != length2) {
1234             return false;
1235         }
1236 
1237         if (length1 == 0 && length2 == 0) {
1238             return true; // since both are empty strings
1239         }
1240 
1241         return regionMatches(0, cs, 0, length2);
1242     }
1243 
1244     /**
1245      * Determines whether this string matches a given regular expression.
1246      *
1247      * @param expr the regular expression to be matched.
1248      * @return {@code true} if the expression matches, otherwise {@code false}.
1249      * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid.
1250      * @throws NullPointerException if {@code expr} is {@code null}.
1251      */
1252     public boolean matches(String expr) {
1253         return Pattern.matches(expr, this);
1254     }
1255 
1256     /**
1257      * Splits this string using the supplied regular expression {@code expr}. The parameter {@code max} controls the
1258      * behavior how many times the pattern is applied to the string.
1259      *
1260      * @param expr the regular expression used to divide the string.
1261      * @param max the number of entries in the resulting array.
1262      * @return an array of Strings created by separating the string along matches of the regular expression.
1263      * @throws NullPointerException if {@code expr} is {@code null}.
1264      * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid.
1265      * @see Pattern#split(CharSequence, int)
1266      */
1267     public AsciiString[] split(String expr, int max) {
1268         return toAsciiStringArray(Pattern.compile(expr).split(this, max));
1269     }
1270 
1271     private static AsciiString[] toAsciiStringArray(String[] jdkResult) {
1272         AsciiString[] res = new AsciiString[jdkResult.length];
1273         for (int i = 0; i < jdkResult.length; i++) {
1274             res[i] = new AsciiString(jdkResult[i]);
1275         }
1276         return res;
1277     }
1278 
1279     /**
1280      * Splits the specified {@link String} with the specified delimiter..
1281      */
1282     public AsciiString[] split(char delim) {
1283         final List<AsciiString> res = new ArrayList<AsciiString>();
1284 
1285         int start = 0;
1286         final byte[] value = this.value;
1287         final int length = value.length;
1288         for (int i = start; i < length; i++) {
1289             if (charAt(i) == delim) {
1290                 if (start == i) {
1291                     res.add(EMPTY_STRING);
1292                 } else {
1293                     res.add(new AsciiString(value, start, i - start, false));
1294                 }
1295                 start = i + 1;
1296             }
1297         }
1298 
1299         if (start == 0) { // If no delimiter was found in the value
1300             res.add(this);
1301         } else {
1302             if (start != length) {
1303                 // Add the last element if it's not empty.
1304                 res.add(new AsciiString(value, start, length - start, false));
1305             } else {
1306                 // Truncate trailing empty elements.
1307                 for (int i = res.size() - 1; i >= 0; i--) {
1308                     if (res.get(i).isEmpty()) {
1309                         res.remove(i);
1310                     } else {
1311                         break;
1312                     }
1313                 }
1314             }
1315         }
1316 
1317         return res.toArray(new AsciiString[res.size()]);
1318     }
1319 
1320     /**
1321      * Determines if this {@code String} contains the sequence of characters in the {@code CharSequence} passed.
1322      *
1323      * @param cs the character sequence to search for.
1324      * @return {@code true} if the sequence of characters are contained in this string, otherwise {@code false}.
1325      */
1326     public boolean contains(CharSequence cs) {
1327         if (cs == null) {
1328             throw new NullPointerException();
1329         }
1330         return indexOf(cs) >= 0;
1331     }
1332 
1333     public int parseInt() {
1334         return parseInt(0, length(), 10);
1335     }
1336 
1337     public int parseInt(int radix) {
1338         return parseInt(0, length(), radix);
1339     }
1340 
1341     public int parseInt(int start, int end) {
1342         return parseInt(start, end, 10);
1343     }
1344 
1345     public int parseInt(int start, int end, int radix) {
1346         if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
1347             throw new NumberFormatException();
1348         }
1349 
1350         if (start == end) {
1351             throw new NumberFormatException();
1352         }
1353 
1354         int i = start;
1355         boolean negative = charAt(i) == '-';
1356         if (negative && ++i == end) {
1357             throw new NumberFormatException(subSequence(start, end).toString());
1358         }
1359 
1360         return parseInt(i, end, radix, negative);
1361     }
1362 
1363     private int parseInt(int start, int end, int radix, boolean negative) {
1364         final byte[] value = this.value;
1365         int max = Integer.MIN_VALUE / radix;
1366         int result = 0;
1367         int offset = start;
1368         while (offset < end) {
1369             int digit = Character.digit((char) (value[offset++] & 0xFF), radix);
1370             if (digit == -1) {
1371                 throw new NumberFormatException(subSequence(start, end).toString());
1372             }
1373             if (max > result) {
1374                 throw new NumberFormatException(subSequence(start, end).toString());
1375             }
1376             int next = result * radix - digit;
1377             if (next > result) {
1378                 throw new NumberFormatException(subSequence(start, end).toString());
1379             }
1380             result = next;
1381         }
1382         if (!negative) {
1383             result = -result;
1384             if (result < 0) {
1385                 throw new NumberFormatException(subSequence(start, end).toString());
1386             }
1387         }
1388         return result;
1389     }
1390 
1391     public long parseLong() {
1392         return parseLong(0, length(), 10);
1393     }
1394 
1395     public long parseLong(int radix) {
1396         return parseLong(0, length(), radix);
1397     }
1398 
1399     public long parseLong(int start, int end) {
1400         return parseLong(start, end, 10);
1401     }
1402 
1403     public long parseLong(int start, int end, int radix) {
1404         if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
1405             throw new NumberFormatException();
1406         }
1407 
1408         if (start == end) {
1409             throw new NumberFormatException();
1410         }
1411 
1412         int i = start;
1413         boolean negative = charAt(i) == '-';
1414         if (negative && ++i == end) {
1415             throw new NumberFormatException(subSequence(start, end).toString());
1416         }
1417 
1418         return parseLong(i, end, radix, negative);
1419     }
1420 
1421     private long parseLong(int start, int end, int radix, boolean negative) {
1422         final byte[] value = this.value;
1423         long max = Long.MIN_VALUE / radix;
1424         long result = 0;
1425         int offset = start;
1426         while (offset < end) {
1427             int digit = Character.digit((char) (value[offset++] & 0xFF), radix);
1428             if (digit == -1) {
1429                 throw new NumberFormatException(subSequence(start, end).toString());
1430             }
1431             if (max > result) {
1432                 throw new NumberFormatException(subSequence(start, end).toString());
1433             }
1434             long next = result * radix - digit;
1435             if (next > result) {
1436                 throw new NumberFormatException(subSequence(start, end).toString());
1437             }
1438             result = next;
1439         }
1440         if (!negative) {
1441             result = -result;
1442             if (result < 0) {
1443                 throw new NumberFormatException(subSequence(start, end).toString());
1444             }
1445         }
1446         return result;
1447     }
1448 
1449     public short parseShort() {
1450         return parseShort(0, length(), 10);
1451     }
1452 
1453     public short parseShort(int radix) {
1454         return parseShort(0, length(), radix);
1455     }
1456 
1457     public short parseShort(int start, int end) {
1458         return parseShort(start, end, 10);
1459     }
1460 
1461     public short parseShort(int start, int end, int radix) {
1462         int intValue = parseInt(start, end, radix);
1463         short result = (short) intValue;
1464         if (result != intValue) {
1465             throw new NumberFormatException(subSequence(start, end).toString());
1466         }
1467         return result;
1468     }
1469 
1470     public float parseFloat() {
1471         return parseFloat(0, length());
1472     }
1473 
1474     public float parseFloat(int start, int end) {
1475         return Float.parseFloat(toString(start, end));
1476     }
1477 
1478     public double parseDouble() {
1479         return parseDouble(0, length());
1480     }
1481 
1482     public double parseDouble(int start, int end) {
1483         return Double.parseDouble(toString(start, end));
1484     }
1485 }