package com.netthreads.osc.common.domain; import io.netty.buffer.ByteBuf; import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; /** * Encoder for OSCMessage and OSCBundle. * */ public class OSCEncoder { private static final BigDecimal MILLISECONDS_FROM_1900_TO_1970 = new BigDecimal("2208988800000"); private static final byte ZERO = 0; private static final int PAD_BYTES = 4; /** * Encode appropriately. * * TODO: Think about moving the encoding back into the domain objects as this is not good design. * * @param message * @param buffer */ private void encode(OSCPacket message, ByteBuf buffer) { if (message instanceof OSCBundle) { encode((OSCBundle) message, buffer); } else { encode((OSCMessage) message, buffer); } } /** * Encode message. * * @param oscMessage * @param buffer */ public void encode(OSCMessage oscMessage, ByteBuf buffer) { // Write address writeAddress(oscMessage.getAddress(), buffer); // Types writeTypes(oscMessage.getArguments(), buffer); // Arguments writeArguments(oscMessage.getArguments(), buffer); } /** * Encode bundle. * * @param oscBundle * @param buffer */ public void encode(OSCBundle oscBundle, ByteBuf buffer) { buffer.writeBytes(OSCString.$().get(OSCDefinition.MESSAGE_BUNDLE_START)); buffer.writeByte(ZERO); // Time-stamp, note : java time runs from 1970 onwards. long millisecs = oscBundle.getTimeTag() + MILLISECONDS_FROM_1900_TO_1970.longValue(); buffer.writeLong(millisecs); // --------------------------------------------------------------- // For each packet stuff bytes into buffer. // --------------------------------------------------------------- for (OSCPacket message : oscBundle.getMessages()) { // --------------------------------------------------------------- // Make a note of position to write size // --------------------------------------------------------------- int sizePos = buffer.writerIndex(); // Insert holder size. buffer.writeInt(0); // --------------------------------------------------------------- // Make a note of start of contents // --------------------------------------------------------------- int startPos = buffer.writerIndex(); // Encode OSC packet this.encode(message, buffer); // Mark buffer.markWriterIndex(); // Insert size of message int writerIndex = buffer.writerIndex(); int size = writerIndex - startPos; buffer.writerIndex(sizePos); buffer.writeInt(size); // Reset to mark. buffer.resetWriterIndex(); } } /** * Write address. * * @param address * @param buffer */ private void writeAddress(String address, ByteBuf buffer) { // Note: We cache ALL address strings. buffer.writeBytes(OSCString.$().get(address)); buffer.writeByte(ZERO); pad(buffer, PAD_BYTES); } /** * Write types. * * @param buffer * The outgoing buffer. */ private void writeTypes(List<Object> arguments, ByteBuf buffer) { buffer.writeByte((byte) ','); for (Object argument : arguments) { writeType(argument, buffer); } buffer.writeByte(ZERO); pad(buffer, PAD_BYTES); } /** * Write type. * * @param argument * @param buffer */ public void writeType(Object argument, ByteBuf buffer) { if (argument instanceof String) { buffer.writeByte((byte) OSCDefinition.TYPE_STRING); } else if (argument instanceof Float) { buffer.writeByte((byte) OSCDefinition.TYPE_FLOAT); } else if (argument instanceof Integer) { buffer.writeByte((byte) OSCDefinition.TYPE_INT); } else if (argument instanceof BigInteger) { buffer.writeByte((byte) OSCDefinition.TYPE_LONG); } else if (argument instanceof byte[]) { buffer.writeByte((byte) OSCDefinition.TYPE_BLOB); } else if (argument instanceof Boolean) { buffer.writeByte((byte) (((Boolean) argument) ? OSCDefinition.TYPE_TRUE : OSCDefinition.TYPE_FALSE)); } else if (argument instanceof Object[]) { Object[] arrayArguments = (Object[]) argument; buffer.writeByte((byte) OSCDefinition.TYPE_ARRAY_START); for (Object arrayArgument : arrayArguments) { writeType(arrayArgument, buffer); } buffer.writeByte((byte) OSCDefinition.TYPE_ARRAY_END); } } /** * Write arguments. * * @param buffer */ private void writeArguments(List<Object> arguments, ByteBuf buffer) { if (arguments.size() > 0) { for (Object argument : arguments) { if (argument instanceof String) { // Adds to heap? buffer.writeBytes(((String) argument).getBytes()); pad(buffer, PAD_BYTES); } else if (argument instanceof Float) { buffer.writeFloat((Float) argument); } else if (argument instanceof Integer) { buffer.writeInt((Integer) argument); } else if (argument instanceof BigInteger) { buffer.writeLong(((BigInteger) argument).longValue()); } else if (argument instanceof byte[]) { byte[] bytes = (byte[]) argument; buffer.writeInt(bytes.length); buffer.writeBytes((byte[]) argument); pad(buffer, PAD_BYTES); } else if (argument instanceof Boolean) { buffer.writeByte(((Boolean) argument) ? (byte) 0x01 : (byte) 0x00); } } pad(buffer, PAD_BYTES); } } /** * Pad buffer contents out to the nearest 'padByte' bytes. * * @param start * @param buffer */ private void pad(ByteBuf buffer, int padBytes) { int position = buffer.writerIndex(); int remainder = position % padBytes; if (remainder > 0) { int pad = padBytes - remainder; while (pad > 0) { buffer.writeByte(ZERO); pad--; } } } }