/* * JBoss, Home of Professional Open Source * * Copyright 2009 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xnio; import java.io.IOException; import java.io.ObjectInputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.io.UnsupportedEncodingException; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.ByteBuffer; import static java.lang.Integer.signum; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.System.arraycopy; import static org.xnio._private.Messages.msg; /** * An immutable string of bytes. Since instances of this class are guaranteed to be immutable, they are * safe to use as {@link Option} values and in an {@link OptionMap}. Some operations can treat this byte string * as an ASCII- or ISO-8858-1-encoded character string. */ public final class ByteString implements Comparable<ByteString>, Serializable, CharSequence { private static final long serialVersionUID = -5998895518404718196L; private final byte[] bytes; private final int offs; private final int len; private transient int hashCode; private transient int hashCodeIgnoreCase; private ByteString(final byte[] bytes, final int offs, final int len) { this.bytes = bytes; this.offs = offs; this.len = len; if (offs < 0) { throw msg.parameterOutOfRange("offs"); } if (len < 0) { throw msg.parameterOutOfRange("len"); } if (offs + len > bytes.length) { throw msg.parameterOutOfRange("offs"); } } private static int calcHashCode(final byte[] bytes, final int offs, final int len) { int hc = 31; final int end = offs + len; for (int i = offs; i < end; i++) { hc = (hc << 5) - hc + (bytes[i] & 0xff); } return hc == 0 ? Integer.MAX_VALUE : hc; } private static int calcHashCodeIgnoreCase(final byte[] bytes, final int offs, final int len) { int hc = 31; final int end = offs + len; for (int i = offs; i < end; i++) { hc = (hc << 5) - hc + (upperCase(bytes[i]) & 0xff); } return hc == 0 ? Integer.MAX_VALUE : hc; } private ByteString(final byte[] bytes) { this(bytes, 0, bytes.length); } /** * Create a byte string of the given literal bytes. The given array is copied. * * @param bytes the bytes * @return the byte string */ public static ByteString of(byte... bytes) { return new ByteString(bytes.clone()); } /** * Create a byte string from the given array segment. * * @param b the byte array * @param offs the offset into the array * @param len the number of bytes to copy * @return the new byte string */ public static ByteString copyOf(byte[] b, int offs, int len) { return new ByteString(Arrays.copyOfRange(b, offs, offs + len)); } /** * Get a byte string from the bytes of a character string. * * @param str the character string * @param charset the character set to use * @return the byte string * @throws UnsupportedEncodingException if the encoding is not supported */ public static ByteString getBytes(String str, String charset) throws UnsupportedEncodingException { return new ByteString(str.getBytes(charset)); } /** * Get a byte string from the bytes of a character string. * * @param str the character string * @param charset the character set to use * @return the byte string */ public static ByteString getBytes(String str, Charset charset) { return new ByteString(str.getBytes(charset)); } /** * Get a byte string from the bytes of the character string. The string must be a Latin-1 string. * * @param str the character string * @return the byte string */ public static ByteString getBytes(String str) { final int length = str.length(); return new ByteString(getStringBytes(false, new byte[length], 0, str, 0, length), 0, length); } /** * Get a byte string from all remaining bytes of a ByteBuffer. * * @param buffer the buffer * @return the byte string */ public static ByteString getBytes(ByteBuffer buffer) { return new ByteString(Buffers.take(buffer)); } /** * Get a byte string from a ByteBuffer. * * @param buffer the buffer * @param length the number of bytes to get * @return the byte string */ public static ByteString getBytes(ByteBuffer buffer, int length) { return new ByteString(Buffers.take(buffer, length)); } /** * Get a copy of the bytes of this ByteString. * * @return the copy */ public byte[] getBytes() { return Arrays.copyOfRange(bytes, offs, len); } /** * Copy the bytes of this ByteString into the destination array. If the array is too short to hold * the bytes, then only enough bytes to fill the array will be copied. * * @param dest the destination array * * @deprecated Replaced by {@link #copyTo(byte[])}. */ public void getBytes(byte[] dest) { copyTo(dest); } /** * Copy the bytes of this ByteString into the destination array. If the array is too short to hold * the bytes, then only enough bytes to fill the array will be copied. * * @param dest the destination array * @param offs the offset into the destination array * * @deprecated Replaced by {@link #copyTo(byte[],int)}. */ public void getBytes(byte[] dest, int offs) { copyTo(dest, offs); } /** * Copy the bytes of this ByteString into the destination array. If the array is too short to hold * the bytes, then only enough bytes to fill the array will be copied. * * @param dest the destination array * @param offs the offset into the destination array * @param len the maximum number of bytes to copy * * @deprecated Replaced by {@link #copyTo(byte[],int,int)}. */ public void getBytes(byte[] dest, int offs, int len) { copyTo(dest, offs, len); } /** * Copy {@code len} bytes from this string at offset {@code srcOffs} to the given array at the given offset. * * @param srcOffs the source offset * @param dst the destination * @param offs the destination offset * @param len the number of bytes to copy */ public void copyTo(int srcOffs, byte[] dst, int offs, int len) { arraycopy(bytes, srcOffs + this.offs, dst, offs, min(this.len, len)); } /** * Copy {@code len} bytes from this string to the given array at the given offset. * * @param dst the destination * @param offs the destination offset * @param len the number of bytes */ public void copyTo(byte[] dst, int offs, int len) { copyTo(0, dst, offs, len); } /** * Copy all the bytes from this string to the given array at the given offset. * * @param dst the destination * @param offs the destination offset */ public void copyTo(byte[] dst, int offs) { copyTo(dst, offs, dst.length - offs); } /** * Copy all the bytes from this string to the given array at the given offset. * * @param dst the destination */ public void copyTo(byte[] dst) { copyTo(dst, 0, dst.length); } /** * Append the bytes of this string into the given buffer. * * @param dest the target buffer */ public void appendTo(ByteBuffer dest) { dest.put(bytes, offs, len); } /** * Append as many bytes as possible to a byte buffer. * * @param offs the start offset * @param buffer the buffer to append to * @return the number of bytes appended */ public int tryAppendTo(final int offs, final ByteBuffer buffer) { final byte[] b = bytes; final int len = min(buffer.remaining(), b.length - offs); buffer.put(b, offs + this.offs, len); return len; } /** * Append to an output stream. * * @param output the stream to write to * @throws IOException if an error occurs */ public void writeTo(OutputStream output) throws IOException { // todo - determine if the output stream is trusted output.write(bytes, offs, len); } /** * Compare this string to another in a case-sensitive manner. * * @param other the other string * @return -1, 0, or 1 */ @Override public int compareTo(final ByteString other) { if (other == this) return 0; final int length = this.len; final int otherLength = other.len; final int len1 = min(length, otherLength); final byte[] bytes = this.bytes; final byte[] otherBytes = other.bytes; final int offs = this.offs; final int otherOffs = other.offs; int res; for (int i = 0; i < len1; i++) { res = signum(bytes[i + offs] - otherBytes[i + otherOffs]); if (res != 0) return res; } // shorter strings sort higher return signum(length - otherLength); } /** * Compare this string to another in a case-insensitive manner. * * @param other the other string * @return -1, 0, or 1 */ public int compareToIgnoreCase(final ByteString other) { if (other == this) return 0; if (other == this) return 0; final int length = this.len; final int otherLength = other.len; final int len1 = min(length, otherLength); final byte[] bytes = this.bytes; final byte[] otherBytes = other.bytes; final int offs = this.offs; final int otherOffs = other.offs; int res; for (int i = 0; i < len1; i++) { res = signum(upperCase(bytes[i + offs]) - upperCase(otherBytes[i + otherOffs])); if (res != 0) return res; } // shorter strings sort higher return signum(length - otherLength); } private static int upperCase(byte b) { return b >= 'a' && b <= 'z' ? b & 0xDF : b; } /** * Convert this byte string to a standard string. * * @param charset the character set to use * @return the standard string * @throws UnsupportedEncodingException if the charset is unknown */ public String toString(String charset) throws UnsupportedEncodingException { if ("ISO-8859-1".equalsIgnoreCase(charset) || "Latin-1".equalsIgnoreCase(charset) || "ISO-Latin-1".equals(charset)) return toString(); return new String(bytes, offs, len, charset); } /** * Get the number of bytes in this byte string. * * @return the number of bytes */ public int length() { return len; } /** * Decode this byte string as a Latin-1 string. * * @return the Latin-1-decoded version of this string */ @SuppressWarnings("deprecation") public String toString() { return new String(bytes, 0, offs, len); } /** * Decode this byte string as a UTF-8 string. * * @return the UTF-8-decoded version of this string */ public String toUtf8String() { return new String(bytes, offs, len, StandardCharsets.UTF_8); } /** * Get the byte at an index. * * @return the byte at an index */ public byte byteAt(int idx) { if (idx < 0 || idx > len) { throw new ArrayIndexOutOfBoundsException(); } return bytes[idx + offs]; } /** * Get the substring of this string starting at the given offset. * * @param offs the offset * @return the substring */ public ByteString substring(int offs) { return substring(offs, len - offs); } /** * Get the substring of this string starting at the given offset. * * @param offs the offset * @param len the substring length * @return the substring */ public ByteString substring(int offs, int len) { if (this.len - offs < len) { throw new IndexOutOfBoundsException(); } return new ByteString(bytes, this.offs + offs, len); } /** * Get the hash code for this ByteString. * * @return the hash code */ public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { this.hashCode = hashCode = calcHashCode(bytes, offs, len); } return hashCode; } public int hashCodeIgnoreCase() { int hashCode = this.hashCodeIgnoreCase; if (hashCode == 0) { this.hashCodeIgnoreCase = hashCode = calcHashCodeIgnoreCase(bytes, offs, len); } return hashCode; } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); } private static boolean equals(byte[] a, int aoff, byte[] b, int boff, int len) { for (int i = 0; i < len; i ++) { if (a[i + aoff] != b[i + boff]) return false; } return true; } private static boolean equalsIgnoreCase(byte[] a, int aoff, byte[] b, int boff, int len) { for (int i = 0; i < len; i ++) { if (upperCase(a[i + aoff]) != upperCase(b[i + boff])) return false; } return true; } /** * Determine if this ByteString equals another ByteString. * * @param obj the other object * @return {@code true} if they are equal */ public boolean equals(final Object obj) { return (obj instanceof ByteString) && equals((ByteString) obj); } /** * Determine if this ByteString equals another ByteString. * * @param other the other object * @return {@code true} if they are equal */ public boolean equals(final ByteString other) { final int len = this.len; return this == other || other != null && len == other.len && equals(bytes, offs, other.bytes, other.offs, len); } /** * Determine if this ByteString equals another ByteString, ignoring case (ASCII). * * @param other the other object * @return {@code true} if they are equal */ public boolean equalsIgnoreCase(final ByteString other) { final int len = this.len; return this == other || other != null && len == other.len && equalsIgnoreCase(bytes, offs, other.bytes, other.offs, len); } /** * Get the unsigned {@code int} value of this string. If the value is greater than would fit in 32 bits, only * the low 32 bits are returned. Parsing stops on the first non-digit character. * * @param start the index to start at (must be less than or equal to length) * @return the value */ public int toInt(final int start) { final int len = this.len; if (start >= len) { return 0; } final byte[] bytes = this.bytes; int v = 0; byte b; for (int i = start + offs; i < len; i ++) { b = bytes[i]; if (b < '0' || b > '9') { return v; } v = (v << 3) + (v << 1) + (b - '0'); } return v; } /** * Get the unsigned {@code int} value of this string. If the value is greater than would fit in 32 bits, only * the low 32 bits are returned. Parsing stops on the first non-digit character. * * @return the value */ public int toInt() { return toInt(0); } /** * Get the unsigned {@code long} value of this string. If the value is greater than would fit in 64 bits, only * the low 64 bits are returned. Parsing stops on the first non-digit character. * * @param start the index to start at (must be less than or equal to length) * @return the value */ public long toLong(final int start) { final int len = this.len; if (start >= len) { return 0; } final byte[] bytes = this.bytes; long v = 0; byte b; for (int i = start; i < len; i ++) { b = bytes[i]; if (b < '0' || b > '9') { return v; } v = (v << 3) + (v << 1) + (b - '0'); } return v; } /** * Get the unsigned {@code long} value of this string. If the value is greater than would fit in 64 bits, only * the low 64 bits are returned. Parsing stops on the first non-digit character. * * @return the value */ public long toLong() { return toLong(0); } private static int decimalCount(int val) { assert val >= 0; // afaik no faster way exists to do this if (val < 10) return 1; if (val < 100) return 2; if (val < 1000) return 3; if (val < 10000) return 4; if (val < 100000) return 5; if (val < 1000000) return 6; if (val < 10000000) return 7; if (val < 100000000) return 8; if (val < 1000000000) return 9; return 10; } private static int decimalCount(long val) { assert val >= 0; // afaik no faster way exists to do this if (val < 10L) return 1; if (val < 100L) return 2; if (val < 1000L) return 3; if (val < 10000L) return 4; if (val < 100000L) return 5; if (val < 1000000L) return 6; if (val < 10000000L) return 7; if (val < 100000000L) return 8; if (val < 1000000000L) return 9; if (val < 10000000000L) return 10; if (val < 100000000000L) return 11; if (val < 1000000000000L) return 12; if (val < 10000000000000L) return 13; if (val < 100000000000000L) return 14; if (val < 1000000000000000L) return 15; if (val < 10000000000000000L) return 16; if (val < 100000000000000000L) return 17; if (val < 1000000000000000000L) return 18; return 19; } private static final ByteString ZERO = new ByteString(new byte[] { '0' }); /** * Get a string version of the given value. * * @param val the value * @return the string */ public static ByteString fromLong(long val) { if (val == 0) return ZERO; // afaik no faster way exists to do this int i = decimalCount(abs(val)); final byte[] b; if (val < 0) { b = new byte[++i]; b[0] = '-'; } else { b = new byte[i]; } long quo; // modulus int mod; do { quo = val / 10; mod = (int) (val - ((quo << 3) + (quo << 1))); b[--i] = (byte) (mod + '0'); val = quo; } while (i > 0); return new ByteString(b); } /** * Get a string version of the given value. * * @param val the value * @return the string */ public static ByteString fromInt(int val) { if (val == 0) return ZERO; // afaik no faster way exists to do this int i = decimalCount(abs(val)); final byte[] b; if (val < 0) { b = new byte[++i]; b[0] = '-'; } else { b = new byte[i]; } int quo; // modulus int mod; do { quo = val / 10; mod = val - ((quo << 3) + (quo << 1)); b[--i] = (byte) (mod + '0'); val = quo; } while (i > 0); return new ByteString(b); } /** * Determine whether this {@code ByteString} is equal (case-sensitively) to the given {@code String}. * * @param str the string to check * @return {@code true} if the given string is equal (case-sensitively) to this instance, {@code false} otherwise */ public boolean equalToString(String str) { if (str == null) return false; final byte[] bytes = this.bytes; final int length = bytes.length; if (str.length() != length) { return false; } char ch; final int end = offs + len; for (int i = offs; i < end; i++) { ch = str.charAt(i); if (ch > 0xff || bytes[i] != (byte) str.charAt(i)) { return false; } } return true; } /** * Determine whether this {@code ByteString} is equal (case-insensitively) to the given {@code String}. * * @param str the string to check * @return {@code true} if the given string is equal (case-insensitively) to this instance, {@code false} otherwise */ public boolean equalToStringIgnoreCase(String str) { if (str == null) return false; final byte[] bytes = this.bytes; final int length = bytes.length; if (str.length() != length) { return false; } char ch; final int end = offs + len; for (int i = offs; i < end; i++) { ch = str.charAt(i); if (ch > 0xff || upperCase(bytes[i]) != upperCase((byte) ch)) { return false; } } return true; } /** * Get the index of the given character in this string. * * @param c the character * @return the index, or -1 if it was not found */ public int indexOf(final char c) { return indexOf(c, 0); } /** * Get the index of the given character in this string. * * @param c the character * @return the index, or -1 if it was not found */ public int indexOf(final char c, int start) { if (c > 255) { return -1; } final int len = this.len; if (start > len) { return -1; } start = max(0, start) + offs; final byte[] bytes = this.bytes; final byte bc = (byte) c; final int end = start + len; for (int i = start; i < end; i++) { if (bytes[i] == bc) { return i; } } return -1; } /** * Get the last index of the given character in this string. * * @param c the character * @return the index, or -1 if it was not found */ public int lastIndexOf(final char c) { return lastIndexOf(c, length() - 1); } /** * Get the last index of the given character in this string. * * @param c the character * @return the index, or -1 if it was not found */ public int lastIndexOf(final char c, int start) { if (c > 255) { return -1; } final byte[] bytes = this.bytes; final int offs = this.offs; start = min(start, len - 1) + offs; final byte bc = (byte) c; for (int i = start; i >= offs; --i) { if (bytes[i] == bc) { return i; } } return -1; } // Linear array searches private static int arrayIndexOf(byte[] a, int aOffs, byte[] b, int bOffs, int bLen) { final int aLen = a.length - aOffs; if (bLen > aLen || aLen < 0) { return -1; } aOffs = max(0, aOffs); if (bLen == 0) { return aOffs; } final byte startByte = b[bOffs]; final int limit = aLen - bLen; OUTER: for (int i = aOffs; i < limit; i ++) { if (a[i] == startByte) { for (int j = 1; j < bLen; j ++) { if (a[i + j] != b[j + bOffs]) { continue OUTER; } } return i; } } return -1; } private static int arrayIndexOf(byte[] a, int aOffs, String string) { final int aLen = a.length - aOffs; final int bLen = string.length(); if (bLen > aLen || aLen < 0) { return -1; } aOffs = max(0, aOffs); if (bLen == 0) { return aOffs; } final char startChar = string.charAt(0); if (startChar > 0xff) { return -1; } char ch; final int limit = aLen - bLen; OUTER: for (int i = aOffs; i < limit; i ++) { if (a[i] == startChar) { for (int j = 1; j < bLen; j ++) { ch = string.charAt(j); if (ch > 0xff) { return -1; } if (a[i + j] != ch) { continue OUTER; } } return i; } } return -1; } private static int arrayIndexOfIgnoreCase(byte[] a, int aOffs, byte[] b, int bOffs, int bLen) { final int aLen = a.length - aOffs; if (bLen > aLen || aLen < 0) { return -1; } aOffs = max(0, aOffs); if (bLen == 0) { return aOffs; } final int startChar = upperCase(b[bOffs]); final int limit = aLen - bLen; OUTER: for (int i = aOffs; i < limit; i ++) { if (upperCase(a[i]) == startChar) { for (int j = 1; j < bLen; j ++) { if (upperCase(a[i + j]) != upperCase(b[j + bOffs])) { continue OUTER; } } return i; } } return -1; } private static int arrayIndexOfIgnoreCase(byte[] a, int aOffs, String string) { final int aLen = a.length - aOffs; final int bLen = string.length(); if (bLen > aLen || aLen < 0) { return -1; } aOffs = max(0, aOffs); if (bLen == 0) { return aOffs; } final char startChar = string.charAt(0); if (startChar > 0xff) { return -1; } final int startCP = upperCase((byte) startChar); final int limit = aLen - bLen; char ch; OUTER: for (int i = aOffs; i < limit; i ++) { if (upperCase(a[i]) == startCP) { for (int j = 1; j < bLen; j ++) { ch = string.charAt(j); if (ch > 0xff) { return -1; } // technically speaking, 'ı' (0x131) maps to I and 'ſ' (0x17F) maps to S, but this is unlikely to come up in ISO-8859-1 if (upperCase(a[i + j]) != upperCase((byte) ch)) { continue OUTER; } } return i; } } return -1; } private static int arrayLastIndexOf(byte[] a, int aOffs, byte[] b, final int bOffs, final int bLen) { final int aLen = a.length - aOffs; if (bLen > aLen || aLen < 0 || aOffs < 0) { return -1; } // move to the last possible position it could be aOffs = min(aLen - bLen, aOffs); if (bLen == 0) { return aOffs; } final byte startByte = b[0]; OUTER: for (int i = aOffs - 1; i >= 0; i --) { if (a[i] == startByte) { for (int j = 1; j < bLen; j++) { if (a[i + j] != b[bOffs + j]) { continue OUTER; } return i; } } } return -1; } private static int arrayLastIndexOf(byte[] a, int aOffs, String string) { final int aLen = a.length - aOffs; final int bLen = string.length(); if (bLen > aLen || aLen < 0 || aOffs < 0) { return -1; } // move to the last possible position it could be aOffs = min(aLen - bLen, aOffs); if (bLen == 0) { return aOffs; } final char startChar = string.charAt(0); if (startChar > 0xff) { return -1; } final byte startByte = (byte) startChar; char ch; OUTER: for (int i = aOffs - 1; i >= 0; i --) { if (a[i] == startByte) { for (int j = 1; j < bLen; j++) { ch = string.charAt(j); if (ch > 0xff) { return -1; } if (a[i + j] != (byte) ch) { continue OUTER; } return i; } } } return -1; } private static int arrayLastIndexOfIgnoreCase(byte[] a, int aOffs, byte[] b, final int bOffs, final int bLen) { final int aLen = a.length - aOffs; if (bLen > aLen || aLen < 0 || aOffs < 0) { return -1; } // move to the last possible position it could be aOffs = min(aLen - bLen, aOffs); if (bLen == 0) { return aOffs; } final int startCP = upperCase(b[bOffs]); OUTER: for (int i = aOffs - 1; i >= 0; i --) { if (upperCase(a[i]) == startCP) { for (int j = 1; j < bLen; j++) { if (upperCase(a[i + j]) != upperCase(b[j + bOffs])) { continue OUTER; } return i; } } } return -1; } private static int arrayLastIndexOfIgnoreCase(byte[] a, int aOffs, String string) { final int aLen = a.length - aOffs; final int bLen = string.length(); if (bLen > aLen || aLen < 0 || aOffs < 0) { return -1; } // move to the last possible position it could be aOffs = min(aLen - bLen, aOffs); if (bLen == 0) { return aOffs; } final char startChar = string.charAt(0); if (startChar > 0xff) { return -1; } final int startCP = upperCase((byte) startChar); char ch; OUTER: for (int i = aOffs - 1; i >= 0; i --) { if (upperCase(a[i]) == startCP) { for (int j = 1; j < bLen; j++) { ch = string.charAt(j); if (ch > 0xff) { return -1; } // technically speaking, 'ı' (0x131) maps to I and 'ſ' (0x17F) maps to S, but this is unlikely to come up in ISO-8859-1 if (upperCase(a[i + j]) != upperCase((byte) ch)) { continue OUTER; } return i; } } } return -1; } /** * Determine whether this string contains another string (case-sensitive). * * @param other the string to test * @return {@code true} if this string contains {@code other}, {@code false} otherwise */ public boolean contains(final ByteString other) { if (other == this) return true; if (other == null) return false; final byte[] otherBytes = other.bytes; return arrayIndexOf(bytes, offs, otherBytes, other.offs, other.len) != -1; } /** * Determine whether this string contains another string (case-sensitive). * * @param other the string to test * @return {@code true} if this string contains {@code other}, {@code false} otherwise */ public boolean contains(final String other) { return other != null && toString().contains(other); } /** * Determine whether this string contains another string (case-insensitive). * * @param other the string to test * @return {@code true} if this string contains {@code other}, {@code false} otherwise */ public boolean containsIgnoreCase(final ByteString other) { return other == this || other != null && arrayIndexOfIgnoreCase(bytes, offs, other.bytes, other.offs, other.len) != -1; } /** * Determine whether this string contains another string (case-sensitive). * * @param other the string to test * @return {@code true} if this string contains {@code other}, {@code false} otherwise */ public boolean containsIgnoreCase(final String other) { return arrayIndexOfIgnoreCase(bytes, offs, other) != -1; } public int indexOf(final ByteString other) { return arrayIndexOf(bytes, offs, other.bytes, other.offs, other.len); } public int indexOf(final ByteString other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayIndexOf(bytes, offs + start, other.bytes, other.offs, other.len); } public int indexOf(final String other) { return arrayIndexOf(bytes, offs, other); } public int indexOf(final String other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayIndexOf(bytes, offs + start, other); } public int indexOfIgnoreCase(final ByteString other) { return arrayIndexOfIgnoreCase(bytes, offs, other.bytes, other.offs, other.len); } public int indexOfIgnoreCase(final ByteString other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayIndexOfIgnoreCase(bytes, offs + start, other.bytes, other.offs, other.len); } public int indexOfIgnoreCase(final String other) { return arrayIndexOfIgnoreCase(bytes, offs, other); } public int indexOfIgnoreCase(final String other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayIndexOfIgnoreCase(bytes, offs + start, other); } public int lastIndexOf(final ByteString other) { return arrayLastIndexOf(bytes, offs, other.bytes, other.offs, other.len); } public int lastIndexOf(final ByteString other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayLastIndexOf(bytes, offs + start, other.bytes, other.offs, other.len); } public int lastIndexOf(final String other) { return arrayLastIndexOf(bytes, offs, other); } public int lastIndexOf(final String other, int start) { return arrayLastIndexOf(bytes, offs + start, other); } public int lastIndexOfIgnoreCase(final ByteString other) { return arrayLastIndexOfIgnoreCase(bytes, offs, other.bytes, other.offs, other.len); } public int lastIndexOfIgnoreCase(final ByteString other, int start) { if (start > len) return -1; if (start < 0) start = 0; return arrayLastIndexOfIgnoreCase(bytes, offs + start, other.bytes, other.offs, other.len); } public int lastIndexOfIgnoreCase(final String other) { return arrayLastIndexOfIgnoreCase(bytes, offs, other); } public int lastIndexOfIgnoreCase(final String other, int start) { return arrayLastIndexOfIgnoreCase(bytes, offs + start, other); } public boolean regionMatches(boolean ignoreCase, int offset, byte[] other, int otherOffset, int len) { if (offset < 0 || otherOffset < 0 || offset + len > this.len || otherOffset + len > other.length) { return false; } if (ignoreCase) { return equalsIgnoreCase(bytes, offset + offs, other, otherOffset, len); } else { return equals(bytes, offset + offs, other, otherOffset, len); } } public boolean regionMatches(boolean ignoreCase, int offset, ByteString other, int otherOffset, int len) { if (offset < 0 || otherOffset < 0 || offset + len > this.len || otherOffset + len > other.len) { return false; } if (ignoreCase) { return equalsIgnoreCase(bytes, offset + offs, other.bytes, otherOffset, len); } else { return equals(bytes, offset + offs, other.bytes, otherOffset, len); } } public boolean regionMatches(boolean ignoreCase, int offset, String other, int otherOffset, int len) { if (offset < 0 || otherOffset < 0 || offset + len > this.len || otherOffset + len > other.length()) { return false; } if (ignoreCase) { return equalsIgnoreCase(bytes, offset + offs, other, otherOffset, len); } else { return equals(bytes, offset + offs, other, otherOffset, len); } } private static boolean equalsIgnoreCase(final byte[] a, int aOffs, String string, int stringOffset, int length) { char ch; for (int i = 0; i < length; i ++) { ch = string.charAt(i + stringOffset); if (ch > 0xff) { return false; } if (a[i + aOffs] != (byte) ch) { return false; } } return true; } private static boolean equals(final byte[] a, int aOffs, String string, int stringOffset, int length) { char ch; for (int i = 0; i < length; i ++) { ch = string.charAt(i + stringOffset); if (ch > 0xff) { return false; } if (upperCase(a[i + aOffs]) != upperCase((byte) ch)) { return false; } } return true; } public boolean startsWith(ByteString prefix) { return regionMatches(false, 0, prefix, 0, prefix.length()); } public boolean startsWith(String prefix) { return regionMatches(false, 0, prefix, 0, prefix.length()); } public boolean startsWith(char prefix) { return prefix <= 0xff && len > 0 && bytes[offs] == (byte) prefix; } public boolean startsWithIgnoreCase(ByteString prefix) { return regionMatches(true, 0, prefix, 0, prefix.length()); } public boolean startsWithIgnoreCase(String prefix) { return regionMatches(true, 0, prefix, 0, prefix.length()); } public boolean startsWithIgnoreCase(char prefix) { return prefix <= 0xff && len > 0 && upperCase(bytes[offs]) == upperCase((byte) prefix); } public boolean endsWith(ByteString suffix) { final int suffixLength = suffix.len; return regionMatches(false, len - suffixLength, suffix, 0, suffixLength); } public boolean endsWith(String suffix) { final int suffixLength = suffix.length(); return regionMatches(false, len - suffixLength, suffix, 0, suffixLength); } public boolean endsWith(char suffix) { final int len = this.len; return suffix <= 0xff && len > 0 && bytes[offs + len - 1] == (byte) suffix; } public boolean endsWithIgnoreCase(ByteString suffix) { final int suffixLength = suffix.length(); return regionMatches(true, len - suffixLength, suffix, 0, suffixLength); } public boolean endsWithIgnoreCase(String suffix) { final int suffixLength = suffix.length(); return regionMatches(true, len - suffixLength, suffix, 0, suffixLength); } public boolean endsWithIgnoreCase(char suffix) { final int len = this.len; return suffix <= 0xff && len > 0 && upperCase(bytes[offs + len - 1]) == upperCase((byte) suffix); } public ByteString concat(byte[] suffixBytes) { return concat(suffixBytes, 0, suffixBytes.length); } public ByteString concat(byte[] suffixBytes, int offs, int len) { if (len <= 0) { return this; } final int length = this.len; byte[] newBytes = Arrays.copyOfRange(bytes, this.offs, length + len); System.arraycopy(suffixBytes, offs, newBytes, length, len); return new ByteString(newBytes); } public ByteString concat(ByteString suffix) { return concat(suffix.bytes, suffix.offs, suffix.len); } public ByteString concat(ByteString suffix, int offs, int len) { return concat(suffix.bytes, offs + suffix.offs, min(len, suffix.len)); } public ByteString concat(String suffix) { return concat(suffix, 0, suffix.length()); } @SuppressWarnings("deprecation") private static byte[] getStringBytes(final boolean trust, final byte[] dst, final int dstOffs, final String src, final int srcOffs, final int len) { if (trust) { src.getBytes(srcOffs, srcOffs + len, dst, dstOffs); } else { for (int i = srcOffs; i < len; i++) { char c = src.charAt(i); if (c > 0xff) { throw new IllegalArgumentException("Invalid string contents"); } dst[i + dstOffs] = (byte) c; } } return dst; } public ByteString concat(String suffix, int offs, int len) { if (len <= 0) { return this; } final byte[] bytes = this.bytes; final int length = this.len; byte[] newBytes = Arrays.copyOfRange(bytes, offs, offs + length + len); getStringBytes(false, newBytes, length, suffix, offs, len); return new ByteString(newBytes); } public static ByteString concat(String prefix, ByteString suffix) { final int prefixLength = prefix.length(); final byte[] suffixBytes = suffix.bytes; final int suffixLength = suffixBytes.length; final byte[] newBytes = new byte[prefixLength + suffixLength]; getStringBytes(false, newBytes, 0, prefix, 0, prefixLength); System.arraycopy(suffixBytes, suffix.offs, newBytes, prefixLength, suffixLength); return new ByteString(newBytes); } public static ByteString concat(String prefix, String suffix) { final int prefixLength = prefix.length(); final int suffixLength = suffix.length(); final byte[] newBytes = new byte[prefixLength + suffixLength]; getStringBytes(false, newBytes, 0, prefix, 0, prefixLength); getStringBytes(false, newBytes, prefixLength, suffix, 0, suffixLength); return new ByteString(newBytes); } public char charAt(final int index) { if (index < 0 || index > len) { throw new ArrayIndexOutOfBoundsException(); } return (char) (bytes[index + offs] & 0xff); } public ByteString subSequence(final int start, final int end) { return substring(start, end); } }