+ * All messages consist of a single byte type identifier, followed by a + * payload whose structure depends on the message. + *
+ */ +public interface Message { + /** + * @return The exact number of bytes that this message will use when written + * to a stream. + */ + int getByteCount(); + + /** + * Writes this message to the given output stream. + * @param o The output stream to write to. + * @throws IOException If an error occurs while writing. + */ + void write(DataOutputStream o) throws IOException; + + /** + * Reads all of this message's properties from the given input stream. + *+ * The single byte type identifier has already been read. + *
+ * @param i The input stream to read from. + * @throws IOException If an error occurs while reading. + */ + void read(DataInputStream i) throws IOException; + + // Utility methods. + + /** + * Gets the number of bytes that the given string will occupy when it is + * serialized. + * @param s The string. + * @return The number of bytes used to serialize the string. + */ + default int getByteSize(String s) { + return Integer.BYTES + s.getBytes(StandardCharsets.UTF_8).length; + } + + /** + * Writes a string to the given output stream using a length-prefixed format + * where an integer length precedes the string's bytes, which are encoded in + * UTF-8. + * @param s The string to write. + * @param o The output stream to write to. + * @throws IOException If the stream could not be written to. + */ + default void writeString(String s, DataOutputStream o) throws IOException { + if (s == null) { + o.writeInt(-1); + } else { + o.writeInt(s.length()); + o.write(s.getBytes(StandardCharsets.UTF_8)); + } + } + + /** + * Reads a string from the given input stream, using a length-prefixed + * format, where an integer length precedes the string's bytes, which are + * encoded in UTF-8. + * @param i The input stream to read from. + * @return The string which was read. + * @throws IOException If the stream could not be read, or if the string is + * malformed. + */ + default String readString(DataInputStream i) throws IOException { + int length = i.readInt(); + if (length == -1) return null; + byte[] data = new byte[length]; + int read = i.read(data); + if (read != length) throw new IOException("Not all bytes of a string of length " + length + " could be read."); + return new String(data, StandardCharsets.UTF_8); + } +} diff --git a/core/src/main/java/nl/andrewl/concord_core/msg/Serializer.java b/core/src/main/java/nl/andrewl/concord_core/msg/Serializer.java new file mode 100644 index 0000000..4a45756 --- /dev/null +++ b/core/src/main/java/nl/andrewl/concord_core/msg/Serializer.java @@ -0,0 +1,55 @@ +package nl.andrewl.concord_core.msg; + +import nl.andrewl.concord_core.msg.types.Chat; +import nl.andrewl.concord_core.msg.types.Identification; +import nl.andrewl.concord_core.msg.types.ServerWelcome; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is responsible for reading and writing messages from streams. + */ +public class Serializer { + private static final Map