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

org.xnio.ByteString Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 32.0.0.Final
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