SimulationMessage.java
package org.sim0mq.message;
import org.djutils.exceptions.Throw;
import org.djutils.serialization.EndianUtil;
import org.djutils.serialization.SerializationException;
import org.djutils.serialization.TypedMessage;
import org.sim0mq.Sim0MQException;
/**
* The message structure of a typical typed Sim0MQ simulation message looks as follows:<br>
* Frame 0. Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 01.<br>
* Frame 1. Simulation run id. Simulation run ids can be provided in different types. Examples are two 64-bit longs indicating a
* UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a simulation run
* number. In order to check whether the right information has been received, the id can be translated to a String and compared
* with an internal string representation of the required id.<br>
* Frame 2. Message type id. Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number. For interoperability
* between different types of simulation, a String id with dot-notation (e.g., DSOL.1 for a simulator start message from DSOL or
* OTS.14 for a statistics message from OpenTrafficSim) would be preferred.<br>
* Frame 3. Message status id. Messages can be about something new (containing a definition that can be quite long), an update
* (which is often just an id followed by a single number), and a deletion (which is often just an id).<br>
* Frame 4. Number of fields. The number of fields in the payload is indicated to be able to check the payload and to avoid
* reading past the end. The number of fields can be encoded using any length type (byte, short, int, long).<br>
* Frame 5-n. Payload, where each field has a 1-byte prefix denoting the type of field.
* <p>
* Copyright (c) 2016-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>.
* </p>
* $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
* initial version Mar 3, 2017 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
*/
public final class SimulationMessage
{
/**
* Do not instantiate this utility class.
*/
private SimulationMessage()
{
// Utility class; do not instantiate.
}
/**
* Encode the object array into a message. Use UTF8 or UTF16 to code Strings.
* @param utf8 choice to use Use UTF8 or UTF16 to code Strings
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
@SuppressWarnings("checkstyle:parameternumber")
private static byte[] encode(final Encoding utf8, final Object simulationRunId, final Object senderId,
final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus,
final Object... content) throws Sim0MQException, SerializationException
{
Object[] simulationContent = new Object[content.length + 8];
simulationContent[0] = Sim0MQMessage.VERSION;
simulationContent[1] = simulationRunId;
simulationContent[2] = senderId;
simulationContent[3] = receiverId;
simulationContent[4] = messageTypeId;
simulationContent[5] = messageId;
simulationContent[6] = new Byte(messageStatus.getStatus());
simulationContent[7] = new Short((short) content.length);
for (int i = 0; i < content.length; i++)
{
simulationContent[i + 8] = content[i];
}
return TypedMessage.encodeUTF8(EndianUtil.BIG_ENDIAN, simulationContent);
}
/**
* Encode the object array into a message. Use UTF8 or UTF16 to code Strings.
* @param utf8 choice to use Use UTF8 or UTF16 to code Strings
* @param identity the identity of the federate to which this is the reply
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
@SuppressWarnings("checkstyle:parameternumber")
private static byte[] encodeReply(final Encoding utf8, final String identity, final Object simulationRunId,
final Object senderId, final Object receiverId, final Object messageTypeId, final long messageId,
final MessageStatus messageStatus, final Object... content) throws Sim0MQException, SerializationException
{
Object[] simulationContent = new Object[content.length + 10];
simulationContent[0] = identity;
simulationContent[1] = new byte[] { 0 };
simulationContent[2] = Sim0MQMessage.VERSION;
simulationContent[3] = simulationRunId;
simulationContent[4] = senderId;
simulationContent[5] = receiverId;
simulationContent[6] = messageTypeId;
simulationContent[7] = messageId;
simulationContent[8] = new Byte(messageStatus.getStatus());
simulationContent[9] = new Short((short) content.length);
for (int i = 0; i < content.length; i++)
{
simulationContent[i + 10] = content[i];
}
return TypedMessage.encodeUTF8(EndianUtil.BIG_ENDIAN, simulationContent);
}
/**
* Encode the object array into a message. Use UTF8 to code Strings.
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
public static byte[] encodeUTF8(final Object simulationRunId, final Object senderId, final Object receiverId,
final Object messageTypeId, final long messageId, final MessageStatus messageStatus, final Object... content)
throws Sim0MQException, SerializationException
{
return encode(Encoding.UTF8, simulationRunId, senderId, receiverId, messageTypeId, messageId, messageStatus, content);
}
/**
* Encode the object array into a message. Use UTF16 to code Strings.
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
public static byte[] encodeUTF16(final Object simulationRunId, final Object senderId, final Object receiverId,
final Object messageTypeId, final long messageId, final MessageStatus messageStatus, final Object... content)
throws Sim0MQException, SerializationException
{
return encode(Encoding.UTF16, simulationRunId, senderId, receiverId, messageTypeId, messageId, messageStatus, content);
}
/**
* Encode the object array into a reply message. Use UTF8 to code Strings.
* @param identity the identity of the federate to which this is the reply
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
@SuppressWarnings("checkstyle:parameternumber")
public static byte[] encodeReplyUTF8(final String identity, final Object simulationRunId, final Object senderId,
final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus,
final Object... content) throws Sim0MQException, SerializationException
{
return encodeReply(Encoding.UTF8, identity, simulationRunId, senderId, receiverId, messageTypeId, messageId,
messageStatus, content);
}
/**
* Encode the object array into a reply message. Use UTF16 to code Strings.
* @param identity the identity of the federate to which this is the reply
* @param simulationRunId the Simulation run ids can be provided in different types. Examples are two 64-bit longs
* indicating a UUID, or a String with a UUID number, a String with meaningful identification, or a short or an
* int with a simulation run number.
* @param senderId The sender id can be used to send back a message to the sender at some later time.
* @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
* error can be sent if we receive a message not meant for us).
* @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
* Examples are a String with a meaningful identification, or a short or an int with a message type number.
* @param messageId The unique message number is meant to confirm with a callback that the message has been received
* correctly. The number is unique for the sender, so not globally within the federation.
* @param messageStatus Three different status messages are defined: 1 for new, 2 for change, and 3 for delete. This field
* is coded as a byte.
* @param content the objects to encode
* @return the zeroMQ message to send as a byte array
* @throws Sim0MQException on unknown data type
* @throws SerializationException on serialization problem
*/
@SuppressWarnings("checkstyle:parameternumber")
public static byte[] encodeReplyUTF16(final String identity, final Object simulationRunId, final Object senderId,
final Object receiverId, final Object messageTypeId, final long messageId, final MessageStatus messageStatus,
final Object... content) throws Sim0MQException, SerializationException
{
return encodeReply(Encoding.UTF16, identity, simulationRunId, senderId, receiverId, messageTypeId, messageId,
messageStatus, content);
}
/**
* Decode the message into an object array. Note that the message fields are coded as follows:<br>
* 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br>
* 1 = simulation run id, could be String, int, Object, ...<br>
* 2 = sender id, could be String, int, Object, ...<br>
* 3 = receiver id, could be String, int, Object, ...<br>
* 4 = message type id, could be String, int, Object, ...<br>
* 5 = message id, as a long.<br>
* 6 = message status, 1=NEW, 2=CHANGE, 3=DELETE.<br>
* 7 = number of fields that follow.<br>
* 8-n = payload, where the number of fields was defined by message[7].
* @param bytes the ZeroMQ byte array to decode
* @return an array of objects of the right type
* @throws Sim0MQException on unknown data type
* @throws SerializationException when deserialization fails
*/
public static Object[] decode(final byte[] bytes) throws Sim0MQException, SerializationException
{
Object[] message = TypedMessage.decodeToObjectDataTypes(bytes, EndianUtil.BIG_ENDIAN);
Throw.when(!(message[7] instanceof Number), Sim0MQException.class, "message[7] is not a number");
Throw.when(message.length != ((Number) message[7]).intValue() + 8, Sim0MQException.class,
"message[7] number of fields not matched by message structure");
return message;
}
/**
* Return a printable version of the message, e.g. for debugging purposes.
* @param message the message to parse
* @return a string representation of the message
*/
public static String print(final Object[] message)
{
StringBuffer s = new StringBuffer();
s.append("0. magic number : " + message[0] + "\n");
s.append("1. simulation run id: " + message[1] + "\n");
s.append("2. sender id : " + message[2] + "\n");
s.append("3. receiver id : " + message[3] + "\n");
s.append("4. message type id : " + message[4] + "\n");
s.append("5. message id : " + message[5] + "\n");
s.append("6. message status : " + MessageStatus.getTypes().get((int) (byte) message[6]) + "\n");
s.append("7. number of fields : " + message[7] + "\n");
int nr = ((Number) message[7]).intValue();
if (message.length != nr + 8)
{
s.append("Error - number of fields not matched by message structure");
}
else
{
for (int i = 0; i < nr; i++)
{
s.append((8 + i) + ". message field : " + message[8 + i] + " (" + message[8 + i].getClass().getSimpleName()
+ ")\n");
}
}
return s.toString();
}
/**
* Return a printable line with the payload of the message, e.g. for debugging purposes.
* @param message the message to parse
* @return a string representation of the message
*/
public static String listPayload(final Object[] message)
{
StringBuffer s = new StringBuffer();
s.append('|');
int nr = ((Number) message[7]).intValue();
if (message.length != nr + 8)
{
s.append("Error - number of fields not matched by message structure");
}
else
{
for (int i = 0; i < nr; i++)
{
s.append(message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() + ") | ");
}
}
return s.toString();
}
}