package javastory.io;
import java.awt.Point;
import com.google.common.base.Preconditions;
/**
* Provides forward-only readBytes access to a game packet.
*
* @author shoftee
*/
public final class PacketReader {
private int position = 0;
private final byte[] buffer;
/**
* Class constructor
*
* @param buffer
* the byte array containing the packet data.
*/
public PacketReader(final byte[] buffer) {
this.buffer = buffer;
}
/**
* Advances a number of bytes ahead.
*
* @param count
* the number of bytes to advance.
*
* @return the position at which the advance started.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
private int checkedAdvance(final int count) throws PacketFormatException {
final int oldPosition = this.position;
this.position += count;
if (this.position >= this.buffer.length) {
throw new PacketFormatException();
}
return oldPosition;
}
/**
* Reads a byte as a boolean value.
*
* @return whether the byte was equal to 1.
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public boolean readBoolean() throws PacketFormatException {
return this.buffer[this.checkedAdvance(1)] == 1;
}
/**
* Reads a byte.
*
* @return the byte that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public byte readByte() throws PacketFormatException {
return this.buffer[this.checkedAdvance(1)];
}
/**
* Checks how many bytes are remaining in the stream.
*
* @return the number of bytes until the end of the stream.
*/
public long remaining() {
return this.buffer.length - this.position;
}
/**
* Gets the current position of the stream.
*/
public long getPosition() {
return this.position;
}
/**
* Advances a specified number of bytes ahead.
*
* @param count
* the number of bytes to skip.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*
* @throws IllegalArgumentException
* if <code>count</code> is negative.
*/
public void skip(final int count) throws PacketFormatException {
Preconditions.checkArgument(count >= 0);
this.checkedAdvance(count);
}
private long readReverse(final int count) throws PacketFormatException {
final int start = this.checkedAdvance(count);
final int end = this.position;
long number = 0;
for (int i = start; i < end; i++) {
number <<= 8;
number |= this.buffer[i] & 0xFF;
}
return number;
}
/**
* Reads a 32-bit integer.
*
* @return the integer that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final int readInt() throws PacketFormatException {
return (int) (this.readReverse(4) & 0xFFFFFFFF);
}
/**
* Reads a 32-bit potentially unsigned integer.
*
* @return the integer that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final long readUnsignedInt() throws PacketFormatException {
return this.readReverse(4) & 0xFFFFFFFF;
}
/**
* Reads a 16-bit integer.
*
* @return the integer that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final short readShort() throws PacketFormatException {
return (short) (this.readReverse(2) & 0xFFFF);
}
/**
* Reads a 16-bit potentially unsigned integer.
*
* @return the integer that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final int readUnsignedShort() throws PacketFormatException {
return (int) (this.readReverse(2) & 0xFFFF);
}
/**
* Reads a single character.
*
* @return the character that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final char readChar() throws PacketFormatException {
return (char) this.readShort();
}
/**
* Reads a 64-bit integer.
*
* @return the integer that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final long readLong() throws PacketFormatException {
return this.readReverse(8);
}
/**
* Reads a 32-bit IEEE-754 floating-point "single float" decimal.
*
* @return the decimal that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final float readFloat() throws PacketFormatException {
final int bits = (int) this.readReverse(4);
return Float.intBitsToFloat(bits);
}
/**
* Reads a 64-bit IEEE 754 floating-point "double format" decimal..
*
* @return the decimal that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final double readDouble() throws PacketFormatException {
final long bits = this.readReverse(8);
return Double.longBitsToDouble(bits);
}
/**
* Reads an ASCII string with the specified length.
*
* @param length
* the length of the string.
* @return the string that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*
* @throws IllegalArgumentException
* if <code>length</code> is negative.
*/
public final String readString(final int length) throws PacketFormatException {
Preconditions.checkArgument(length >= 0);
final StringBuilder b = new StringBuilder(length);
final int start = this.checkedAdvance(length);
final int end = this.position;
for (int i = start; i < end; i++) {
b.append((char) this.buffer[i]);
}
return b.toString();
}
/**
* Reads a null-terminated string.
*
* @return the string that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final String readNullTerminatedString() throws PacketFormatException {
final int start = this.position;
final StringBuilder builder = new StringBuilder();
int i = start;
while (this.buffer[i = this.checkedAdvance(1)] != 0x00) {
builder.append((char) this.buffer[i]);
}
return builder.toString();
}
/**
* Reads a length-prefixed ASCII string.
*
* @return the string that was readBytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final String readLengthPrefixedString() throws PacketFormatException {
final int length = this.readShort();
final String string = this.readString(length);
return string;
}
/**
* Reads a two-dimensional point consisting of two 16-bit integers.
*
* @return the Point object constructed from the data.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*/
public final Point readVector() throws PacketFormatException {
final int x = this.readShort();
final int y = this.readShort();
return new Point(x, y);
}
/**
* Reads a number of bytes off the stream.
*
* @param count
* The number of bytes to readBytes.
* @return An array of the readBytes bytes.
*
* @throws PacketFormatException
* if the advance put the current position past the end of the
* buffer.
*
* @throws IllegalArgumentException
* if <code>count</code> is negative.
*/
public final byte[] readBytes(final int count) throws PacketFormatException {
Preconditions.checkArgument(count >= 0);
final int start = this.checkedAdvance(count);
final byte[] bytes = new byte[count];
System.arraycopy(this.buffer, start, bytes, 0, count);
return bytes;
}
}