View Javadoc
1   package org.sim0mq.message;
2   
3   import java.io.Serializable;
4   
5   import org.djutils.exceptions.Throw;
6   import org.djutils.serialization.EndianUtil;
7   import org.djutils.serialization.SerializationException;
8   import org.djutils.serialization.TypedMessage;
9   import org.sim0mq.Sim0MQException;
10  
11  /**
12   * Sim0MQMessage contains the abstract body of the message with the first fields of every Sim0MQ message. The message structure
13   * of a typical typed Sim0MQ simulation message looks as follows:
14   * <ul>
15   * <li>Frame 0. Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 02.</li>
16   * <li>Frame 1. Endianness. A boolean that is True for Big-Endian encoding and false for Little-Endian encosing of the
17   * message.</li>
18   * <li>Frame 2. Federation id. Federation ids can be provided in different types. Examples are a 64-bit long, or a String with a
19   * UUID number, a String with meaningful identification, or a short or an int with a simulation run number. In order to check
20   * whether the right information has been received, the id can be translated to a String and compared with an internal string
21   * representation of the required simulation run id. Typically we use a String to provide maximum freedom. In that case, the run
22   * id can be coded as UTF-8 or UTF-16.</li>
23   * <li>Frame 3. Sender id. Sender ids can be provided in different types. Examples are a 64-bit long, or a String with a UUID
24   * number, a String with meaningful identification, or a short or an int with a sender id number. The sender id can be used to
25   * send back a message to the sender at some later time. Typically we use a String to provide maximum freedom. In that case, the
26   * sender id can be coded as UTF-8 or UTF-16.</li>
27   * <li>Frame 4. Receiver id. Receiver ids can be provided in different types. Examples are a 64-bit long, or a String with a
28   * UUID number, a String with meaningful identification, or a short or an int with a receiver id number. The receiver id can be
29   * 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
30   * not meant for us). Typically we use a String to provide maximum freedom. In that case, the receiver id can be coded as UTF-8
31   * or UTF-16.</li>
32   * <li>Frame 5. Message type id. Message type ids can be defined per type of simulation, and can be provided in different types.
33   * Examples are a String with a meaningful identification, or a short or an int with a message type number. For interoperability
34   * between different types of simulation, a String id with dot-notation (e.g., DSOL.1 for a simulator start message from DSOL or
35   * OTS.14 for a statistics message from OpenTrafficSim) would be preferred. In that case, the message type id can be coded as
36   * UTF-8 or UTF-16.</li>
37   * <li>Frame 6. Unique message number. The unique message number can have any type, but is typically sent as a long (64 bits),
38   * and is meant to confirm with a callback that the message has been received correctly. The number is unique for the sender, so
39   * not globally within the federation.</li>
40   * <li>Frame 7. Number of fields. The number of fields in the payload is indicated to be able to check the payload and to avoid
41   * reading past the end. The number of fields can be encoded using byte, short, int, or long. A 16-bit positive short (including
42   * zero) is the standard encoding.</li>
43   * <li>Frame 8-n. Payload, where each field has a 1-byte prefix denoting the type of field.</li>
44   * </ul>
45   * <p>
46   * Copyright (c) 2016-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
47   * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>.
48   * </p>
49   * initial version Mar 3, 2017 <br>
50   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
51   */
52  public class Sim0MQMessage implements Serializable
53  {
54      /** */
55      private static final long serialVersionUID = 20191017L;
56  
57      /** version of the protocol, magic number. */
58      protected static final String VERSION = "SIM02";
59  
60      /**
61       * the federation id can be provided in different types. Examples are two 64-bit longs indicating a UUID, or a String with a
62       * UUID number, a String with meaningful identification, or a byte, short or int with a simulation run number.
63       */
64      private final Object federationId;
65  
66      /**
67       * Indicates whether this message using little endian or big endian encoding. Big endian is encoded as true, and little
68       * endian as false.
69       */
70      private final boolean bigEndian;
71  
72      /** The sender id can be used to send back a message to the sender at some later time. */
73      private final Object senderId;
74  
75      /**
76       * 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
77       * if we receive a message not meant for us).
78       */
79      private final Object receiverId;
80  
81      /**
82       * Message type ids can be defined per type of simulation, and can be provided in different types. Examples are a String
83       * with a meaningful identification, or a short or an int with a message type number.
84       */
85      private final Object messageTypeId;
86  
87      /**
88       * The unique message id is meant to be used in, e.g., a callback that the message has been received correctly. Within this
89       * implementation, the messageId is unique for the sender, so not globally within the federation.
90       */
91      private final Object messageId;
92  
93      /** Payload as an Object array. */
94      private final Object[] payload;
95  
96      /**
97       * Encode the object array into a message.
98       * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
99       *            encoded as true, and little endian as false.
100      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
101      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
102      *            simulation run number.
103      * @param senderId The sender id can be used to send back a message to the sender at some later time.
104      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
105      *            error can be sent if we receive a message not meant for us).
106      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
107      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
108      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
109      *            correctly. The number is unique for the sender, so not globally within the federation.
110      * @param payload Object[]; Payload as an Object array
111      * @throws Sim0MQException on unknown data type
112      * @throws NullPointerException when one of the parameters is null
113      */
114     public Sim0MQMessage(final boolean bigEndian, final Object federationId, final Object senderId, final Object receiverId,
115             final Object messageTypeId, final Object messageId, final Object[] payload)
116             throws Sim0MQException, NullPointerException
117     {
118         Throw.whenNull(federationId, "federationId cannot be null");
119         Throw.whenNull(senderId, "senderId cannot be null");
120         Throw.whenNull(receiverId, "receiverId cannot be null");
121         Throw.whenNull(messageTypeId, "messageTypeId cannot be null");
122         Throw.whenNull(messageId, "messageId cannot be null");
123         if (payload != null)
124         {
125             for (int i = 0; i < payload.length; i++)
126             {
127                 Throw.whenNull(payload[i], "payload[" + i + "] cannot be null");
128             }
129         }
130 
131         this.bigEndian = bigEndian;
132         this.federationId = federationId;
133         this.senderId = senderId;
134         this.receiverId = receiverId;
135         this.messageTypeId = messageTypeId;
136         this.messageId = messageId;
137         this.payload = payload == null ? new Object[0] : payload;
138     }
139 
140     /**
141      * Encode the object array into a message. <br>
142      * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br>
143      * 1 = endianness, boolean indicating the endianness for the message. True is Big Endian, false is Little Endian.<br>
144      * 2 = federation id, could be String, int, Object, ...<br>
145      * 3 = sender id, could be String, int, Object, ...<br>
146      * 4 = receiver id, could be String, int, Object, ...<br>
147      * 5 = message type id, could be String, int, Object, ...<br>
148      * 6 = message id, could be long, Object, String, ....<br>
149      * 7 = number of fields that follow, as a Number (byte, short, int, long).<br>
150      * 8-n = payload, where the number of fields was defined by message[7].
151      * @param objectArray Object[]; Full message object array
152      * @param expectedNumberOfPayloadFields int; the expected number of fields in the message (field 8 and further)
153      * @param expectedMessageTypeId the expected message type id
154      * @throws Sim0MQException on unknown data type
155      * @throws NullPointerException when one of the parameters is null
156      */
157     public Sim0MQMessage(final Object[] objectArray, final int expectedNumberOfPayloadFields,
158             final Object expectedMessageTypeId) throws Sim0MQException, NullPointerException
159     {
160         Throw.whenNull(objectArray, "objectArray cannot be null");
161         Throw.when(objectArray.length != 8 + expectedNumberOfPayloadFields, Sim0MQException.class,
162                 "FS1RequestStatusMessage should have " + expectedNumberOfPayloadFields + " fields but has "
163                         + (objectArray.length - 8) + " fields");
164         for (int i = 0; i < 8; i++)
165         {
166             Throw.whenNull(objectArray[i], "objectArray[" + i + "] cannot be null");
167         }
168         Throw.when(!objectArray[0].equals(VERSION), Sim0MQException.class, "objectArray.version != " + VERSION);
169         Throw.when(!objectArray[5].equals(expectedMessageTypeId), Sim0MQException.class,
170                 "objectArray.messageTypeId != " + expectedMessageTypeId);
171         Throw.when(!(objectArray[1] instanceof Boolean), Sim0MQException.class, "objectArray.bigEndian not boolean");
172         this.bigEndian = ((Boolean) objectArray[1]).booleanValue();
173         this.federationId = objectArray[2];
174         this.senderId = objectArray[3];
175         this.receiverId = objectArray[4];
176         this.messageTypeId = objectArray[5];
177         this.messageId = objectArray[6];
178         Throw.when(!(objectArray[7] instanceof Number), Sim0MQException.class, "objectArray.numberOfFields not a number");
179         this.payload = new Object[((Number) objectArray[7]).intValue()];
180         for (int i = 0; i < this.payload.length; i++)
181         {
182             this.payload[i] = objectArray[i + 8];
183         }
184     }
185 
186     /**
187      * @return Magic number = |9|0|0|0|5|S|I|M|#|#| where ## stands for the version number, e.g., 01. Internally, the magic
188      *         number is always coded as a UTF-8 String, so it always starts with a byte equal to 9.
189      */
190     public final Object getMagicNumber()
191     {
192         return VERSION;
193     }
194 
195     /**
196      * @return bigEndian
197      */
198     public final boolean isBigEndian()
199     {
200         return this.bigEndian;
201     }
202 
203     /**
204      * @return federationId
205      */
206     public final Object getFederationId()
207     {
208         return this.federationId;
209     }
210 
211     /**
212      * @return senderId
213      */
214     public final Object getSenderId()
215     {
216         return this.senderId;
217     }
218 
219     /**
220      * @return receiverId
221      */
222     public final Object getReceiverId()
223     {
224         return this.receiverId;
225     }
226 
227     /**
228      * @return messageTypeId
229      */
230     public final Object getMessageTypeId()
231     {
232         return this.messageTypeId;
233     }
234 
235     /**
236      * @return messageId
237      */
238     public final Object getMessageId()
239     {
240         return this.messageId;
241     }
242 
243     /**
244      * Create a Sim0MQ object array of the fields.
245      * @return Object[] a Sim0MQ object array of the fields
246      */
247     public final Object[] createObjectArray()
248     {
249         Object[] result = new Object[8 + getNumberOfPayloadFields()];
250         result[0] = Sim0MQMessage.VERSION;
251         result[1] = isBigEndian();
252         result[2] = getFederationId();
253         result[3] = getSenderId();
254         result[4] = getReceiverId();
255         result[5] = getMessageTypeId();
256         result[6] = getMessageId();
257         result[7] = getNumberOfPayloadFields();
258         for (int i = 0; i < getNumberOfPayloadFields(); i++)
259         {
260             result[8 + i] = this.payload[i];
261         }
262         return result;
263     }
264 
265     /**
266      * Create a byte array of the fields.
267      * @return byte[] a Sim0MQ byte array of the content
268      * @throws Sim0MQException on unknown data type as part of the content
269      * @throws SerializationException when the byte array cannot be created, e.g. because the number of bytes does not match
270      */
271     public final byte[] createByteArray() throws Sim0MQException, SerializationException
272     {
273         return Sim0MQMessage.encodeUTF8(this.bigEndian, getFederationId(), getSenderId(), getReceiverId(), getMessageTypeId(),
274                 getMessageId(), this.payload);
275     }
276 
277     /**
278      * Get the number of payload fields in the message.
279      * @return short; the number of payload fields in the message.
280      */
281     public final short getNumberOfPayloadFields()
282     {
283         return (short) this.payload.length;
284     }
285 
286     /**
287      * Check the consistency of a message from an Object[] that was received.
288      * @param fields Object[]; the fields in the message
289      * @param expectedPayloadFields the expected number of payload fields
290      * @param expectedMessageType the expected message type
291      * @param intendedReceiverId id of the intended receiver
292      * @throws Sim0MQException when errors in the message have been detected
293      */
294     public static void check(final Object[] fields, final int expectedPayloadFields, final String expectedMessageType,
295             final Object intendedReceiverId) throws Sim0MQException
296     {
297         Throw.when(fields.length != expectedPayloadFields + 8, Sim0MQException.class,
298                 "Message " + expectedMessageType + " does not contain the right number of fields. " + "Expected: "
299                         + (expectedPayloadFields + 8) + ", Actual: " + fields.length);
300 
301         for (int i = 0; i < fields.length; i++)
302         {
303             Object field = fields[i];
304             if (field == null)
305             {
306                 throw new Sim0MQException("Message " + expectedMessageType + " field " + i + " equals null");
307             }
308         }
309 
310         Throw.when(!expectedMessageType.equals(fields[5].toString()), Sim0MQException.class,
311                 "Message type not right -- should have been " + expectedMessageType);
312 
313         Throw.when(!fields[4].equals(intendedReceiverId), Sim0MQException.class,
314                 "Receiver in message of type " + expectedMessageType + " not right. Should have been: " + intendedReceiverId);
315 
316         Throw.when(!(fields[7] instanceof Number), Sim0MQException.class,
317                 "Message " + expectedMessageType + " does not have a Number field[7] for the number of fields");
318         Throw.when(((Number) fields[7]).longValue() != expectedPayloadFields, Sim0MQException.class,
319                 "Message " + expectedMessageType + " does not contain the right number of payload fields in field[7]");
320     }
321 
322     /* ******************************************************************************************************* */
323     /* ************************************ STATIC METHODS TO BUILD A MESSAGE ******************************** */
324     /* ******************************************************************************************************* */
325 
326     /**
327      * Encode the object array into a message. Use UTF8 or UTF16 to code Strings.
328      * @param stringEncoding choice to use Use UTF8 or UTF16 to code Strings
329      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
330      *            encoded as true, and little endian as false.
331      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
332      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
333      *            simulation run number.
334      * @param senderId The sender id can be used to send back a message to the sender at some later time.
335      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
336      *            error can be sent if we receive a message not meant for us).
337      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
338      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
339      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
340      *            correctly. The number is unique for the sender, so not globally within the federation.
341      * @param content the objects to encode
342      * @return the zeroMQ message to send as a byte array
343      * @throws Sim0MQException on unknown data type
344      * @throws SerializationException on serialization problem
345      */
346     @SuppressWarnings("checkstyle:parameternumber")
347     private static byte[] encode(final StringEncoding stringEncoding, final boolean bigEndian, final Object federationId,
348             final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId,
349             final Object... content) throws Sim0MQException, SerializationException
350     {
351         Object[] simulationContent = new Object[content.length + 8];
352         simulationContent[0] = Sim0MQMessage.VERSION;
353         simulationContent[1] = bigEndian;
354         simulationContent[2] = federationId;
355         simulationContent[3] = senderId;
356         simulationContent[4] = receiverId;
357         simulationContent[5] = messageTypeId;
358         simulationContent[6] = messageId;
359         if (content.length < Short.MAX_VALUE)
360         {
361             simulationContent[7] = Short.valueOf((short) content.length);
362         }
363         else
364         {
365             simulationContent[7] = Integer.valueOf(content.length);
366         }
367         for (int i = 0; i < content.length; i++)
368         {
369             simulationContent[i + 8] = content[i];
370         }
371         return stringEncoding.isUTF8()
372                 ? TypedMessage.encodeUTF8(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent)
373                 : TypedMessage.encodeUTF16(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent);
374     }
375 
376     /**
377      * Encode the object array into a message. Use UTF8 or UTF16 to code Strings.
378      * @param identity the identity of the federate to which this is the reply
379      * @param stringEncoding choice to use Use UTF8 or UTF16 to code Strings
380      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
381      *            encoded as true, and little endian as false.
382      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
383      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
384      *            simulation run number.
385      * @param senderId The sender id can be used to send back a message to the sender at some later time.
386      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
387      *            error can be sent if we receive a message not meant for us).
388      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
389      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
390      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
391      *            correctly. The number is unique for the sender, so not globally within the federation.
392      * @param content the objects to encode
393      * @return the zeroMQ message to send as a byte array
394      * @throws Sim0MQException on unknown data type
395      * @throws SerializationException on serialization problem
396      */
397     @SuppressWarnings("checkstyle:parameternumber")
398     private static byte[] encodeReply(final String identity, final StringEncoding stringEncoding, final boolean bigEndian,
399             final Object federationId, final Object senderId, final Object receiverId, final Object messageTypeId,
400             final Object messageId, final Object... content) throws Sim0MQException, SerializationException
401     {
402         Object[] simulationContent = new Object[content.length + 10];
403         simulationContent[0] = identity;
404         simulationContent[1] = new byte[] {0};
405         simulationContent[2] = Sim0MQMessage.VERSION;
406         simulationContent[3] = bigEndian;
407         simulationContent[4] = federationId;
408         simulationContent[5] = senderId;
409         simulationContent[6] = receiverId;
410         simulationContent[7] = messageTypeId;
411         simulationContent[8] = messageId;
412         if (content.length < Short.MAX_VALUE)
413         {
414             simulationContent[9] = Short.valueOf((short) content.length);
415         }
416         else
417         {
418             simulationContent[9] = Integer.valueOf(content.length);
419         }
420         for (int i = 0; i < content.length; i++)
421         {
422             simulationContent[i + 10] = content[i];
423         }
424         return stringEncoding.isUTF8()
425                 ? TypedMessage.encodeUTF8(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent)
426                 : TypedMessage.encodeUTF16(bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN, simulationContent);
427     }
428 
429     /**
430      * Encode the object array into a message. Use UTF8 to code Strings.
431      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
432      *            encoded as true, and little endian as false.
433      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
434      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
435      *            simulation run number.
436      * @param senderId The sender id can be used to send back a message to the sender at some later time.
437      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
438      *            error can be sent if we receive a message not meant for us).
439      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
440      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
441      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
442      *            correctly. The number is unique for the sender, so not globally within the federation.
443      * @param content the objects to encode
444      * @return the zeroMQ message to send as a byte array
445      * @throws Sim0MQException on unknown data type
446      * @throws SerializationException on serialization problem
447      */
448     public static byte[] encodeUTF8(final boolean bigEndian, final Object federationId, final Object senderId,
449             final Object receiverId, final Object messageTypeId, final Object messageId, final Object... content)
450             throws Sim0MQException, SerializationException
451     {
452         return encode(StringEncoding.UTF8, bigEndian, federationId, senderId, receiverId, messageTypeId, messageId, content);
453     }
454 
455     /**
456      * Encode the object array into a message. Use UTF16 to code Strings.
457      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
458      *            encoded as true, and little endian as false.
459      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
460      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
461      *            simulation run number.
462      * @param senderId The sender id can be used to send back a message to the sender at some later time.
463      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
464      *            error can be sent if we receive a message not meant for us).
465      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
466      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
467      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
468      *            correctly. The number is unique for the sender, so not globally within the federation.
469      * @param content the objects to encode
470      * @return the zeroMQ message to send as a byte array
471      * @throws Sim0MQException on unknown data type
472      * @throws SerializationException on serialization problem
473      */
474     public static byte[] encodeUTF16(final boolean bigEndian, final Object federationId, final Object senderId,
475             final Object receiverId, final Object messageTypeId, final Object messageId, final Object... content)
476             throws Sim0MQException, SerializationException
477     {
478         return encode(StringEncoding.UTF16, bigEndian, federationId, senderId, receiverId, messageTypeId, messageId, content);
479     }
480 
481     /**
482      * Encode the object array into a reply message. Use UTF8 to code Strings.
483      * @param identity the identity of the federate to which this is the reply
484      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
485      *            encoded as true, and little endian as false.
486      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
487      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
488      *            simulation run number.
489      * @param senderId The sender id can be used to send back a message to the sender at some later time.
490      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
491      *            error can be sent if we receive a message not meant for us).
492      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
493      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
494      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
495      *            correctly. The number is unique for the sender, so not globally within the federation.
496      * @param content the objects to encode
497      * @return the zeroMQ message to send as a byte array
498      * @throws Sim0MQException on unknown data type
499      * @throws SerializationException on serialization problem
500      */
501     @SuppressWarnings("checkstyle:parameternumber")
502     public static byte[] encodeReplyUTF8(final String identity, final boolean bigEndian, final Object federationId,
503             final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId,
504             final Object... content) throws Sim0MQException, SerializationException
505     {
506         return encodeReply(identity, StringEncoding.UTF8, bigEndian, federationId, senderId, receiverId, messageTypeId,
507                 messageId, content);
508     }
509 
510     /**
511      * Encode the object array into a reply message. Use UTF16 to code Strings.
512      * @param identity the identity of the federate to which this is the reply
513      * @param bigEndian boolean; Indicates whether this message using little endian or big endian encoding. Big endian is
514      *            encoded as true, and little endian as false.
515      * @param federationId the federation id can be coded using different types. Examples are two 64-bit longs indicating a
516      *            UUID, or a String with a UUID number, a String with meaningful identification, or a short or an int with a
517      *            simulation run number.
518      * @param senderId The sender id can be used to send back a message to the sender at some later time.
519      * @param receiverId The receiver id can be used to check whether the message is meant for us, or should be discarded (or an
520      *            error can be sent if we receive a message not meant for us).
521      * @param messageTypeId Message type ids can be defined per type of simulation, and can be provided in different types.
522      *            Examples are a String with a meaningful identification, or a short or an int with a message type number.
523      * @param messageId The unique message number is meant to confirm with a callback that the message has been received
524      *            correctly. The number is unique for the sender, so not globally within the federation.
525      * @param content the objects to encode
526      * @return the zeroMQ message to send as a byte array
527      * @throws Sim0MQException on unknown data type
528      * @throws SerializationException on serialization problem
529      */
530     @SuppressWarnings("checkstyle:parameternumber")
531     public static byte[] encodeReplyUTF16(final String identity, final boolean bigEndian, final Object federationId,
532             final Object senderId, final Object receiverId, final Object messageTypeId, final Object messageId,
533             final Object... content) throws Sim0MQException, SerializationException
534     {
535         return encodeReply(identity, StringEncoding.UTF16, bigEndian, federationId, senderId, receiverId, messageTypeId,
536                 messageId, content);
537     }
538 
539     /**
540      * Decode the message into an object array. Note that the message fields are coded as follows:<br>
541      * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br>
542      * 1 = endianness, boolean indicating the endianness for the message. True is Big Endian, false is Little Endian.<br>
543      * 2 = federation id, could be String, int, Object, ...<br>
544      * 3 = sender id, could be String, int, Object, ...<br>
545      * 4 = receiver id, could be String, int, Object, ...<br>
546      * 5 = message type id, could be String, int, Object, ...<br>
547      * 6 = message id, could be long, Object, String, ....<br>
548      * 7 = number of fields that follow, as a Number (byte, short, int, long).<br>
549      * 8-n = payload, where the number of fields was defined by message[7].
550      * @param bytes the ZeroMQ byte array to decode
551      * @return Sim0MQMessage; a newly created Sim0MQMessage based on the decoded bytes
552      * @throws Sim0MQException on unknown data type
553      * @throws SerializationException when deserialization fails
554      */
555     public static Sim0MQMessage decode(final byte[] bytes) throws Sim0MQException, SerializationException
556     {
557         Object[] array = decodeToArray(bytes);
558         return new Sim0MQMessage(array, array.length - 8, array[5]);
559     }
560 
561     /**
562      * Decode the message into an object array. Note that the message fields are coded as follows:<br>
563      * 0 = magic number, equal to the String "SIM##" where ## stands for the version number of the protocol.<br>
564      * 1 = endianness, boolean indicatingthe enfianness for the message. True is Big Endian, false is Little Endian.<br>
565      * 2 = simulation run id, could be String, int, Object, ...<br>
566      * 3 = sender id, could be String, int, Object, ...<br>
567      * 4 = receiver id, could be String, int, Object, ...<br>
568      * 5 = message type id, could be String, int, Object, ...<br>
569      * 6 = message id, as a long.<br>
570      * 7 = number of fields that follow.<br>
571      * 8-n = payload, where the number of fields was defined by message[6].
572      * @param bytes the ZeroMQ byte array to decode
573      * @return an array of objects of the right type
574      * @throws Sim0MQException on unknown data type
575      * @throws SerializationException when deserialization fails
576      */
577     public static Object[] decodeToArray(final byte[] bytes) throws Sim0MQException, SerializationException
578     {
579         Throw.whenNull(bytes, "bytes should not be null");
580         Throw.when(bytes.length < 12, Sim0MQException.class, "number of bytes in message < 12: " + bytes.length);
581         Throw.when(bytes[10] != 6 || bytes[11] < 0 || bytes[11] > 1, Sim0MQException.class,
582                 "Bytes 10+11 in the byte array do not contain a boolean");
583         boolean bigEndian = bytes[11] == 1;
584         Object[] objectArray =
585                 TypedMessage.decodeToObjectDataTypes(bytes, bigEndian ? EndianUtil.BIG_ENDIAN : EndianUtil.LITTLE_ENDIAN);
586         Throw.when(objectArray.length < 8, Sim0MQException.class, "number of message fields < 8: " + objectArray.length);
587         Throw.when(!(objectArray[0] instanceof String) || !(objectArray[0].equals(Sim0MQMessage.VERSION)),
588                 Sim0MQException.class, "message[0] does not contain the right version number: " + objectArray[0]);
589         Throw.when(!(objectArray[1] instanceof Boolean), Sim0MQException.class, "message[1] is not a boolean");
590         Throw.when(!(objectArray[7] instanceof Number), Sim0MQException.class, "message[7] is not a number");
591         Throw.when(objectArray.length != ((Number) objectArray[7]).intValue() + 8, Sim0MQException.class,
592                 "message[7] number of fields not matched by message structure");
593         return objectArray;
594     }
595 
596     /**
597      * Return a printable version of the message, e.g. for debugging purposes.
598      * @param message the message to parse
599      * @return a string representation of the message
600      */
601     public static String print(final Object[] message)
602     {
603         StringBuffer s = new StringBuffer();
604         s.append("0. magic number     : " + message[0] + "\n");
605         s.append("1. endianness       : " + message[1] + "\n");
606         s.append("2. simulation run id: " + message[2] + "\n");
607         s.append("3. sender id        : " + message[3] + "\n");
608         s.append("4. receiver id      : " + message[4] + "\n");
609         s.append("5. message type id  : " + message[5] + "\n");
610         s.append("6. message id       : " + message[6] + "\n");
611         s.append("7. number of fields : " + message[7] + "\n");
612         int nr = ((Number) message[7]).intValue();
613         if (message.length != nr + 8)
614         {
615             s.append("Error - number of fields not matched by message structure");
616         }
617         else
618         {
619             for (int i = 0; i < nr; i++)
620             {
621                 s.append((8 + i) + ". message field    : " + message[8 + i] + "  (" + message[8 + i].getClass().getSimpleName()
622                         + ")\n");
623             }
624         }
625         return s.toString();
626     }
627 
628     /**
629      * Return a printable line with the payload of the message, e.g. for debugging purposes.
630      * @param message the message to parse
631      * @return a string representation of the message
632      */
633     public static String listPayload(final Object[] message)
634     {
635         StringBuffer s = new StringBuffer();
636         s.append('|');
637         int nr = ((Number) message[7]).intValue();
638         if (message.length != nr + 8)
639         {
640             s.append("Error - number of fields not matched by message structure");
641         }
642         else
643         {
644             for (int i = 0; i < nr; i++)
645             {
646                 s.append(message[8 + i] + " (" + message[8 + i].getClass().getSimpleName() + ") | ");
647             }
648         }
649         return s.toString();
650     }
651 
652     /**
653      * Builder for the Sim0MQMessage. Can string setters together, and call build() at the end to build the actual message.
654      * <p>
655      * Copyright (c) 2016-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
656      * <br>
657      * BSD-style license. See <a href="http://sim0mq.org/docs/current/license.html">Sim0MQ License</a>.
658      * </p>
659      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
660      * initial version Apr 22, 2017 <br>
661      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
662      * @param <B> the actual inherited builder for the return types.
663      */
664     public abstract static class Builder<B extends Sim0MQMessage.Builder<B>>
665     {
666         /**
667          * the Simulation run ids can be provided in different types. Examples are two 64-bit longs indicating a UUID, or a
668          * String with a UUID number, a String with meaningful identification, or a short or an int with a simulation run
669          * number.
670          */
671         @SuppressWarnings("checkstyle:visibilitymodifier")
672         protected Object federationId;
673 
674         /** The sender id can be used to send back a message to the sender at some later time. */
675         @SuppressWarnings("checkstyle:visibilitymodifier")
676         protected Object senderId;
677 
678         /**
679          * The receiver id can be used to check whether the message is meant for us, or should be discarded (or an error can be
680          * sent if we receive a message not meant for us).
681          */
682         @SuppressWarnings("checkstyle:visibilitymodifier")
683         protected Object receiverId;
684 
685         /**
686          * Message type ids can be defined per type of simulation, and can be provided in different types. Examples are a String
687          * with a meaningful identification, or a short or an int with a message type number.
688          */
689         @SuppressWarnings("checkstyle:visibilitymodifier")
690         protected Object messageTypeId;
691 
692         /**
693          * The unique message number is meant to confirm with a callback that the message has been received correctly. The
694          * number is unique for the sender, so not globally within the federation.
695          */
696         @SuppressWarnings("checkstyle:visibilitymodifier")
697         protected Object messageId;
698 
699         /**
700          * Empty constructor.
701          */
702         public Builder()
703         {
704             // nothing to do.
705         }
706 
707         /**
708          * @param newFederationId set federationId
709          * @return the original object for chaining
710          */
711         @SuppressWarnings("unchecked")
712         public final B setSimulationRunId(final Object newFederationId)
713         {
714             this.federationId = newFederationId;
715             return (B) this;
716         }
717 
718         /**
719          * @param newSenderId set senderId
720          * @return the original object for chaining
721          */
722         @SuppressWarnings("unchecked")
723         public final B setSenderId(final Object newSenderId)
724         {
725             this.senderId = newSenderId;
726             return (B) this;
727         }
728 
729         /**
730          * @param newReceiverId set receiverId
731          * @return the original object for chaining
732          */
733         @SuppressWarnings("unchecked")
734         public final B setReceiverId(final Object newReceiverId)
735         {
736             this.receiverId = newReceiverId;
737             return (B) this;
738         }
739 
740         /**
741          * @param newMessageTypeId set messageTypeId
742          * @return the original object for chaining
743          */
744         @SuppressWarnings("unchecked")
745         protected final B setMessageTypeId(final Object newMessageTypeId)
746         {
747             this.messageTypeId = newMessageTypeId;
748             return (B) this;
749         }
750 
751         /**
752          * @param newMessageId set messageId
753          * @return the original object for chaining
754          */
755         @SuppressWarnings("unchecked")
756         public final B setMessageId(final Object newMessageId)
757         {
758             this.messageId = newMessageId;
759             return (B) this;
760         }
761 
762         /**
763          * Build the object.
764          * @return the message object from the builder.
765          * @throws Sim0MQException on unknown data type
766          * @throws NullPointerException when one of the parameters is null
767          */
768         public abstract Sim0MQMessage build() throws Sim0MQException, NullPointerException;
769 
770     }
771 }