diff --git a/lib/src/main/java/io/ably/lib/types/Summary.java b/lib/src/main/java/io/ably/lib/types/Summary.java index 1411c0f81..f461be681 100644 --- a/lib/src/main/java/io/ably/lib/types/Summary.java +++ b/lib/src/main/java/io/ably/lib/types/Summary.java @@ -31,6 +31,12 @@ public class Summary { private static final String TAG = Summary.class.getName(); + private static final String TOTAL = "total"; + private static final String CLIENT_IDS = "clientIds"; + private static final String CLIPPED = "clipped"; + private static final String TOTAL_UNIDENTIFIED = "totalUnidentified"; + private static final String TOTAL_CLIENT_IDS = "totalClientIds"; + /** * (TM2q1) The sdk MUST be able to cope with structures and aggregation types that have it does not yet know about * or have explicit support for, hence the loose (JsonObject) type. @@ -59,27 +65,55 @@ public static Map asSummaryMultipleV1(JsonObject for (Map.Entry entry : jsonObject.entrySet()) { String key = entry.getKey(); JsonObject value = entry.getValue().getAsJsonObject(); - int total = value.get("total").getAsInt(); + int total = value.get(TOTAL).getAsInt(); Map clientIds = new HashMap<>(); - for (Map.Entry clientEntry: value.get("clientIds").getAsJsonObject().entrySet()) { + for (Map.Entry clientEntry: value.get(CLIENT_IDS).getAsJsonObject().entrySet()) { clientIds.put(clientEntry.getKey(), clientEntry.getValue().getAsInt()); } - summary.put(key, new SummaryClientIdCounts(total, clientIds)); + Integer totalUnidentified = tryReadIntField(value, TOTAL_UNIDENTIFIED); + Integer totalClientIds = tryReadIntField(value, TOTAL_CLIENT_IDS); + summary.put(key, new SummaryClientIdCounts( + total, + clientIds, + totalUnidentified == null ? 0 : totalUnidentified, + tryReadBooleanField(value, CLIPPED), + totalClientIds == null ? total : totalClientIds + )); } return summary; } public static SummaryClientIdList asSummaryFlagV1(JsonObject jsonObject) { - int total = jsonObject.get("total").getAsInt(); - List clientIds = Serialisation.gson.fromJson(jsonObject.get("clientIds"), List.class); - return new SummaryClientIdList(total, clientIds); + int total = jsonObject.get(TOTAL).getAsInt(); + List clientIds = Serialisation.gson.fromJson(jsonObject.get(CLIENT_IDS), List.class); + return new SummaryClientIdList( + total, + clientIds, + tryReadBooleanField(jsonObject, CLIPPED) + ); } public static SummaryTotal asSummaryTotalV1(JsonObject jsonObject) { - int total = jsonObject.get("total").getAsInt(); + int total = jsonObject.get(TOTAL).getAsInt(); return new SummaryTotal(total); } + private static boolean tryReadBooleanField(JsonObject jsonObject, String fieldName) { + JsonElement fieldElement = jsonObject.get(fieldName); + if (fieldElement != null && fieldElement.isJsonPrimitive() && fieldElement.getAsJsonPrimitive().isBoolean()) { + return fieldElement.getAsBoolean(); + } + return false; + } + + private static Integer tryReadIntField(JsonObject jsonObject, String fieldName) { + JsonElement fieldElement = jsonObject.get(fieldName); + if (fieldElement != null && fieldElement.isJsonPrimitive() && fieldElement.getAsJsonPrimitive().isNumber()) { + return fieldElement.getAsInt(); + } + return null; + } + static Summary read(MessageUnpacker unpacker) { try { return read(Serialisation.msgpackToGson(unpacker.unpackValue())); diff --git a/lib/src/main/java/io/ably/lib/types/SummaryClientIdCounts.java b/lib/src/main/java/io/ably/lib/types/SummaryClientIdCounts.java index d99996749..574746575 100644 --- a/lib/src/main/java/io/ably/lib/types/SummaryClientIdCounts.java +++ b/lib/src/main/java/io/ably/lib/types/SummaryClientIdCounts.java @@ -2,12 +2,45 @@ import java.util.Map; +/** + * The per-name value for the multiple.v1 aggregation method. + */ public class SummaryClientIdCounts { + /** + * The sum of the counts from all clients who have published an annotation with this name + */ public final int total; // TM7d1a + /** + * A list of the clientIds of all clients who have published an annotation with this + * name, and the count each of them have contributed. + */ public final Map clientIds; // TM7d1b + /** + * The sum of the counts from all unidentified clients who have published an annotation with this + * name, and so who are not included in the clientIds list + */ + public final int totalUnidentified; // TM7d1d + /** + * Whether the list of clientIds has been clipped due to exceeding the maximum number of + * clients. + */ + public final boolean clipped; // TM7d1c + /** + * The total number of distinct clientIds in the map (equal to length of map if clipped is false). + */ + public final int totalClientIds; // TM7d1e - public SummaryClientIdCounts(int total, Map clientIds) { + public SummaryClientIdCounts( + int total, + Map clientIds, + int totalUnidentified, + boolean clipped, + int totalClientIds + ) { this.total = total; this.clientIds = clientIds; + this.totalUnidentified = totalUnidentified; + this.clipped = clipped; + this.totalClientIds = totalClientIds; } } diff --git a/lib/src/main/java/io/ably/lib/types/SummaryClientIdList.java b/lib/src/main/java/io/ably/lib/types/SummaryClientIdList.java index 2c9db8a08..7563d34a2 100644 --- a/lib/src/main/java/io/ably/lib/types/SummaryClientIdList.java +++ b/lib/src/main/java/io/ably/lib/types/SummaryClientIdList.java @@ -2,12 +2,29 @@ import java.util.List; +/** + * The summary entry for aggregated annotations that use the flag.v1 + * aggregation method; also the per-name value for some other aggregation methods. + */ public class SummaryClientIdList { + /** + * The sum of the counts from all clients who have published an annotation with this name + */ public final int total; // TM7c1a - public final List clientIds; // TM7c1b + /** + * A list of the clientIds of all clients who have published an annotation with this name (or + * type, depending on context). + */ + public final List clientIds; // TM7 + /** + * Whether the list of clientIds has been clipped due to exceeding the maximum number of + * clients. + */ + public final boolean clipped; // TM7c1c - public SummaryClientIdList(int total, List clientIds) { + public SummaryClientIdList(int total, List clientIds, boolean clipped) { this.total = total; this.clientIds = clientIds; + this.clipped = clipped; } } diff --git a/lib/src/test/java/io/ably/lib/types/SummaryTest.java b/lib/src/test/java/io/ably/lib/types/SummaryTest.java index 13490f395..12cb37f7a 100644 --- a/lib/src/test/java/io/ably/lib/types/SummaryTest.java +++ b/lib/src/test/java/io/ably/lib/types/SummaryTest.java @@ -7,6 +7,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -126,6 +127,25 @@ public void testAsSummaryFlagV1_SingleEntry() { assertTrue(result.clientIds.contains("client1")); assertTrue(result.clientIds.contains("client2")); assertTrue(result.clientIds.contains("client3")); + assertFalse(result.clipped); + } + + @Test + public void testAsSummaryFlagV1_clippedTrue() { + JsonObject entryValue = new JsonObject(); + entryValue.addProperty("total", 100); + JsonArray clientIds = new JsonArray(); + clientIds.add("client1"); + entryValue.add("clientIds", clientIds); + entryValue.addProperty("clipped", true); + + SummaryClientIdList result = Summary.asSummaryFlagV1(entryValue); + + assertNotNull(result); + assertEquals(100, result.total); + assertEquals(1, result.clientIds.size()); + assertTrue(result.clientIds.contains("client1")); + assertTrue(result.clipped); } @Test @@ -196,6 +216,9 @@ public void testAsSummaryMultipleV1_MultipleEntries() { assertEquals(2, summaryA.clientIds.size()); assertEquals(3, (int) summaryA.clientIds.get("clientA")); assertEquals(2, (int) summaryA.clientIds.get("clientB")); + assertEquals(0, summaryA.totalUnidentified); + assertEquals(5, summaryA.totalClientIds); + assertFalse(summaryA.clipped); SummaryClientIdCounts summaryB = result.get("👍️️️️️️"); assertNotNull(summaryB); @@ -203,6 +226,30 @@ public void testAsSummaryMultipleV1_MultipleEntries() { assertEquals(2, summaryB.clientIds.size()); assertEquals(1, (int) summaryB.clientIds.get("clientX")); assertEquals(1, (int) summaryB.clientIds.get("clientY")); + assertEquals(0, summaryB.totalUnidentified); + assertEquals(2, summaryB.totalClientIds); + assertFalse(summaryA.clipped); + } + + @Test + public void testAsSummaryMultipleV1_ClippedTrue() { + JsonObject jsonObject = new JsonObject(); + + JsonObject entryValue1 = new JsonObject(); + entryValue1.addProperty("total", 5); + JsonObject clientIds1 = new JsonObject(); + clientIds1.addProperty("clientA", 1); + entryValue1.add("clientIds", clientIds1); + entryValue1.addProperty("clipped", true); + entryValue1.addProperty("totalClientIds", 1); + jsonObject.add("😄️️️", entryValue1); + + Map result = Summary.asSummaryMultipleV1(jsonObject); + SummaryClientIdCounts summary = result.get("😄️️️"); + assertEquals(5, summary.total); + assertEquals(1, summary.totalClientIds); + assertEquals(0, summary.totalUnidentified); + assertTrue(summary.clipped); } @Test