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

io.snice.buffer.impl.DefaultImmutableBuffer Maven / Gradle / Ivy

package io.snice.buffer.impl;

import com.google.polo.pairing.HexDump;
import io.snice.buffer.Buffer;
import io.snice.buffer.ByteNotFoundException;
import io.snice.buffer.ReadableBuffer;
import io.snice.buffer.WritableBuffer;
import io.snice.net.IPv4;
import io.snice.preconditions.PreConditions;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import static io.snice.preconditions.PreConditions.assertArgument;
import static io.snice.preconditions.PreConditions.assertArray;

/**
 * The default implementation of our immutable buffer and is truly 100% immutable.
 */
public class DefaultImmutableBuffer implements Buffer {

    public static Buffer of(final byte[] buffer) {
        assertArray(buffer);
        if (buffer.length == 0) {
            return EmptyBuffer.EMPTY;
        }

        return new DefaultImmutableBuffer(buffer, 0, buffer.length);
    }

    public static Buffer of(final byte[] buffer, final int offset, final int length) {
        assertArray(buffer, offset, length);
        if (buffer.length == 0) {
            return EmptyBuffer.EMPTY;
        }

        return new DefaultImmutableBuffer(buffer, offset, offset + length);
    }

    /**
     * The actual buffer
     */
    private final byte[] buffer;

    /**
     * Used when slicing out portions of the buffer.
     * The lower boundary is inclusive.
     */
    private final int lowerBoundary;

    /**
     * The upper boundary of the "window" of data this buffer is allowed to "see".
     */
    private final int upperBoundary;

    private DefaultImmutableBuffer(final byte[] buffer, final int lowerBoundary, final int upperBoundary) {
        this.buffer = buffer;
        this.lowerBoundary = lowerBoundary;
        this.upperBoundary = upperBoundary;
    }

    @Override
    public int countWhiteSpace(final int startIndex) {
        checkIndex(lowerBoundary + startIndex);
        int count = 0;
        for (int i = lowerBoundary + startIndex; i < upperBoundary; ++i) {
            if (buffer[i] != SP && buffer[i] != HTAB) {
                break;
            }
            ++count;
        }

        return count;
    }


    @Override
    public Buffer toBuffer() {
        return this;
    }

    @Override
    public byte[] getContent() {
        final int capacity = capacity();
        final var content = new byte[capacity];
        System.arraycopy(buffer, lowerBoundary, content, 0, capacity);
        return content;
    }

    @Override
    public ReadableBuffer toReadableBuffer() {
        return DefaultReadableBuffer.of(this);
    }

    @Override
    public WritableBuffer toWritableBuffer() {
        final int capacity = capacity();
        final byte[] copy = new byte[capacity];
        System.arraycopy(buffer, lowerBoundary, copy, 0, capacity);
        return WritableBuffer.of(copy);
    }

    @Override
    public int indexOfSingleCRLF() {
        throw new RuntimeException("Not implemented yet");
    }

    @Override
    public Buffer indexOfDoubleCRLF() {
        throw new RuntimeException("Not implemented yet");
    }

    @Override
    public boolean isEmpty() {
        return capacity() == 0;
    }

    @Override
    public int capacity() {
        return this.upperBoundary - this.lowerBoundary;
    }

    @Override
    public int indexdOfSafe(final int maxBytes, final byte... bytes) throws IllegalArgumentException {
        throw new RuntimeException("Not implemented yet");
    }

    /**
     * Just because the underlying buffer is of a certain size doesn't mean that all of those
     * bytes are available to this particular slice.
     *
     * @return the total readable bytes, which is the number of bytes in our "window"
     */
    private int getReadableBytes() {
        return upperBoundary - lowerBoundary;
    }


    public static boolean hasReadableBytes() {
        // when creating this buffer we will check if the passed in
        // array is empty and if so, we'll actually return an empty buffer
        // instead so this is safe.
        return true;
    }

    @Override
    public int indexOf(final int maxBytes, final byte... bytes) throws ByteNotFoundException, IllegalArgumentException {
        return indexOf(0, maxBytes, bytes);
    }

    @Override
    public int indexOf(final int startIndex, final int maxBytes, final byte... bytes) throws ByteNotFoundException, IllegalArgumentException {
        checkIndex(lowerBoundary + startIndex);
        assertArgument(maxBytes > 0, "The max bytes must be at least 1");
        assertArgument(bytes.length > 0, "No bytes specified. Not sure what you want me to look for");

        final int capacity = capacity();
        // remember that the start index is zero based off of the lower boundary.
        // however, the getByte method takes this into account, which is why
        // we don't do it here.
        int index = startIndex;

        while ((index < capacity) && (maxBytes > index)) {
            if (isByteInArray(getByte(index), bytes)) {
                return index;
            }
            ++index;
        }

        if (index - startIndex >= maxBytes) {
            throw new ByteNotFoundException(capacity, bytes);
        }

        return -1;
    }

    @Override
    public void writeTo(final OutputStream out) throws IOException {
        final int length = getReadableBytes();
        out.write(buffer, lowerBoundary, length);
    }

    @Override
    public void writeTo(final WritableBuffer out) {
        final int length = getReadableBytes();
        out.write(buffer, lowerBoundary, length);
    }

    @Override
    public int indexOf(final byte b) throws ByteNotFoundException, IllegalArgumentException {
        return indexOf(0, 4096, b);
    }

    @Override
    public int countOccurences(final int startIndex, final int maxBytes, final byte b) throws IllegalArgumentException {
        checkIndex(lowerBoundary + startIndex);
        final int capacity = capacity();
        final int stop = Math.min(lowerBoundary + startIndex + maxBytes, lowerBoundary + capacity);
        int count = 0;

        for (int i = lowerBoundary + startIndex; i < stop; ++i) {
            if (buffer[i] == b) {
                ++count;
            }
        }

        return count;
    }

    @Override
    public Buffer slice(final int start, final int stop) throws IndexOutOfBoundsException, IllegalArgumentException {
        PreConditions.assertArgument(start >= 0, "The start index must be greater than zero");
        PreConditions.assertArgument(stop >= start, "The stop index (" + stop + ") must be greater or equal " +
                "to the start (" + start + ") index");
        if (start == stop) {
            return EmptyBuffer.EMPTY;
        }
        checkIndex(lowerBoundary + start);
        checkIndex(lowerBoundary + stop - 1);
        final int upperBoundary = lowerBoundary + stop;
        return new DefaultImmutableBuffer(buffer, lowerBoundary + start, upperBoundary);
    }

    @Override
    public Buffer slice(final int stop) {
        return slice(0, stop);
    }

    @Override
    public Buffer slice() {
        return this;
    }

    @Override
    public byte getByte(final int index) throws IndexOutOfBoundsException {
        checkIndex(lowerBoundary + index);
        return buffer[lowerBoundary + index];
    }

