All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xnio.ByteString Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy