Skip to content

Commit b69cb81

Browse files
committed
[ECO-5482] Updated LiveMap interface to return and perform ops using LiveMapValue
- Updated implementation for the same to return LiveMapValue - Fixed related integration tests with the same
1 parent f551f5f commit b69cb81

8 files changed

Lines changed: 160 additions & 133 deletions

File tree

lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public interface LiveMap extends LiveMapChange {
3030
* @return the value associated with the specified key, or null if the key does not exist.
3131
*/
3232
@Nullable
33-
Object get(@NotNull String keyName);
33+
LiveMapValue get(@NotNull String keyName);
3434

3535
/**
3636
* Retrieves all entries (key-value pairs) in the map.
@@ -40,7 +40,7 @@ public interface LiveMap extends LiveMapChange {
4040
*/
4141
@NotNull
4242
@Unmodifiable
43-
Iterable<Map.Entry<String, Object>> entries();
43+
Iterable<Map.Entry<String, LiveMapValue>> entries();
4444

4545
/**
4646
* Retrieves all keys in the map.
@@ -60,27 +60,29 @@ public interface LiveMap extends LiveMapChange {
6060
*/
6161
@NotNull
6262
@Unmodifiable
63-
Iterable<Object> values();
63+
Iterable<LiveMapValue> values();
6464

6565
/**
6666
* Sets the specified key to the given value in the map.
6767
* Send a MAP_SET operation to the realtime system to set a key on this LiveMap object to a specified value.
6868
* This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when
6969
* the published MAP_SET operation is echoed back to the client and applied to the object following the regular
7070
* operation application procedure.
71+
* Spec: RTLM20
7172
*
7273
* @param keyName the key to be set.
7374
* @param value the value to be associated with the key.
7475
*/
7576
@Blocking
76-
void set(@NotNull String keyName, @NotNull Object value);
77+
void set(@NotNull String keyName, @NotNull LiveMapValue value);
7778

7879
/**
7980
* Removes the specified key and its associated value from the map.
8081
* Send a MAP_REMOVE operation to the realtime system to tombstone a key on this LiveMap object.
8182
* This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when
8283
* the published MAP_REMOVE operation is echoed back to the client and applied to the object following the regular
8384
* operation application procedure.
85+
* Spec: RTLM21
8486
*
8587
* @param keyName the key to be removed.
8688
*/
@@ -103,6 +105,7 @@ public interface LiveMap extends LiveMapChange {
103105
* This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when
104106
* the published MAP_SET operation is echoed back to the client and applied to the object following the regular
105107
* operation application procedure.
108+
* Spec: RTLM20
106109
*
107110
* @param keyName the key to be set.
108111
* @param value the value to be associated with the key.
@@ -117,6 +120,7 @@ public interface LiveMap extends LiveMapChange {
117120
* This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when
118121
* the published MAP_REMOVE operation is echoed back to the client and applied to the object following the regular
119122
* operation application procedure.
123+
* Spec: RTLM21
120124
*
121125
* @param keyName the key to be removed.
122126
* @param callback the callback to handle the result or any errors.

live-objects/src/main/kotlin/io/ably/lib/objects/Helpers.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.ably.lib.objects
22

3+
import io.ably.lib.objects.type.BaseLiveObject
4+
import io.ably.lib.objects.type.map.LiveMapValue
35
import io.ably.lib.realtime.ChannelState
46
import io.ably.lib.realtime.CompletionListener
57
import io.ably.lib.types.ChannelMode

live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal val LiveObjectUpdate.noOp get() = this.update == null
2727
*/
2828
internal abstract class BaseLiveObject(
2929
internal val objectId: String, // // RTLO3a
30-
private val objectType: ObjectType,
30+
internal val objectType: ObjectType,
3131
) {
3232

3333
protected open val tag = "BaseLiveObject"

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import io.ably.lib.objects.type.ObjectType
1111
import io.ably.lib.objects.type.map.LiveMap
1212
import io.ably.lib.objects.type.map.LiveMapChange
1313
import io.ably.lib.objects.type.map.LiveMapUpdate
14+
import io.ably.lib.objects.type.map.LiveMapValue
1415
import io.ably.lib.objects.type.noOp
1516
import io.ably.lib.types.Callback
1617
import io.ably.lib.util.Log
@@ -45,7 +46,7 @@ internal class DefaultLiveMap private constructor(
4546
private val adapter: LiveObjectsAdapter get() = liveObjects.adapter
4647
internal val objectsPool: ObjectsPool get() = liveObjects.objectsPool
4748

48-
override fun get(keyName: String): Any? {
49+
override fun get(keyName: String): LiveMapValue? {
4950
adapter.throwIfInvalidAccessApiConfiguration(channelName) // RTLM5b, RTLM5c
5051
if (isTombstoned) {
5152
return null
@@ -56,10 +57,10 @@ internal class DefaultLiveMap private constructor(
5657
return null // RTLM5d1
5758
}
5859

59-
override fun entries(): Iterable<Map.Entry<String, Any>> {
60+
override fun entries(): Iterable<Map.Entry<String, LiveMapValue>> {
6061
adapter.throwIfInvalidAccessApiConfiguration(channelName) // RTLM11b, RTLM11c
6162

62-
return sequence<Map.Entry<String, Any>> {
63+
return sequence<Map.Entry<String, LiveMapValue>> {
6364
for ((key, entry) in data.entries) {
6465
val value = entry.getResolvedValue(objectsPool) // RTLM11d, RTLM11d2
6566
value?.let {
@@ -78,7 +79,7 @@ internal class DefaultLiveMap private constructor(
7879
}.asIterable()
7980
}
8081

81-
override fun values(): Iterable<Any> {
82+
override fun values(): Iterable<LiveMapValue> {
8283
val iterableEntries = entries()
8384
return sequence {
8485
for (entry in iterableEntries) {
@@ -92,7 +93,7 @@ internal class DefaultLiveMap private constructor(
9293
return data.values.count { !it.isEntryOrRefTombstoned(objectsPool) }.toLong() // RTLM10d
9394
}
9495

95-
override fun set(keyName: String, value: Any) {
96+
override fun set(keyName: String, value: LiveMapValue) {
9697
TODO("Not yet implemented")
9798
}
9899

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
package io.ably.lib.objects.type.livemap
22

3+
import com.google.gson.JsonArray
4+
import com.google.gson.JsonObject
5+
import io.ably.lib.objects.*
6+
import io.ably.lib.objects.Binary
37
import io.ably.lib.objects.ObjectData
48
import io.ably.lib.objects.ObjectsPool
59
import io.ably.lib.objects.ObjectsPoolDefaults
10+
import io.ably.lib.objects.type.BaseLiveObject
11+
import io.ably.lib.objects.type.ObjectType
12+
import io.ably.lib.objects.type.counter.LiveCounter
13+
import io.ably.lib.objects.type.map.LiveMap
14+
import io.ably.lib.objects.type.map.LiveMapValue
615

716
/**
817
* @spec RTLM3 - Map data structure storing entries
@@ -36,17 +45,17 @@ internal fun LiveMapEntry.isEntryOrRefTombstoned(objectsPool: ObjectsPool): Bool
3645
* Returns value as is if object data stores a primitive type or
3746
* a reference to another LiveObject from the pool if it stores an objectId.
3847
*/
39-
internal fun LiveMapEntry.getResolvedValue(objectsPool: ObjectsPool): Any? {
48+
internal fun LiveMapEntry.getResolvedValue(objectsPool: ObjectsPool): LiveMapValue? {
4049
if (isTombstoned) { return null } // RTLM5d2a
4150

42-
data?.value?.let { return it.value } // RTLM5d2b, RTLM5d2c, RTLM5d2d, RTLM5d2e
51+
data?.value?.let { return fromObjectValue(it) } // RTLM5d2b, RTLM5d2c, RTLM5d2d, RTLM5d2e
4352

4453
data?.objectId?.let { refId -> // RTLM5d2f -has an objectId reference
4554
objectsPool.get(refId)?.let { refObject ->
4655
if (refObject.isTombstoned) {
4756
return null // tombstoned objects must not be surfaced to the end users
4857
}
49-
return refObject // RTLM5d2f2
58+
return fromLiveObject(refObject) // RTLM5d2f2
5059
}
5160
}
5261
return null // RTLM5d2g, RTLM5d2f1
@@ -59,3 +68,22 @@ internal fun LiveMapEntry.isEligibleForGc(): Boolean {
5968
val currentTime = System.currentTimeMillis()
6069
return isTombstoned && tombstonedAt?.let { currentTime - it >= ObjectsPoolDefaults.GC_GRACE_PERIOD_MS } == true
6170
}
71+
72+
private fun fromObjectValue(objValue: ObjectValue): LiveMapValue {
73+
return when (val value = objValue.value) {
74+
is String -> LiveMapValue.of(value)
75+
is Number -> LiveMapValue.of(value)
76+
is Boolean -> LiveMapValue.of(value)
77+
is Binary -> LiveMapValue.of(value.data)
78+
is JsonObject -> LiveMapValue.of(value)
79+
is JsonArray -> LiveMapValue.of(value)
80+
else -> throw IllegalArgumentException("Unsupported value type: ${value::class.java}")
81+
}
82+
}
83+
84+
private fun fromLiveObject(baseLiveObject: BaseLiveObject): LiveMapValue {
85+
return when (baseLiveObject.objectType) {
86+
ObjectType.Map -> LiveMapValue.of(baseLiveObject as LiveMap)
87+
ObjectType.Counter -> LiveMapValue.of(baseLiveObject as LiveCounter)
88+
}
89+
}

live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveCounterTest.kt

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.ably.lib.objects.integration
22

3-
import io.ably.lib.objects.type.counter.LiveCounter
4-
import io.ably.lib.objects.type.map.LiveMap
53
import io.ably.lib.objects.assertWaiter
64
import io.ably.lib.objects.integration.helpers.ObjectId
75
import io.ably.lib.objects.integration.helpers.fixtures.createUserEngagementMatrixMap
@@ -29,64 +27,64 @@ class DefaultLiveCounterTest: IntegrationTest() {
2927
val rootMap = channel.objects.root
3028

3129
// Get the user map object from the root map
32-
val userMap = rootMap.get("user") as LiveMap
30+
val userMap = rootMap.get("user")?.asLiveMap
3331
assertNotNull(userMap, "User map should be synchronized")
3432
assertEquals(7L, userMap.size(), "User map should contain 7 top-level entries")
3533

3634
// Assert direct counter objects at the top level of the user map
3735
// Test profileViews counter - should have initial value of 127
38-
val profileViewsCounter = userMap.get("profileViews") as LiveCounter
36+
val profileViewsCounter = userMap.get("profileViews")?.asLiveCounter
3937
assertNotNull(profileViewsCounter, "Profile views counter should exist")
4038
assertEquals(127.0, profileViewsCounter.value(), "Profile views counter should have initial value of 127")
4139

4240
// Test postLikes counter - should have initial value of 45
43-
val postLikesCounter = userMap.get("postLikes") as LiveCounter
41+
val postLikesCounter = userMap.get("postLikes")?.asLiveCounter
4442
assertNotNull(postLikesCounter, "Post likes counter should exist")
4543
assertEquals(45.0, postLikesCounter.value(), "Post likes counter should have initial value of 45")
4644

4745
// Test commentCount counter - should have initial value of 23
48-
val commentCountCounter = userMap.get("commentCount") as LiveCounter
46+
val commentCountCounter = userMap.get("commentCount")?.asLiveCounter
4947
assertNotNull(commentCountCounter, "Comment count counter should exist")
5048
assertEquals(23.0, commentCountCounter.value(), "Comment count counter should have initial value of 23")
5149

5250
// Test followingCount counter - should have initial value of 89
53-
val followingCountCounter = userMap.get("followingCount") as LiveCounter
51+
val followingCountCounter = userMap.get("followingCount")?.asLiveCounter
5452
assertNotNull(followingCountCounter, "Following count counter should exist")
5553
assertEquals(89.0, followingCountCounter.value(), "Following count counter should have initial value of 89")
5654

5755
// Test followersCount counter - should have initial value of 156
58-
val followersCountCounter = userMap.get("followersCount") as LiveCounter
56+
val followersCountCounter = userMap.get("followersCount")?.asLiveCounter
5957
assertNotNull(followersCountCounter, "Followers count counter should exist")
6058
assertEquals(156.0, followersCountCounter.value(), "Followers count counter should have initial value of 156")
6159

6260
// Test loginStreak counter - should have initial value of 7
63-
val loginStreakCounter = userMap.get("loginStreak") as LiveCounter
61+
val loginStreakCounter = userMap.get("loginStreak")?.asLiveCounter
6462
assertNotNull(loginStreakCounter, "Login streak counter should exist")
6563
assertEquals(7.0, loginStreakCounter.value(), "Login streak counter should have initial value of 7")
6664

6765
// Assert the nested engagement metrics map
68-
val engagementMetrics = userMap.get("engagementMetrics") as LiveMap
66+
val engagementMetrics = userMap.get("engagementMetrics")?.asLiveMap
6967
assertNotNull(engagementMetrics, "Engagement metrics map should exist")
7068
assertEquals(4L, engagementMetrics.size(), "Engagement metrics map should contain 4 counter entries")
7169

7270
// Assert counter objects within the engagement metrics map
7371
// Test totalShares counter - should have initial value of 34
74-
val totalSharesCounter = engagementMetrics.get("totalShares") as LiveCounter
72+
val totalSharesCounter = engagementMetrics.get("totalShares")?.asLiveCounter
7573
assertNotNull(totalSharesCounter, "Total shares counter should exist")
7674
assertEquals(34.0, totalSharesCounter.value(), "Total shares counter should have initial value of 34")
7775

7876
// Test totalBookmarks counter - should have initial value of 67
79-
val totalBookmarksCounter = engagementMetrics.get("totalBookmarks") as LiveCounter
77+
val totalBookmarksCounter = engagementMetrics.get("totalBookmarks")?.asLiveCounter
8078
assertNotNull(totalBookmarksCounter, "Total bookmarks counter should exist")
8179
assertEquals(67.0, totalBookmarksCounter.value(), "Total bookmarks counter should have initial value of 67")
8280

8381
// Test totalReactions counter - should have initial value of 189
84-
val totalReactionsCounter = engagementMetrics.get("totalReactions") as LiveCounter
82+
val totalReactionsCounter = engagementMetrics.get("totalReactions")?.asLiveCounter
8583
assertNotNull(totalReactionsCounter, "Total reactions counter should exist")
8684
assertEquals(189.0, totalReactionsCounter.value(), "Total reactions counter should have initial value of 189")
8785

8886
// Test dailyActiveStreak counter - should have initial value of 12
89-
val dailyActiveStreakCounter = engagementMetrics.get("dailyActiveStreak") as LiveCounter
87+
val dailyActiveStreakCounter = engagementMetrics.get("dailyActiveStreak")?.asLiveCounter
9088
assertNotNull(dailyActiveStreakCounter, "Daily active streak counter should exist")
9189
assertEquals(12.0, dailyActiveStreakCounter.value(), "Daily active streak counter should have initial value of 12")
9290

@@ -130,7 +128,7 @@ class DefaultLiveCounterTest: IntegrationTest() {
130128
assertWaiter { rootMap.get("testCounter") != null }
131129

132130
// Assert initial state after creation
133-
val testCounter = rootMap.get("testCounter") as LiveCounter
131+
val testCounter = rootMap.get("testCounter")?.asLiveCounter
134132
assertNotNull(testCounter, "Test counter should be created and accessible")
135133
assertEquals(10.0, testCounter.value(), "Counter should have initial value of 10")
136134

@@ -201,7 +199,7 @@ class DefaultLiveCounterTest: IntegrationTest() {
201199
assertNotNull(testCounter, "Counter should still be accessible at the end")
202200

203201
// Verify we can still access it from the root map
204-
val finalCounterCheck = rootMap.get("testCounter") as LiveCounter
202+
val finalCounterCheck = rootMap.get("testCounter")?.asLiveCounter
205203
assertNotNull(finalCounterCheck, "Counter should still be accessible from root map")
206204
assertEquals(30.0, finalCounterCheck.value(), "Final counter value should be 30 when accessed from root map")
207205
}
@@ -215,11 +213,11 @@ class DefaultLiveCounterTest: IntegrationTest() {
215213
val channel = getRealtimeChannel(channelName)
216214
val rootMap = channel.objects.root
217215

218-
val userEngagementMap = rootMap.get("userMatrix") as LiveMap
219-
assertEquals(4L, userEngagementMap.size(), "User engagement map should contain 4 top-level entries")
216+
val userEngagementMap = rootMap.get("userMatrix")?.asLiveMap
217+
assertEquals(4L, userEngagementMap!!.size(), "User engagement map should contain 4 top-level entries")
220218

221-
val totalReactions = userEngagementMap.get("totalReactions") as LiveCounter
222-
assertEquals(189.0, totalReactions.value(), "Total reactions counter should have initial value of 189")
219+
val totalReactions = userEngagementMap.get("totalReactions")?.asLiveCounter
220+
assertEquals(189.0, totalReactions!!.value(), "Total reactions counter should have initial value of 189")
223221

224222
// Subscribe to changes on the totalReactions counter
225223
val counterUpdates = mutableListOf<Double>()

0 commit comments

Comments
 (0)