    @Override
    public int getInt(final int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 3);
        return Buffer.signedInt(buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3]);
    }

    @Override
    public String toIPv4String(final int index) {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 3);
        return IPv4.convertToStringIP(buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3]);
    }

    @Override
    public long getLong(final int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 7);
        return Buffer.signedLong(buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4], buffer[i + 5], buffer[i + 6], buffer[i + 7]);
    }

    public long getLongFromFiveOctets(int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 4);
        return Buffer.signedLong(buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4]);
    }

    @Override
    public int getIntFromThreeOctets(final int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 2);
        return Buffer.signedInt(buffer[i], buffer[i + 1], buffer[i + 2]);
    }

    @Override
    public long getUnsignedInt(final int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 3);
        return Buffer.unsignedInt(buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3]);
    }

    @Override
    public short getShort(final int index) throws IndexOutOfBoundsException {
        final int i = lowerBoundary + index;
        checkIndex(i);
        checkIndex(i + 1);

        // big endian
        return (short) (buffer[i] << 8 | buffer[i + 1] & 0xFF);

        // little endian
        // return (short) (this.buffer[i] & 0xFF | this.buffer[i + 1] << 8);
    }

    @Override
    public int getUnsignedShort(final int index) throws IndexOutOfBoundsException {
        return getShort(index) & 0xFFFF;
    }

    @Override
    public short getUnsignedByte(final int index) throws IndexOutOfBoundsException {
        return (short) (getByte(index) & 0xFF);
    }

    @Override
    public int parseToInt() throws NumberFormatException {
        return parseToInt(10);
    }

    @Override
    public boolean endsWith(final byte[] content) throws IllegalArgumentException {
        assertArray(content);
        assertArgument(content.length > 0, "The byte-array cannot be empty");
        if (content.length > capacity()) {
            return false;
        }

        final int length = content.length;
        for (int i = 0; i < length; ++i) {
            if (content[i] != buffer[upperBoundary - length + i]) {
                return false;
            }
        }

        return true;
    }

    @Override
    public boolean endsWith(final byte b) throws IllegalArgumentException {
        return buffer[upperBoundary - 1] == b;
    }

    @Override
    public boolean endsWith(final byte b1, final byte b2) throws IllegalArgumentException {
        if (capacity() < 2) {
            return false;
        }

        return buffer[upperBoundary - 2] == b1
                && buffer[upperBoundary - 1] == b2;
    }

    @Override
    public boolean endsWith(final byte b1, final byte b2, final byte b3) throws IllegalArgumentException {
        if (capacity() < 3) {
            return false;
        }

        return buffer[upperBoundary - 3] == b1
                && buffer[upperBoundary - 2] == b2
                && buffer[upperBoundary - 1] == b3;
    }

    @Override
    public boolean endsWith(final byte b1, final byte b2, final byte b3, final byte b4) throws IllegalArgumentException {
        if (capacity() < 4) {
            return false;
        }

        return buffer[upperBoundary - 4] == b1
                && buffer[upperBoundary - 3] == b2
                && buffer[upperBoundary - 2] == b3
                && buffer[upperBoundary - 1] == b4;
    }

    @Override
    public String dumpAsHex() {
        return HexDump.dumpHexString(buffer, lowerBoundary, upperBoundary - lowerBoundary);
    }

    @Override
    public String toHexString(boolean prefix) {
        return HexDump.toHexString(prefix, buffer, lowerBoundary, upperBoundary - lowerBoundary);
    }

    @Override
    public Buffer clone() {
        return this;
    }

    @Override
    public boolean equalsIgnoreCase(final Object other) {
        return internalEquals(true, other);
    }

    @Override
    public String toString() {
        return toUTF8String();
    }

    @Override
    public String toUTF8String() {
        try {
            final int length = getReadableBytes();
            return new String(buffer, lowerBoundary, length, "UTF-8");
        } catch (final UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int result = 1;
        for (int i = lowerBoundary; i < upperBoundary; ++i) {
            result = 31 * result + buffer[i];
        }
        return result;
    }

    @Override
    public boolean equals(final Object other) {
        return internalEquals(false, other);
    }

    /**
     * Due to the decision to not implement the default buffer vs the readable version using
     * inheritance but rather through delegation we end up in some unfortunate situations like
     * the one below. Perhaps I should have stuck with inheritance instead even though I do think
     * it overall may potentially, possibly maybe be a more robust solution by doing it through
     * delegation... but I may be wrong...
     *
     * @param other
     * @return
     */
    private static DefaultImmutableBuffer ensureBuffer(final Object other) throws ClassCastException {
        // if this is already an immutable buffer, as opposed to a readable etc, then this is cheap.
        // if it isn't, for a readable buffer it is cheap as well, for a writable, it will force us
        // to make a clone of the underlying byte-array...
        final Buffer buffer = (Buffer)other;
        return (DefaultImmutableBuffer)buffer.toBuffer();
    }

    private boolean internalEquals(final boolean ignoreCase, final Object other) {
        try {
            if (this == other) {
                return true;
            }
            final DefaultImmutableBuffer b = ensureBuffer(other);
            if (getReadableBytes() != b.getReadableBytes()) {
                return false;
            }

            final int length = getReadableBytes();
            for (int i = 0; i < length; ++i) {
                final byte a1 = buffer[lowerBoundary + i];
                final byte b1 = b.buffer[b.lowerBoundary + i];
                // Do a UTF-8-aware, possibly case-insensitive character match. Only considers
                // case of 7-bit ASCII characters 'a'-'z'. In UTF-8, all bytes of multi-byte
                // characters have thier most signifcant bit set, so they won't be erroneously
                // considered by this algorithm since they won't fall in the range 0x41-0x5a/
                // 0x61-0x7a.

                // This algorithm won't work with UTF-16, and could misfire on malformed UTF-8,
                // e.g. the first byte of a UTF-8 sequence marks the beginning of a multi-byte
                // sequence but the second byte does not have the two high-order bits set to 10.

                // For 7-bit ascii leters, upper and lower-case only differ by one bit,
                // i.e. 'A' is 0x41, and 'a' is 0x61. We need only compare the 5 least
                // signifcant bits.

                if (a1 != b1) {
                    if (ignoreCase &&
                            ((a1 >= 'A' && a1 <= 'Z') || (a1 >= 'a' && a1 <= 'z')) &&
                            ((b1 >= 'A' && b1 <= 'Z') || (b1 >= 'a' && b1 <= 'z')) &&
                            (a1 & 0x1f) == (b1 & 0x1f)) {
                        continue;
                    }
                    return false;
                }
            }

            return true;
        } catch (final NullPointerException | ClassCastException e) {
            return false;
        }
    }

    /**
     * (Copied from the Integer class and slightly altered to read from this
     * buffer instead of a String)
     *
     * Parses the string argument as a signed integer in the radix specified by
     * the second argument. The characters in the string must all be digits of
     * the specified radix (as determined by whether
     * {@link java.lang.Character#digit(char, int)} returns a nonnegative
     * value), except that the first character may be an ASCII minus sign
     * '-' ('\u002D') to indicate a negative
     * value. The resulting integer value is returned.
     * 

* An exception of type NumberFormatException is thrown if any * of the following situations occurs: *

    *
  • The first argument is null or is a string of length * zero. *
  • The radix is either smaller than * {@link java.lang.Character#MIN_RADIX} or larger than * {@link java.lang.Character#MAX_RADIX}. *
  • Any character of the string is not a digit of the specified radix, * except that the first character may be a minus sign '-' ( * '\u002D') provided that the string is longer than length * 1. *
  • The value represented by the string is not a value of type * int. *
*

* Examples:

* *
     * parseInt("0", 10) returns 0
     * parseInt("473", 10) returns 473
     * parseInt("-0", 10) returns 0
     * parseInt("-FF", 16) returns -255
     * parseInt("1100110", 2) returns 102
     * parseInt("2147483647", 10) returns 2147483647
     * parseInt("-2147483648", 10) returns -2147483648
     * parseInt("2147483648", 10) throws a NumberFormatException
     * parseInt("99", 8) throws a NumberFormatException
     * parseInt("Kona", 10) throws a NumberFormatException
     * parseInt("Kona", 27) returns 411787
     * 
* *
* * @param radix * the radix to be used while parsing s. * @return the integer represented by the string argument in the specified * radix. * @exception NumberFormatException * if the String does not contain a parsable * int. */ @Override public final int parseToInt(final int radix) throws NumberFormatException { if (isEmpty()) { throw new NumberFormatException("Buffer is empty, cannot convert it to an integer"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if (radix > Character.MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; boolean negative = false; int i = 0; final int max = getReadableBytes(); final int limit; final int multmin; int digit; if (max > 0) { if (getByte(i) == (byte) '-') { negative = true; limit = Integer.MIN_VALUE; i++; } else { limit = -Integer.MAX_VALUE; } multmin = limit / radix; if (i < max) { digit = Character.digit((char) getByte(i++), radix); if (digit < 0) { throw new NumberFormatException("For input string: \"" + this + "\""); } else { result = -digit; } } while (i < max) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit((char) getByte(i++), radix); if (digit < 0) { throw new NumberFormatException("For input string: \"" + this + "\""); } if (result < multmin) { throw new NumberFormatException("For input string: \"" + this + "\""); } result *= radix; if (result < limit + digit) { throw new NumberFormatException("For input string: \"" + this + "\""); } result -= digit; } } else { throw new NumberFormatException("For input string: \"" + this + "\""); } if (negative) { if (i > 1) { return result; } else { /* Only got "-" */ throw new NumberFormatException("For input string: \"" + this + "\""); } } else { return -result; } } protected static boolean isByteInArray(final byte b, final byte[] bytes) { for (final byte x : bytes) { if (x == b) { return true; } } return false; } /** * Convenience method for checking if we can read at the index * * @param index * @throws IndexOutOfBoundsException */ protected void checkIndex(final int index) throws IndexOutOfBoundsException { final int upperBoundary = lowerBoundary + capacity(); if (index >= upperBoundary) { throw new IndexOutOfBoundsException("Trying to access index " + index + " in a buffer whose upper bound is (exclusive) " + upperBoundary); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy