org.xnio.ByteString Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xnio-api Show documentation
Show all versions of xnio-api Show documentation
The API JAR of the XNIO project
/*
* 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, 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);
}
}