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

org.glassfish.grizzly.http.util.ByteChunk Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright 2004 The Apache Software Foundation
 *
 * 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.glassfish.grizzly.http.util;

import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;

/*
 * In a server it is very important to be able to operate on
 * the original byte[] without converting everything to chars.
 * Some protocols are ASCII only, and some allow different
 * non-UNICODE encodings. The encoding is not known beforehand,
 * and can even change during the execution of the protocol.
 * ( for example a multipart message may have parts with different
 *  encoding )
 *
 * For HTTP it is not very clear how the encoding of RequestURI
 * and mime values can be determined, but it is a great advantage
 * to be able to parse the request without converting to string.
 */

/**
 * This class is used to represent a chunk of bytes, and utilities to manipulate byte[].
 *
 * The buffer can be modified and used for both input and output.
 *
 * @author [email protected]
 * @author James Todd [[email protected]]
 * @author Costin Manolache
 * @author Remy Maucherat
 */
public final class ByteChunk implements Chunk, Cloneable, Serializable {

    private static final long serialVersionUID = -1L;

    // Input interface, used when the buffer is emptied.
    public interface ByteInputChannel {
        /**
         * Read new bytes ( usually the internal conversion buffer ). The implementation is allowed to ignore the parameters,
         * and mutate the chunk if it wishes to implement its own buffering.
         */
        int realReadBytes(byte cbuf[], int off, int len) throws IOException;
    }

    // Output interface, used when the buffer is filled.
    public interface ByteOutputChannel {
        /**
         * Send the bytes ( usually the internal conversion buffer ). Expect 8k output if the buffer is full.
         */
        void realWriteBytes(byte cbuf[], int off, int len) throws IOException;
    }

    // --------------------

    /**
     * Default encoding used to convert to strings. It should be UTF8, as most standards seem to converge, but the servlet
     * API requires 8859_1, and this object is used mostly for servlets.
     */
    private static final Charset DEFAULT_CHARSET = Constants.DEFAULT_HTTP_CHARSET;

    // byte[]
    private byte[] buff;

    private int start = 0;
    private int end;

    private Charset charset;

    private boolean isSet = false; // XXX

    // How much can it grow, when data is added
    private int limit = -1;

    private transient ByteInputChannel in = null;
    private transient ByteOutputChannel out = null;

    private boolean optimizedWrite = true;

    private String cachedString;
    private Charset cachedStringCharset;

    /**
     * Creates a new, uninitialized ByteChunk object.
     */
    public ByteChunk() {
    }

    public ByteChunk(int initial) {
        allocate(initial, -1);
    }

    // --------------------
    public ByteChunk getClone() {
        try {
            return (ByteChunk) this.clone();
        } catch (Exception ex) {
            return null;
        }
    }

    public boolean isNull() {
        return !isSet; // buff==null;
    }

    /**
     * Resets the message buff to an uninitialized state.
     */
    public void recycle() {
//        buff = null;
        charset = null;
        start = 0;
        end = 0;
        isSet = false;
    }

    public void recycleAndReset() {
        buff = null;
        charset = null;
        start = 0;
        end = 0;
        isSet = false;
        resetStringCache();
    }

    public void reset() {
        buff = null;
        resetStringCache();
    }

    protected void resetStringCache() {
        cachedString = null;
        cachedStringCharset = null;
    }

    // -------------------- Setup --------------------

    public void allocate(int initial, int limit) {
        if (buff == null || buff.length < initial) {
            buff = new byte[initial];
        }
        this.limit = limit;
        start = 0;
        end = 0;
        isSet = true;
        resetStringCache();
    }

    /**
     * Sets the message bytes to the specified sub-array of bytes.
     *
     * @param b the ascii bytes
     * @param off the start offset of the bytes
     * @param len the length of the bytes
     */
    public void setBytes(byte[] b, int off, int len) {
        buff = b;
        start = off;
        end = start + len;
        isSet = true;
        resetStringCache();
    }

    public void setOptimizedWrite(boolean optimizedWrite) {
        this.optimizedWrite = optimizedWrite;
    }

    public Charset getCharset() {
        return charset != null ? charset : DEFAULT_CHARSET;
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
        resetStringCache();
    }

