33import java .io .IOException ;
44import java .lang .reflect .Type ;
55import java .util .Collection ;
6- import java .util .HashMap ;
7- import java .util .Map ;
86
97import com .google .gson .JsonArray ;
108import com .google .gson .JsonDeserializer ;
@@ -50,143 +48,41 @@ public class Message extends BaseMessage {
5048 public String connectionKey ;
5149
5250 /**
53- * (TM2k ) serial string – an opaque string that uniquely identifies the message. If a message received from Ably
51+ * (TM2r ) serial string – an opaque string that uniquely identifies the message. If a message received from Ably
5452 * (whether over realtime or REST, eg history) with an action of MESSAGE_CREATE does not contain a serial,
5553 * the SDK must set it equal to its version.
5654 */
5755 public String serial ;
5856
5957 /**
60- * (TM2p) version string – an opaque string that uniquely identifies the message, and is different for different versions.
58+ * (TM2s) version object – enhanced version tracking for Protocol v4.
59+ * Contains detailed version metadata including serial and timestamp information.
6160 * (May not be populated depending on app & channel namespace settings)
6261 */
63- public String version ;
62+ public MessageVersion version ;
6463
6564 /**
6665 * (TM2j) action enum
6766 */
6867 public MessageAction action ;
6968
70- /**
71- * (TM2o) createdAt time in milliseconds since epoch. If a message received from Ably
72- * (whether over realtime or REST, eg history) with an action of MESSAGE_CREATE does not contain a createdAt,
73- * the SDK must set it equal to the TM2f timestamp.
74- */
75- public Long createdAt ;
76-
77- /**
78- * (TM2l) ref string – an opaque string that uniquely identifies some referenced message.
79- */
80- public String refSerial ;
81-
82- /**
83- * (TM2m) refType string – an opaque string that identifies the type of this reference.
84- */
85- public String refType ;
86-
87- /**
88- * (TM2n) operation object – data object that may contain the `optional` attributes.
89- */
90- public Operation operation ;
91-
9269 /**
9370 * (TM2q) A summary of all the annotations that have been made to the message, whose keys are the `type` fields
9471 * from any annotations that it includes. Will always be populated for a message with action {@code MESSAGE_SUMMARY},
9572 * and may be populated for any other type (in particular a message retrieved from
9673 * REST history will have its latest summary included).
9774 */
98- public Summary summary ;
99-
100- public static class Operation {
101- public String clientId ;
102- public String description ;
103- public Map <String , String > metadata ;
75+ public MessageAnnotations annotations ;
10476
105- void write (MessagePacker packer ) throws IOException {
106- int fieldCount = 0 ;
107- if (clientId != null ) fieldCount ++;
108- if (description != null ) fieldCount ++;
109- if (metadata != null ) fieldCount ++;
11077
111- packer .packMapHeader (fieldCount );
112-
113- if (clientId != null ) {
114- packer .packString ("clientId" );
115- packer .packString (clientId );
116- }
117- if (description != null ) {
118- packer .packString ("description" );
119- packer .packString (description );
120- }
121- if (metadata != null ) {
122- packer .packString ("metadata" );
123- packer .packMapHeader (metadata .size ());
124- for (Map .Entry <String , String > entry : metadata .entrySet ()) {
125- packer .packString (entry .getKey ());
126- packer .packString (entry .getValue ());
127- }
128- }
129- }
130-
131- protected static Operation read (final MessageUnpacker unpacker ) throws IOException {
132- Operation operation = new Operation ();
133- int fieldCount = unpacker .unpackMapHeader ();
134- for (int i = 0 ; i < fieldCount ; i ++) {
135- String fieldName = unpacker .unpackString ().intern ();
136- switch (fieldName ) {
137- case "clientId" :
138- operation .clientId = unpacker .unpackString ();
139- break ;
140- case "description" :
141- operation .description = unpacker .unpackString ();
142- break ;
143- case "metadata" :
144- int mapSize = unpacker .unpackMapHeader ();
145- operation .metadata = new HashMap <>(mapSize );
146- for (int j = 0 ; j < mapSize ; j ++) {
147- String key = unpacker .unpackString ();
148- String value = unpacker .unpackString ();
149- operation .metadata .put (key , value );
150- }
151- break ;
152- default :
153- unpacker .skipValue ();
154- break ;
155- }
156- }
157- return operation ;
158- }
159-
160- protected static Operation read (final JsonObject jsonObject ) throws MessageDecodeException {
161- Operation operation = new Operation ();
162- if (jsonObject .has ("clientId" )) {
163- operation .clientId = jsonObject .get ("clientId" ).getAsString ();
164- }
165- if (jsonObject .has ("description" )) {
166- operation .description = jsonObject .get ("description" ).getAsString ();
167- }
168- if (jsonObject .has ("metadata" )) {
169- JsonObject metadataObject = jsonObject .getAsJsonObject ("metadata" );
170- operation .metadata = new HashMap <>();
171- for (Map .Entry <String , JsonElement > entry : metadataObject .entrySet ()) {
172- operation .metadata .put (entry .getKey (), entry .getValue ().getAsString ());
173- }
174- }
175- return operation ;
176- }
177- }
17878
17979 private static final String NAME = "name" ;
18080 private static final String EXTRAS = "extras" ;
18181 private static final String CONNECTION_KEY = "connectionKey" ;
18282 private static final String SERIAL = "serial" ;
18383 private static final String VERSION = "version" ;
18484 private static final String ACTION = "action" ;
185- private static final String CREATED_AT = "createdAt" ;
186- private static final String REF_SERIAL = "refSerial" ;
187- private static final String REF_TYPE = "refType" ;
188- private static final String OPERATION = "operation" ;
189- private static final String SUMMARY = "summary" ;
85+ private static final String ANNOTATIONS = "annotations" ;
19086
19187 /**
19288 * Default constructor
@@ -270,11 +166,7 @@ void writeMsgpack(MessagePacker packer) throws IOException {
270166 if (serial != null ) ++fieldCount ;
271167 if (version != null ) ++fieldCount ;
272168 if (action != null ) ++fieldCount ;
273- if (createdAt != null ) ++fieldCount ;
274- if (refSerial != null ) ++fieldCount ;
275- if (refType != null ) ++fieldCount ;
276- if (operation != null ) ++fieldCount ;
277- if (summary != null ) ++fieldCount ;
169+ if (annotations != null ) ++fieldCount ;
278170
279171 packer .packMapHeader (fieldCount );
280172 super .writeFields (packer );
@@ -296,31 +188,15 @@ void writeMsgpack(MessagePacker packer) throws IOException {
296188 }
297189 if (version != null ) {
298190 packer .packString (VERSION );
299- packer . packString ( version );
191+ version . writeMsgpack ( packer );
300192 }
301193 if (action != null ) {
302194 packer .packString (ACTION );
303195 packer .packInt (action .ordinal ());
304196 }
305- if (createdAt != null ) {
306- packer .packString (CREATED_AT );
307- packer .packLong (createdAt );
308- }
309- if (refSerial != null ) {
310- packer .packString (REF_SERIAL );
311- packer .packString (refSerial );
312- }
313- if (refType != null ) {
314- packer .packString (REF_TYPE );
315- packer .packString (refType );
316- }
317- if (operation != null ) {
318- packer .packString (OPERATION );
319- operation .write (packer );
320- }
321- if (summary != null ) {
322- packer .packString (SUMMARY );
323- summary .write (packer );
197+ if (annotations != null ) {
198+ packer .packString (ANNOTATIONS );
199+ annotations .writeMsgpack (packer );
324200 }
325201 }
326202
@@ -346,19 +222,11 @@ Message readMsgpack(MessageUnpacker unpacker) throws IOException {
346222 } else if (fieldName .equals (SERIAL )) {
347223 serial = unpacker .unpackString ();
348224 } else if (fieldName .equals (VERSION )) {
349- version = unpacker . unpackString ( );
225+ version = MessageVersion . fromMsgpack ( unpacker );
350226 } else if (fieldName .equals (ACTION )) {
351227 action = MessageAction .tryFindByOrdinal (unpacker .unpackInt ());
352- } else if (fieldName .equals (CREATED_AT )) {
353- createdAt = unpacker .unpackLong ();
354- } else if (fieldName .equals (REF_SERIAL )) {
355- refSerial = unpacker .unpackString ();
356- } else if (fieldName .equals (REF_TYPE )) {
357- refType = unpacker .unpackString ();
358- } else if (fieldName .equals (OPERATION )) {
359- operation = Operation .read (unpacker );
360- } else if (fieldName .equals (SUMMARY )) {
361- summary = Summary .read (unpacker );
228+ } else if (fieldName .equals (ANNOTATIONS )) {
229+ annotations = MessageAnnotations .fromMsgpack (unpacker );
362230 }
363231 else {
364232 Log .v (TAG , "Unexpected field: " + fieldName );
@@ -519,27 +387,18 @@ protected void read(final JsonObject map) throws MessageDecodeException {
519387 connectionKey = readString (map , CONNECTION_KEY );
520388
521389 serial = readString (map , SERIAL );
522- version = readString (map , VERSION );
390+
391+ // Handle version field - supports both legacy string format and new MessageVersion object format
392+ final JsonElement versionElement = map .get (VERSION );
393+ if (versionElement != null ) {
394+ version = MessageVersion .read (versionElement );
395+ }
523396 Integer actionOrdinal = readInt (map , ACTION );
524397 action = actionOrdinal == null ? null : MessageAction .tryFindByOrdinal (actionOrdinal );
525- createdAt = readLong (map , CREATED_AT );
526- refSerial = readString (map , REF_SERIAL );
527- refType = readString (map , REF_TYPE );
528-
529- final JsonElement operationElement = map .get (OPERATION );
530- if (null != operationElement ) {
531- if (!operationElement .isJsonObject ()) {
532- throw MessageDecodeException .fromDescription ("Message operation is of type \" " + operationElement .getClass () + "\" when expected a JSON object." );
533- }
534- operation = Operation .read (operationElement .getAsJsonObject ());
535- }
536398
537- final JsonElement summaryElement = map .get (SUMMARY );
538- if (summaryElement != null ) {
539- if (!summaryElement .isJsonObject ()) {
540- throw MessageDecodeException .fromDescription ("Message summary is of type \" " + summaryElement .getClass () + "\" when expected a JSON object." );
541- }
542- summary = Summary .read (summaryElement .getAsJsonObject ());
399+ final JsonElement annotationsElement = map .get (ANNOTATIONS );
400+ if (annotationsElement != null ) {
401+ annotations = MessageAnnotations .read (annotationsElement );
543402 }
544403 }
545404
@@ -560,25 +419,13 @@ public JsonElement serialize(Message message, Type typeOfMessage, JsonSerializat
560419 json .addProperty (SERIAL , message .serial );
561420 }
562421 if (message .version != null ) {
563- json .addProperty (VERSION , message .version );
422+ json .add (VERSION , message .version . toJsonTree () );
564423 }
565424 if (message .action != null ) {
566425 json .addProperty (ACTION , message .action .ordinal ());
567426 }
568- if (message .createdAt != null ) {
569- json .addProperty (CREATED_AT , message .createdAt );
570- }
571- if (message .refSerial != null ) {
572- json .addProperty (REF_SERIAL , message .refSerial );
573- }
574- if (message .refType != null ) {
575- json .addProperty (REF_TYPE , message .refType );
576- }
577- if (message .operation != null ) {
578- json .add (OPERATION , Serialisation .gson .toJsonTree (message .operation ));
579- }
580- if (message .summary != null ) {
581- json .add (SUMMARY , message .summary .toJsonTree ());
427+ if (message .annotations != null ) {
428+ json .add (ANNOTATIONS , message .annotations .toJsonTree ());
582429 }
583430 return json ;
584431 }
0 commit comments