com.github.shyiko.mysql.binlog.io.ByteArrayInputStream Maven / Gradle / Ivy
/*
* 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