    /**
     * Returns the message bytes.
     */
    public byte[] getBytes() {
        return getBuffer();
    }

    /**
     * Returns the message bytes.
     */
    public byte[] getBuffer() {
        return buff;
    }

    /**
     * Returns the start offset of the bytes. For output this is the end of the buffer.
     */
    @Override
    public int getStart() {
        return start;
    }

    public int getOffset() {
        return getStart();
    }

    @Override
    public void setStart(int start) {
        if (end < start) {
            end = start;
        }
        this.start = start;
        resetStringCache();
    }

    public void setOffset(int off) {
        setStart(off);
    }

    /**
     * Returns the length of the bytes. XXX need to clean this up
     */
    @Override
    public int getLength() {
        return end - start;
    }

    /**
     * Maximum amount of data in this buffer.
     *
     * If -1 or not set, the buffer will grow indefinitely. Can be smaller than the current buffer size ( which will not
     * shrink ). When the limit is reached, the buffer will be flushed ( if out is set ) or throw exception.
     */
    public void setLimit(int limit) {
        this.limit = limit;
        resetStringCache();
    }

    public int getLimit() {
        return limit;
    }

    /**
     * When the buffer is empty, read the data from the input channel.
     */
    public void setByteInputChannel(ByteInputChannel in) {
        this.in = in;
    }

    /**
     * When the buffer is full, write the data to the output channel. Also used when large amount of data is appended.
     *
     * If not set, the buffer will grow to the limit.
     */
    public void setByteOutputChannel(ByteOutputChannel out) {
        this.out = out;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setEnd(int i) {
        end = i;
        resetStringCache();
    }

    /**
     * Notify the Chunk that its content is going to be changed directly
     */
    protected void notifyDirectUpdate() {
    }

    @Override
    public int indexOf(final String s, int fromIdx) {
        // Works only for UTF
        final int strLen = s.length();
        if (strLen == 0) {
            return fromIdx;
        }

        int absFromIdx = fromIdx + start;

        if (strLen > end - absFromIdx) {
            return -1;
        }

        int strOffs = 0;
        final int lastOffs = end - strLen;

        while (absFromIdx <= lastOffs + strOffs) {
            final byte b = buff[absFromIdx];
            if (b == s.charAt(strOffs)) {
                strOffs++;
                if (strOffs == strLen) {
                    return absFromIdx - strLen - start + 1;
                }
            } else {
                strOffs = 0;
            }

            absFromIdx++;
        }
        return -1;
    }

    @Override
    public void delete(final int start, final int end) {
        resetStringCache();

        final int absDeleteStart = this.start + start;
        final int absDeleteEnd = this.start + end;

        final int diff = this.end - absDeleteEnd;
        if (diff == 0) {
            this.end = absDeleteStart;
        } else {
            System.arraycopy(buff, absDeleteEnd, buff, absDeleteStart, diff);
            this.end = absDeleteStart + diff;
        }
    }

    // -------------------- Adding data to the buffer --------------------
    public void append(char c) throws IOException {
        append((byte) c);
    }

    public void append(byte b) throws IOException {
        resetStringCache();

        makeSpace(1);

        // couldn't make space
        if (limit > 0 && end >= limit) {
            flushBuffer();
        }
        buff[end++] = b;
    }

    public void append(ByteChunk src) throws IOException {
        append(src.getBytes(), src.getStart(), src.getLength());
    }

    /**
     * Add data to the buffer
     */
    public void append(byte src[], int off, int len) throws IOException {
        resetStringCache();

        // will grow, up to limit
        makeSpace(len);

        // if we don't have limit: makeSpace can grow as it wants
        if (limit < 0) {
            // assert: makeSpace made enough space
            System.arraycopy(src, off, buff, end, len);
            end += len;
            return;
        }

        // Optimize on a common case.
        // If the buffer is empty and the source is going to fill up all the
        // space in buffer, may as well write it directly to the output,
        // and avoid an extra copy
        if (optimizedWrite && len == limit && end == start) {
            out.realWriteBytes(src, off, len);
            return;
        }
        // if we have limit and we're below
        if (len <= limit - end) {
            // makeSpace will grow the buffer to the limit,
            // so we have space
            System.arraycopy(src, off, buff, end, len);
            end += len;
            return;
        }

        // need more space than we can afford, need to flush
        // buffer

        // the buffer is already at ( or bigger than ) limit

        // We chunk the data into slices fitting in the buffer limit, although
        // if the data is written directly if it doesn't fit

        int avail = limit - end;
        System.arraycopy(src, off, buff, end, avail);
        end += avail;

        flushBuffer();

        int remain = len - avail;

        while (remain > limit - end) {
            out.realWriteBytes(src, off + len - remain, limit - end);
            remain = remain - (limit - end);
        }

        System.arraycopy(src, off + len - remain, buff, end, remain);
        end += remain;

    }

    // -------------------- Removing data from the buffer --------------------

    public int substract() throws IOException {

        resetStringCache();

        if (end - start == 0) {
            if (in == null) {
                return -1;
            }
            int n = in.realReadBytes(buff, 0, buff.length);
            if (n < 0) {
                return -1;
            }
        }

        return buff[start++] & 0xFF;

    }

    public int substract(ByteChunk src) throws IOException {

        resetStringCache();

        if (end - start == 0) {
            if (in == null) {
                return -1;
            }
            int n = in.realReadBytes(buff, 0, buff.length);
            if (n < 0) {
                return -1;
            }
        }

        int len = getLength();
        src.append(buff, start, len);
        start = end;
        return len;

    }

    public int substract(byte src[], int off, int len) throws IOException {

        resetStringCache();

        if (end - start == 0) {
            if (in == null) {
                return -1;
            }
            int n = in.realReadBytes(buff, 0, buff.length);
            if (n < 0) {
                return -1;
            }
        }

        int n = len;
        if (len > getLength()) {
            n = getLength();
        }
        System.arraycopy(buff, start, src, off, n);
        start += n;
        return n;

    }

    public void flushBuffer() throws IOException {
        // assert out!=null
        if (out == null) {
            throw new IOException("Buffer overflow, no sink " + limit + ' ' + buff.length);
        }
        out.realWriteBytes(buff, start, end - start);
        end = start;
    }

    // See if we can add more space without flushing the buffer
    boolean canGrow() {
        if (buff.length == limit) {
            return false;
        }
        // This seems like a potential place for huge memory use, but it's
        // the same algorithm as makeSpace() has always effectively used.
        int desiredSize = buff.length * 2;
        if (limit > 0 && desiredSize > limit && limit > end - start) {
            desiredSize = limit;
        }
        byte[] tmp = new byte[desiredSize];
        System.arraycopy(buff, start, tmp, 0, end - start);
        buff = tmp;
        tmp = null;
        end = end - start;
        start = 0;
        return true;
    }

    /**
     * Make space for len chars. If len is small, allocate a reserve space too. Never grow bigger than limit.
     */
    private void makeSpace(int count) {
        byte[] tmp;

        int newSize;
        int desiredSize = end + count;

        // Can't grow above the limit
        if (limit > 0 && desiredSize > limit) {
            desiredSize = limit;
        }

        if (buff == null) {
            if (desiredSize < 256) {
                desiredSize = 256; // take a minimum
            }
            buff = new byte[desiredSize];
        }

        // limit < buf.length ( the buffer is already big )
        // or we already have space XXX
        if (desiredSize <= buff.length) {
            return;
        }
        // grow in larger chunks
        if (desiredSize < 2 * buff.length) {
            newSize = buff.length * 2;
            if (limit > 0 && newSize > limit) {
                newSize = limit;
            }
            tmp = new byte[newSize];
        } else {
            newSize = buff.length * 2 + count;
            if (limit > 0 && newSize > limit) {
                newSize = limit;
            }
            tmp = new byte[newSize];
        }

        System.arraycopy(buff, start, tmp, 0, end - start);
        buff = tmp;
        tmp = null;
        end = end - start;
        start = 0;
    }

    // -------------------- Conversion and getters --------------------

    public void trimLeft() {
        boolean modified = false;
        while (buff[start] <= 0x20) {
            modified = true;
            start++;
        }
        if (modified) {
            resetStringCache();
        }
    }

    @Override
    public String toString() {
        if (null == buff || end - start == 0) {
            return "";
        } else if (cachedString != null) {
            return cachedString;
        }
//        return StringCache.toString(this);
        cachedString = toStringInternal();
        return cachedString;
    }

    @Override
    public String toString(int start, int end) {
        if (start == this.start && end == this.end) {
            return toString();
        } else if (null == buff) {
            return null;
        } else if (end - start == 0) {
            return "";
        }

        if (charset == null) {
            charset = DEFAULT_CHARSET;
        }

        try {
            return new String(buff, this.start + start, end - start, charset.name());
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Unexpected error", e);
        }
    }

    public String toString(Charset charset) {
        if (charset == null) {
            charset = this.charset != null ? this.charset : DEFAULT_CHARSET;
        }

        if (cachedString != null && charset.equals(cachedStringCharset)) {
            return cachedString;
        }

        cachedString = charset.decode(ByteBuffer.wrap(buff, start, end - start)).toString();
        cachedStringCharset = charset;

        return cachedString;
    }

    public String toStringInternal() {
        if (charset == null) {
            charset = DEFAULT_CHARSET;
        }

        return toString(charset);
    }

    public int getInt() {
        return Ascii.parseInt(buff, start, end - start);
    }

    public long getLong() {
        return Ascii.parseLong(buff, start, end - start);
    }

    // -------------------- equals --------------------

    @Override
    public int hashCode() {
        int result = Arrays.hashCode(buff);
        result = 31 * result + start;
        result = 31 * result + end;
        result = 31 * result + charset.hashCode();
        result = 31 * result + (isSet ? 1 : 0);
        result = 31 * result + limit;
        result = 31 * result + in.hashCode();
        result = 31 * result + out.hashCode();
        result = 31 * result + (optimizedWrite ? 1 : 0);
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ByteChunk byteChunk = (ByteChunk) o;

        if (end != byteChunk.end) {
            return false;
        }
        if (isSet != byteChunk.isSet) {
            return false;
        }
        if (limit != byteChunk.limit) {
            return false;
        }
        if (optimizedWrite != byteChunk.optimizedWrite) {
            return false;
        }
        if (start != byteChunk.start) {
            return false;
        }
        if (!Arrays.equals(buff, byteChunk.buff)) {
            return false;
        }
        if (charset != null ? !charset.equals(byteChunk.charset) : byteChunk.charset != null) {
            return false;
        }
        if (in != null ? !in.equals(byteChunk.in) : byteChunk.in != null) {
            return false;
        }
        if (out != null ? !out.equals(byteChunk.out) : byteChunk.out != null) {
            return false;
        }

        return true;
    }

    /**
     * Compares the message bytes to the specified String object.
     * 
     * @param s the String to compare
     * @return true if the comparison succeeded, false otherwise
     */
    public boolean equals(final String s) {
        return equals(buff, start, end - start, s);
    }

    /**
     * Compares the message bytes to the specified byte array.
     * 
     * @param bytes the byte[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.3
     */
    public boolean equals(final byte[] bytes) {
        return equals(buff, start, end - start, bytes, 0, bytes.length);
    }

    /**
     * Compares the message bytes to the specified String object.
     * 
     * @param s the String to compare
     * @return true if the comparison succeeded, false otherwise
     */
    public boolean equalsIgnoreCase(final String s) {
        return equalsIgnoreCase(buff, start, getLength(), s);
    }

    public boolean equalsIgnoreCase(final byte[] b) {
        return equalsIgnoreCase(b, 0, b.length);
    }

    public boolean equalsIgnoreCase(final byte[] b, final int offset, final int len) {
        return equalsIgnoreCase(buff, start, getLength(), b, offset, len);
    }

    public boolean equalsIgnoreCaseLowerCase(byte[] cmpTo) {
        return equalsIgnoreCaseLowerCase(buff, start, end, cmpTo);
    }

    public boolean equals(ByteChunk bb) {
        return equals(bb.getBytes(), bb.getStart(), bb.getLength());
    }

    public boolean equals(byte b2[], int off2, int len2) {
        byte b1[] = buff;
        if (b1 == null && b2 == null) {
            return true;
        }

        int len = end - start;
        if (len2 != len || b1 == null || b2 == null) {
            return false;
        }

        int off1 = start;

        while (len-- > 0) {
            if (b1[off1++] != b2[off2++]) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(CharChunk cc) {
        return equals(cc.getChars(), cc.getStart(), cc.getLength());
    }

    public boolean equals(char c2[], int off2, int len2) {
        // XXX works only for enc compatible with ASCII/UTF !!!
        byte b1[] = buff;
        if (c2 == null && b1 == null) {
            return true;
        }

        if (b1 == null || c2 == null || end - start != len2) {
            return false;
        }
        int off1 = start;
        int len = end - start;

        while (len-- > 0) {
            if ((char) b1[off1++] != c2[off2++]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * 
     * @param s the string
     */
    public boolean startsWith(String s) {
        return startsWith(s, 0);
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * 
     * @param s the string
     * @param offset The position
     */
    public boolean startsWith(String s, int offset) {
        byte[] b = buff;
        int len = s.length();
        if (b == null || len + offset > end - start) {
            return false;
        }
        int off = start + offset;
        for (int i = 0; i < len; i++) {
            if (b[off++] != s.charAt(i)) {
                return false;
            }
        }
        return true;
    }

    /* Returns true if the message bytes start with the specified byte array */
    public boolean startsWith(byte[] b2) {
        byte[] b1 = buff;
        if (b1 == null && b2 == null) {
            return true;
        }

        int len = end - start;
        if (b1 == null || b2 == null || b2.length > len) {
            return false;
        }
        for (int i = start, j = 0; i < end && j < b2.length;) {
            if (b1[i++] != b2[j++]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * 
     * @param s the string
     * @param pos The position
     */
    public boolean startsWithIgnoreCase(String s, int pos) {
        byte[] b = buff;
        int len = s.length();
        if (b == null || len + pos > end - start) {
            return false;
        }
        int off = start + pos;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public int indexOf(String src, int srcOff, int srcLen, int myOff) {
        char first = src.charAt(srcOff);

        // Look for first char
        int srcEnd = srcOff + srcLen;

        for (int i = myOff + start; i <= end - srcLen; i++) {
            if (buff[i] != first) {
                continue;
            }
            // found first char, now look for a match
            int myPos = i + 1;
            for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
                if (buff[myPos++] != src.charAt(srcPos++)) {
                    break;
                }
                if (srcPos == srcEnd) {
                    return i - start; // found it
                }
            }
        }
        return -1;
    }

    // -------------------- Hash code --------------------

    // normal hash.
    public int hash() {
        return hashBytes(buff, start, end - start);
    }

    // hash ignoring case
    public int hashIgnoreCase() {
        return hashBytesIC(buff, start, end - start);
    }

    public static boolean equals(final byte[] b1, final int b1Offs, final int b1Len, final byte[] b2, final int b2Offs, final int b2Len) {
        // XXX ENCODING - this only works if encoding is UTF8-compat
        // ( ok for tomcat, where we compare ascii - header names, etc )!!!

        if (b1Len != b2Len) {
            return false;
        }

        if (b1 == b2) {
            return true;
        }

        if (b1 == null || b2 == null) {
            return false;
        }

        for (int i = 0; i < b1Len; i++) {
            if (b1[i + b1Offs] != b2[i + b2Offs]) {
                return false;
            }
        }
        return true;
    }

    public static boolean equals(final byte[] b, int offs, final int len, final String s) {
        // XXX ENCODING - this only works if encoding is UTF8-compat
        // ( ok for tomcat, where we compare ascii - header names, etc )!!!

        if (b == null || len != s.length()) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            if (b[offs++] != s.charAt(i)) {
                return false;
            }
        }
        return true;

    }

    public static boolean equalsIgnoreCase(final byte[] b1, final int b1Offs, final int b1Len, final byte[] b2, final int b2Offs, final int b2Len) {
        // XXX ENCODING - this only works if encoding is UTF8-compat
        // ( ok for tomcat, where we compare ascii - header names, etc )!!!

        if (b1Len != b2Len) {
            return false;
        }

        if (b1 == b2) {
            return true;
        }

        if (b1 == null || b2 == null) {
            return false;
        }

        for (int i = 0; i < b1Len; i++) {
            if (Ascii.toLower(b1[i + b1Offs]) != Ascii.toLower(b2[i + b2Offs])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Compares the message bytes to the specified String object.
     * 
     * @param s the String to compare
     * @return true if the comparison succeeded, false otherwise
     */
    public static boolean equalsIgnoreCase(final byte[] b, final int offset, final int len, final String s) {
        if (len != s.length()) {
            return false;
        }
        int boff = offset;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(b[boff++]) != Ascii.toLower(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * Compares the buffer chunk to the specified byte array representing lower-case ASCII characters.
     *
     * @param buffer the byte[] to compare
     * @param start buffer start
     * @param end buffer end
     * @param cmpTo byte[] to compare against
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.3
     */
    public static boolean equalsIgnoreCaseLowerCase(final byte[] buffer, final int start, final int end, final byte[] cmpTo) {
        final int len = end - start;
        if (len != cmpTo.length) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(buffer[i + start]) != cmpTo[i]) {
                return false;
            }
        }

        return true;
    }

    public static boolean startsWith(final byte[] buffer, final int start, final int end, final byte[] cmpTo) {
        final int len = end - start;

        if (len < cmpTo.length) {
            return false;
        }

        for (int i = 0; i < cmpTo.length; i++) {
            if (buffer[start + i] != cmpTo[i]) {
                return false;
            }
        }

        return true;
    }

    private static int hashBytes(byte[] buff, int start, int bytesLen) {
        int max = start + bytesLen;
        int code = 0;
        for (int i = start; i < max; i++) {
            code = code * 31 + buff[i];
        }
        return code;
    }

    private static int hashBytesIC(byte[] bytes, int start, int bytesLen) {
        int max = start + bytesLen;
        int code = 0;
        for (int i = start; i < max; i++) {
            code = code * 31 + Ascii.toLower(bytes[i]);
        }
        return code;
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * 
     * @param c the character
     * @param starting The start position
     */
    @Override
    public int indexOf(char c, int starting) {
        int ret = indexOf(buff, start + starting, end, c);
        return ret >= start ? ret - start : -1;
    }

    public static int indexOf(byte bytes[], int off, int end, char qq) {
        // Works only for UTF
        while (off < end) {
            byte b = bytes[off];
            if (b == qq) {
                return off;
            }

            off++;
        }
        return -1;
    }

    /**
     * Find a character, no side effects.
     * 
     * @return index of char if found, -1 if not
     */
    public static int findChar(byte buf[], int start, int end, char c) {
        byte b = (byte) c;
        int offset = start;
        while (offset < end) {
            if (buf[offset] == b) {
                return offset;
            }
            offset++;
        }
        return -1;
    }

    /**
     * Find a character, no side effects.
     * 
     * @return index of char if found, -1 if not
     */
    public static int findChars(byte buf[], int start, int end, byte c[]) {
        int clen = c.length;
        int offset = start;
        while (offset < end) {
            for (int i = 0; i < clen; i++) {
                if (buf[offset] == c[i]) {
                    return offset;
                }
            }
            offset++;
        }
        return -1;
    }

    /**
     * Find the first character != c
     * 
     * @return index of char if found, -1 if not
     */
    public static int findNotChars(byte buf[], int start, int end, byte c[]) {
        int clen = c.length;
        int offset = start;
        boolean found;

        while (offset < end) {
            found = true;
            for (int i = 0; i < clen; i++) {
                if (buf[offset] == c[i]) {
                    found = false;
                    break;
                }
            }
            if (found) { // buf[offset] != c[0..len]
                return offset;
            }
            offset++;
        }
        return -1;
    }

    /**
     * Convert specified String to a byte array.
     *
     * @param value to convert to byte array
     * @return the byte array value
     */
    public static byte[] convertToBytes(String value) {
        byte[] result = new byte[value.length()];
        for (int i = 0; i < value.length(); i++) {
            result[i] = (byte) value.charAt(i);
        }
        return result;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy