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

com.github.shyiko.mysql.binlog.io.ByteArrayInputStream Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Stanley Shyiko
 *
 * 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 com.github.shyiko.mysql.binlog.io;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;

/**
 * @author Stanley Shyiko
 */
public class ByteArrayInputStream extends InputStream {

    private InputStream inputStream;
    private int peek = -1;
    private int pos, markPosition;
    private int blockLength = -1;
    private int initialBlockLength = -1;

    public ByteArrayInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
        this.pos = 0;
    }

    public ByteArrayInputStream(byte[] bytes) {
        this(new java.io.ByteArrayInputStream(bytes));
    }

    /**
     * Read int written in little-endian format.
	 * @param length length of the integer to read
	 * @throws IOException in case of EOF
	 * @return the integer from the binlog
     */
    public int readInteger(int length) throws IOException {
        int result = 0;
        for (int i = 0; i < length; ++i) {
            result |= (this.read() << (i << 3));
        }
        return result;
    }

    /**
     * Read long written in little-endian format.
	 * @param length length of the long to read
	 * @throws IOException in case of EOF
	 * @return the long from the binlog
     */
    public long readLong(int length) throws IOException {
        long result = 0;
        for (int i = 0; i < length; ++i) {
            result |= (((long) this.read()) << (i << 3));
        }
        return result;
    }

    /**
     * Read fixed length string.
	 * @param length length of string to read
	 * @throws IOException in case of EOF
	 * @return string
     */
    public String readString(int length) throws IOException {
        return new String(read(length));
    }

    /**
     * Read variable-length string. Preceding packed integer indicates the length of the string.
	 * @throws IOException in case of EOF
	 * @return string
     */
    public String readLengthEncodedString() throws IOException {
        return readString(readPackedInteger());
    }

    /**
     * Read variable-length string. End is indicated by 0x00 byte.
	 * @throws IOException in case of EOF
	 * @return string
     */
    public String readZeroTerminatedString() throws IOException {
        ByteArrayOutputStream s = new ByteArrayOutputStream();
        for (int b; (b = this.read()) != 0; ) {
            s.writeInteger(b, 1);
        }
        return new String(s.toByteArray());
    }

    public byte[] read(int length) throws IOException {
        byte[] bytes = new byte[length];
        fill(bytes, 0, length);
        return bytes;
    }

    public void fill(byte[] bytes, int offset, int length) throws IOException {
        int remaining = length;
        while (remaining != 0) {
            int read = read(bytes, offset + length - remaining, remaining);
            if (read == -1) {
                throw new EOFException(
                    String.format("Failed to read remaining %d of %d bytes from position %d. Block length: %d. Initial block length: %d.",
                        remaining, length, pos, blockLength, initialBlockLength)
                );
            }
            remaining -= read;
        }
    }

    public BitSet readBitSet(int length, boolean bigEndian) throws IOException {
        // according to MySQL internals the amount of storage required for N columns is INT((N+7)/8) bytes
        byte[] bytes = read((length + 7) >> 3);
        bytes = bigEndian ? bytes : reverse(bytes);
        BitSet result = new BitSet();
        for (int i = 0; i < length; i++) {
            if ((bytes[i >> 3] & (1 << (i % 8))) != 0) {
                result.set(i);
            }
        }
        return result;
    }

    private byte[] reverse(byte[] bytes) {
        for (int i = 0, length = bytes.length >> 1; i < length; i++) {
            int j = bytes.length - 1 - i;
            byte t = bytes[i];
            bytes[i] = bytes[j];
            bytes[j] = t;
        }
        return bytes;
    }

    /**
     * @see #readPackedNumber()
	 * @throws IOException in case of malformed number, eof, null, or long
	 * @return integer
     */
    public int readPackedInteger() throws IOException {
        Number number = readPackedNumber();
        if (number == null) {
            throw new IOException("Unexpected NULL where int should have been");
        }
        if (number.longValue() > Integer.MAX_VALUE) {
            throw new IOException("Stumbled upon long even though int expected");
        }
        return number.intValue();
    }

    /**
     * @see #readPackedNumber()
     * @throws IOException in case of malformed number, eof, null
     * @return long
     */
    public long readPackedLong() throws IOException {
        Number number = readPackedNumber();
        if (number == null) {
            throw new IOException("Unexpected NULL where long should have been");
        }
        return number.longValue();
    }

    /**
     * Format (first-byte-based):
* 0-250 - The first byte is the number (in the range 0-250). No additional bytes are used.
* 251 - SQL NULL value
* 252 - Two more bytes are used. The number is in the range 251-0xffff.
* 253 - Three more bytes are used. The number is in the range 0xffff-0xffffff.
* 254 - Eight more bytes are used. The number is in the range 0xffffff-0xffffffffffffffff. * @throws IOException in case of malformed number or EOF * @return long or null */ public Number readPackedNumber() throws IOException { int b = this.read(); if (b < 251) { return b; } else if (b == 251) { return null; } else if (b == 252) { return (long) readInteger(2); } else if (b == 253) { return (long) readInteger(3); } else if (b == 254) { return readLong(8); } throw new IOException("Unexpected packed number byte " + b); } @Override public int available() throws IOException { if (blockLength != -1) { return blockLength; } return inputStream.available(); } public int peek() throws IOException { if (peek == -1) { peek = readWithinBlockBoundaries(); } return peek; } @Override public int read() throws IOException { int result; if (peek == -1) { result = readWithinBlockBoundaries(); } else { result = peek; peek = -1; } if (result == -1) { throw new EOFException(String.format("Failed to read next byte from position %d", this.pos)); } this.pos += 1; return result; } private int readWithinBlockBoundaries() throws IOException { if (blockLength != -1) { if (blockLength == 0) { return -1; } blockLength--; } return inputStream.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } if (peek != -1) { b[off] = (byte) peek; off += 1; len -= 1; } int read = readWithinBlockBoundaries(b, off, len); if (read > 0) { this.pos += read; } if (peek != -1) { peek = -1; read = read <= 0 ? 1 : read + 1; } return read; } private int readWithinBlockBoundaries(byte[] b, int off, int len) throws IOException { if (blockLength == -1) { return inputStream.read(b, off, len); } else if (blockLength == 0) { return -1; } int read = inputStream.read(b, off, Math.min(len, blockLength)); if (read > 0) { blockLength -= read; } return read; } @Override public void close() throws IOException { inputStream.close(); } public void enterBlock(int length) { this.blockLength = length < -1 ? -1 : length; this.initialBlockLength = length; } public void skipToTheEndOfTheBlock() throws IOException { if (blockLength != -1) { skip(blockLength); blockLength = -1; } } public int getPosition() { return pos; } @Override public synchronized void mark(int readlimit) { markPosition = pos; inputStream.mark(readlimit); } @Override public boolean markSupported() { return inputStream.markSupported(); } @Override public synchronized void reset() throws IOException { pos = markPosition; inputStream.reset(); } /** * This method implements fast-forward skipping in the stream. * It can be used if and only if the underlying stream is fully available till its end. * In other cases the regular {@link #skip(long)} method must be used. * * @param n - number of bytes to skip * @return number of bytes skipped * @throws IOException */ public synchronized long fastSkip(long n) throws IOException { long skipOf = n; if (blockLength != -1) { skipOf = Math.min(blockLength, skipOf); blockLength -= skipOf; if (blockLength == 0) { blockLength = -1; } } pos += (int) skipOf; return inputStream.skip(skipOf); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy