package rescuecore2.misc;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.EOFException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.Property;
import rescuecore2.messages.Message;
import rescuecore2.registry.Registry;
/**
A bunch of useful tools for encoding and decoding things like integers.
*/
public final class EncodingTools {
/** The size of an INT_32 in bytes. */
public static final int INT_32_SIZE = 4;
/** Charset for encoding/decoding strings. Should always be UTF-8 */
private static final Charset CHARSET = Charset.forName("UTF-8");
/**
Private constructor: this is a utility class.
*/
private EncodingTools() {}
/** Turn off the checkstyle test for magic numbers since we use a lot of them here */
/** CHECKSTYLE:OFF:MagicNumber */
/**
Write a 32-bit integer to an OutputStream, big-endian style.
@param i The integer to write.
@param out The OutputStream to write it to.
@throws IOException If the OutputStream blows up.
*/
public static void writeInt32(int i, OutputStream out) throws IOException {
// Most significant byte first
out.write((byte) (i >> 24) & 0xFF);
out.write((byte) (i >> 16) & 0xFF);
out.write((byte) (i >> 8) & 0xFF);
out.write((byte) i & 0xFF);
}
/**
Write a 32-bit integer to a DataOutput, big-endian style.
@param i The integer to write.
@param out The DataOutput to write it to.
@throws IOException If the DataOutput blows up.
*/
public static void writeInt32(int i, DataOutput out) throws IOException {
// DataOutput writes big-endian
out.write(i);
}
/**
Write a 32-bit integer to a byte array, big-endian style.
@param i The integer to write.
@param out The buffer to write it to.
@param offset Where in the buffer to write it.
*/
public static void writeInt32(int i, byte[] out, int offset) {
// Most significant byte first
out[offset] = (byte) ((i >> 24) & 0xFF);
out[offset + 1] = (byte) ((i >> 16) & 0xFF);
out[offset + 2] = (byte) ((i >> 8) & 0xFF);
out[offset + 3] = (byte) (i & 0xFF);
}
/**
Read a 32-bit integer from an input stream, big-endian style.
@param in The InputStream to read from.
@return The next big-endian, 32-bit integer in the stream.
@throws IOException If the InputStream blows up.
@throws EOFException If the end of the stream is reached.
*/
public static int readInt32(InputStream in) throws IOException {
int first = in.read();
if (first == -1) {
throw new EOFException("Broken input pipe. Read 0 bytes of 4.");
}
int second = in.read();
if (second == -1) {
throw new EOFException("Broken input pipe. Read 1 bytes of 4.");
}
int third = in.read();
if (third == -1) {
throw new EOFException("Broken input pipe. Read 2 bytes of 4.");
}
int fourth = in.read();
if (fourth == -1) {
throw new EOFException("Broken input pipe. Read 3 bytes of 4.");
}
return (first << 24) | (second << 16) | (third << 8) | fourth;
}
/**
Read a 32-bit integer from a DataInput.
@param in The DataInput to read from.
@return The next big-endian, 32-bit integer in the stream.
@throws IOException If the DataInput blows up.
@throws EOFException If the end of the stream is reached.
*/
public static int readInt32(DataInput in) throws IOException {
return in.readInt();
}
/**
Read a 32-bit integer from an input stream, little-endian style.
@param in The InputStream to read from.
@return The next little-endian, 32-bit integer in the stream.
@throws IOException If the InputStream blows up.
@throws EOFException If the end of the stream is reached.
*/
public static int readInt32LE(InputStream in) throws IOException {
int first = in.read();
if (first == -1) {
throw new EOFException("Broken input pipe. Read 0 bytes of 4.");
}
int second = in.read();
if (second == -1) {
throw new EOFException("Broken input pipe. Read 1 bytes of 4.");
}
int third = in.read();
if (third == -1) {
throw new EOFException("Broken input pipe. Read 2 bytes of 4.");
}
int fourth = in.read();
if (fourth == -1) {
throw new EOFException("Broken input pipe. Read 3 bytes of 4.");
}
return (fourth << 24) | (third << 16) | (second << 8) | first;
}
/**
Read a 32-bit integer from a byte array, big-endian style.
@param in The buffer to read from.
@param offset Where to begin reading.
@return The next big-endian, 32-bit integer in the buffer.
*/
public static int readInt32(byte[] in, int offset) {
return (in[offset] << 24) | (in[offset + 1] << 16) | (in[offset + 2] << 8) | (in[offset + 3]);
}
/**
Read a 32-bit integer from a byte array, big-endian style. This is equivalent to calling {@link #readInt32(byte[], int) readInt32(in, 0)}.
@param in The buffer to read from.
@return The first big-endian, 32-bit integer in the buffer.
*/
public static int readInt32(byte[] in) {
return readInt32(in, 0);
}
/**
Write a String to an OutputStream. Strings are always in UTF-8.
@param s The String to write.
@param out The OutputStream to write to.
@throws IOException If the OutputStream blows up.
*/
public static void writeString(String s, OutputStream out) throws IOException {
byte[] bytes = s.getBytes(CHARSET);
writeInt32(bytes.length, out);
out.write(bytes);
}
/**
Write a String to a DataOutput. Strings are always in UTF-8.
@param s The String to write.
@param out The DataOutput to write to.
@throws IOException If the DataOutput blows up.
*/
public static void writeString(String s, DataOutput out) throws IOException {
byte[] bytes = s.getBytes(CHARSET);
writeInt32(bytes.length, out);
out.write(bytes);
}
/**
Write a String to a byte array. Strings are always in UTF-8.
@param s The String to write.
@param out The byte array to write to. Make sure it's big enough!
@param offset The index to start writing from.
*/
public static void writeString(String s, byte[] out, int offset) {
byte[] bytes = s.getBytes(CHARSET);
writeInt32(bytes.length, out, offset);
System.arraycopy(bytes, 0, out, offset + 4, bytes.length);
}
/**
Read a String from an InputStream.
@param in The InputStream to read.
@return The string that was read.
@throws IOException If the InputStream blows up.
@throws EOFException If the end of the stream is reached.
*/
public static String readString(InputStream in) throws IOException {
int length = readInt32(in);
byte[] buffer = new byte[length];
int count = 0;
while (count < length) {
int read = in.read(buffer, count, length - count);
if (read == -1) {
throw new EOFException("Broken input pipe. Read " + count + " bytes of " + length + ".");
}
count += read;
}
return new String(buffer, CHARSET);
}
/**
Read a String from a DataInput.
@param in The DataInput to read.
@return The string that was read.
@throws IOException If the DataInput blows up.
@throws EOFException If the end of the stream is reached.
*/
public static String readString(DataInput in) throws IOException {
int length = readInt32(in);
byte[] buffer = new byte[length];
in.readFully(buffer);
return new String(buffer, CHARSET);
}
/**
Read a String from a byte array. This is equivalent to calling {@link #readString(byte[], int) readString(in, 0)}.
@param in The byte array to read.
@return The string that was read.
*/
public static String readString(byte[] in) {
return readString(in, 0);
}
/**
Read a String from a byte array.
@param in The byte array to read.
@param offset The index in the array to read from.
@return The string that was read.
*/
public static String readString(byte[] in, int offset) {
int length = readInt32(in, offset);
byte[] buffer = new byte[length];
System.arraycopy(in, offset + 4, buffer, 0, length);
return new String(buffer, CHARSET);
}
/**
Read a fixed number of bytes from an InputStream into an array.
@param size The number of bytes to read.
@param in The InputStream to read from.
@return A new byte array containing the bytes.
@throws IOException If the read operation fails.
*/
public static byte[] readBytes(int size, InputStream in) throws IOException {
byte[] buffer = new byte[size];
int total = 0;
while (total < size) {
int read = in.read(buffer, total, size - total);
if (read == -1) {
throw new EOFException("Broken input pipe. Read " + total + " bytes of " + size + ".");
}
total += read;
}
return buffer;
}
/**
Read a fixed number of bytes from a DataInput into an array.
@param size The number of bytes to read.
@param in The DataInput to read from.
@return A new byte array containing the bytes.
@throws IOException If the read operation fails.
*/
public static byte[] readBytes(int size, DataInput in) throws IOException {
byte[] buffer = new byte[size];
in.readFully(buffer);
return buffer;
}
/**
Write a double to an OutputStream.
@param d The double to write.
@param out The OutputStream to write it to.
@throws IOException If the OutputStream blows up.
*/
public static void writeDouble(double d, OutputStream out) throws IOException {
long bits = Double.doubleToLongBits(d);
out.write((byte) (bits >> 56) & 0xFF);
out.write((byte) (bits >> 48) & 0xFF);
out.write((byte) (bits >> 40) & 0xFF);
out.write((byte) (bits >> 32) & 0xFF);
out.write((byte) (bits >> 24) & 0xFF);
out.write((byte) (bits >> 16) & 0xFF);
out.write((byte) (bits >> 8) & 0xFF);
out.write((byte) bits & 0xFF);
}
/**
Write a double to a DataOutput.
@param d The double to write.
@param out The DataOutput to write it to.
@throws IOException If the DataOutput blows up.
*/
public static void writeDouble(double d, DataOutput out) throws IOException {
out.writeDouble(d);
}
/**
Write a double to a byte array.
@param d The double to write.
@param out The buffer to write it to.
@param offset Where in the buffer to write it.
*/
public static void writeDouble(double d, byte[] out, int offset) {
long bits = Double.doubleToLongBits(d);
out[offset + 0] = (byte) ((bits >> 56) & 0xFF);
out[offset + 1] = (byte) ((bits >> 48) & 0xFF);
out[offset + 2] = (byte) ((bits >> 40) & 0xFF);
out[offset + 3] = (byte) ((bits >> 32) & 0xFF);
out[offset + 4] = (byte) ((bits >> 24) & 0xFF);
out[offset + 5] = (byte) ((bits >> 16) & 0xFF);
out[offset + 6] = (byte) ((bits >> 8) & 0xFF);
out[offset + 7] = (byte) (bits & 0xFF);
}
/**
Read a double from an input stream.
@param in The InputStream to read from.
@return The next double in the stream.
@throws IOException If the InputStream blows up.
@throws EOFException If the end of the stream is reached.
*/
public static double readDouble(InputStream in) throws IOException {
long[] data = new long[8];
for (int i = 0; i < data.length; ++i) {
data[i] = in.read();
if (data[i] == -1) {
throw new EOFException("Broken input pipe. Read " + i + " bytes of 8.");
}
}
long result = data[0] << 56
| data[1] << 48
| data[2] << 40
| data[3] << 32
| data[4] << 24
| data[5] << 16
| data[6] << 8
| data[7];
return Double.longBitsToDouble(result);
}
/**
Read a double from a DataInput.
@param in The DataInput to read from.
@return The next double in the stream.
@throws IOException If the DataInput blows up.
@throws EOFException If the end of the stream is reached.
*/
public static double readDouble(DataInput in) throws IOException {
return in.readDouble();
}
/**
Read a double from a byte array.
@param in The buffer to read from.
@param offset Where to begin reading.
@return The next double in the buffer.
*/
public static double readDouble(byte[] in, int offset) {
long[] parts = new long[8];
for (int i = 0; i < 8; ++i) {
parts[i] = in[offset + i];
}
long result = parts[0] << 56
| parts[1] << 48
| parts[2] << 40
| parts[3] << 32
| parts[4] << 24
| parts[5] << 16
| parts[6] << 8
| parts[7];
return Double.longBitsToDouble(result);
}
/**
Read a double from a byte array. This is equivalent to calling {@link #readDouble(byte[], int) readDouble(in, 0)}.
@param in The buffer to read from.
@return The first double in the buffer.
*/
public static double readDouble(byte[] in) {
return readDouble(in, 0);
}
/**
Write a boolean to an OutputStream.
@param b The boolean to write.
@param out The OutputStream to write it to.
@throws IOException If the OutputStream blows up.
*/
public static void writeBoolean(boolean b, OutputStream out) throws IOException {
out.write(b ? 1 : 0);
}
/**
Write a boolean to a DataOutput.
@param b The boolean to write.
@param out The DataOutput to write it to.
@throws IOException If the DataOutput blows up.
*/
public static void writeBoolean(boolean b, DataOutput out) throws IOException {
out.writeBoolean(b);
}
/**
Write a boolean to a byte array.
@param b The boolean to write.
@param out The buffer to write it to.
@param offset Where in the buffer to write it.
*/
public static void writeBoolean(boolean b, byte[] out, int offset) {
out[offset] = (byte)(b ? 1 : 0);
}
/**
Read a boolean from an input stream.
@param in The InputStream to read from.
@return The next boolean in the stream.
@throws IOException If the InputStream blows up.
@throws EOFException If the end of the stream is reached.
*/
public static boolean readBoolean(InputStream in) throws IOException {
int b = in.read();
return b == 1;
}
/**
Read a boolean from a DataInput.
@param in The DataInput to read from.
@return The next boolean in the stream.
@throws IOException If the DataInput blows up.
@throws EOFException If the end of the stream is reached.
*/
public static boolean readBoolean(DataInput in) throws IOException {
return in.readBoolean();
}
/**
Read a boolean from a byte array.
@param in The buffer to read from.
@param offset Where to begin reading.
@return The next boolean in the buffer.
*/
public static boolean readBoolean(byte[] in, int offset) {
return in[offset] == 1;
}
/**
Read a boolean from a byte array. This is equivalent to calling {@link #readBoolean(byte[], int) readBoolean(in, 0)}.
@param in The buffer to read from.
@return The first boolean in the buffer.
*/
public static boolean readBoolean(byte[] in) {
return readBoolean(in, 0);
}
/**
Call InputStream.skip until exactly <code>count</code> bytes have been skipped. If InputStream.skip ever returns a negative number then an EOFException is thrown.
@param in The InputStream to skip.
@param count The number of bytes to skip.
@throws IOException If the bytes cannot be skipped for some reason.
*/
public static void reallySkip(InputStream in, long count) throws IOException {
long done = 0;
while (done < count) {
long next = in.skip(count - done);
if (next < 0) {
throw new EOFException();
}
done += next;
}
}
/**
Call DataInput.skip until exactly <code>count</code> bytes have been skipped. If DataInput.skip ever returns a negative number then an EOFException is thrown.
@param in The DataInput to skip.
@param count The number of bytes to skip.
@throws IOException If the bytes cannot be skipped for some reason.
*/
public static void reallySkip(DataInput in, int count) throws IOException {
int done = 0;
while (done < count) {
int next = in.skipBytes(count - done);
if (next < 0) {
throw new EOFException();
}
done += next;
}
}
/**
Write an entity to a stream.
@param e The entity to write.
@param out The OutputStream to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeEntity(Entity e, OutputStream out) throws IOException {
// Type URN, entityID, size, content
// Gather the content first
ByteArrayOutputStream gather = new ByteArrayOutputStream();
e.write(gather);
byte[] bytes = gather.toByteArray();
// Type URN
writeString(e.getURN(), out);
// EntityID
writeInt32(e.getID().getValue(), out);
// Size
writeInt32(bytes.length, out);
// Content
out.write(bytes);
}
/**
Write an entity to a DataOutput.
@param e The entity to write.
@param out The DataOutput to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeEntity(Entity e, DataOutput out) throws IOException {
// Type URN, entityID, size, content
// Gather the content first
ByteArrayOutputStream gather = new ByteArrayOutputStream();
e.write(gather);
byte[] bytes = gather.toByteArray();
// Type URN
writeString(e.getURN(), out);
// EntityID
writeInt32(e.getID().getValue(), out);
// Size
writeInt32(bytes.length, out);
// Content
out.write(bytes);
}
/**
Read an entity from a stream.
@param in The InputStream to read from.
@return A new Entity, or null if the entity URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Entity readEntity(InputStream in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
int entityID = readInt32(in);
int size = readInt32(in);
byte[] content = readBytes(size, in);
Entity result = Registry.getCurrentRegistry().createEntity(urn, new EntityID(entityID));
if (result != null) {
result.read(new ByteArrayInputStream(content));
}
return result;
}
/**
Read an entity from a DataInput.
@param in The DataInput to read from.
@return A new Entity, or null if the entity URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Entity readEntity(DataInput in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
int entityID = readInt32(in);
int size = readInt32(in);
byte[] content = readBytes(size, in);
Entity result = Registry.getCurrentRegistry().createEntity(urn, new EntityID(entityID));
if (result != null) {
result.read(new ByteArrayInputStream(content));
}
return result;
}
/**
Write a property to a stream.
@param p The property to write.
@param out The OutputStream to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeProperty(Property p, OutputStream out) throws IOException {
// Type
writeString(p.getURN(), out);
writeBoolean(p.isDefined(), out);
if (p.isDefined()) {
ByteArrayOutputStream gather = new ByteArrayOutputStream();
p.write(gather);
byte[] bytes = gather.toByteArray();
// Size
writeInt32(bytes.length, out);
// Data
out.write(bytes);
}
}
/**
Write a property to a DataOutput.
@param p The property to write.
@param out The DataOutput to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeProperty(Property p, DataOutput out) throws IOException {
// Type
writeString(p.getURN(), out);
writeBoolean(p.isDefined(), out);
if (p.isDefined()) {
ByteArrayOutputStream gather = new ByteArrayOutputStream();
p.write(gather);
byte[] bytes = gather.toByteArray();
// Size
writeInt32(bytes.length, out);
// Data
out.write(bytes);
}
}
/**
Read a property from a stream.
@param in The InputStream to read from.
@return A new Property, or null if the property URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Property readProperty(InputStream in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
boolean defined = readBoolean(in);
Property result = Registry.getCurrentRegistry().createProperty(urn);
if (defined) {
int size = readInt32(in);
byte[] content = readBytes(size, in);
if (result != null) {
result.read(new ByteArrayInputStream(content));
}
}
return result;
}
/**
Read a property from a DataInput.
@param in The DataInput to read from.
@return A new Property, or null if the property URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Property readProperty(DataInput in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
boolean defined = readBoolean(in);
Property result = Registry.getCurrentRegistry().createProperty(urn);
if (defined) {
int size = readInt32(in);
byte[] content = readBytes(size, in);
if (result != null) {
result.read(new ByteArrayInputStream(content));
}
}
return result;
}
/**
Write a message to a stream.
@param m The message to write.
@param out The OutputStream to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeMessage(Message m, OutputStream out) throws IOException {
// Type URN, size, content
// Gather the content first
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
m.write(bytes);
byte[] content = bytes.toByteArray();
// Type URN
writeString(m.getURN(), out);
// Size
writeInt32(content.length, out);
// Content
out.write(content);
}
/**
Write a message to a DataOutput.
@param m The message to write.
@param out The DataOutput to write to.
@throws IOException If there is a problem writing to the stream.
*/
public static void writeMessage(Message m, DataOutput out) throws IOException {
// Type URN, size, content
// Gather the content first
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
m.write(bytes);
byte[] content = bytes.toByteArray();
// Type URN
writeString(m.getURN(), out);
// Size
writeInt32(content.length, out);
// Content
out.write(content);
}
/**
Read a message from a stream.
@param in The InputStream to read from.
@return A new Message, or null if the message URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Message readMessage(InputStream in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
int size = readInt32(in);
byte[] content = readBytes(size, in);
Message result = Registry.getCurrentRegistry().createMessage(urn, new ByteArrayInputStream(content));
return result;
}
/**
Read a message from a DataInput.
@param in The DataInput to read from.
@return A new Message, or null if the message URN is not recognised.
@throws IOException If there is a problem reading from the stream.
*/
public static Message readMessage(DataInput in) throws IOException {
String urn = readString(in);
if ("".equals(urn)) {
return null;
}
int size = readInt32(in);
byte[] content = readBytes(size, in);
Message result = Registry.getCurrentRegistry().createMessage(urn, new ByteArrayInputStream(content));
return result;
}
/** CHECKSTYLE:ON:MagicNumber */
}