From c435f5d8bf5af2856ed6e2415fe54163154ad3e7 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 13 May 2025 13:08:22 +0100 Subject: [PATCH 1/5] Created and configured liveobjects as a separate module to the project 1. Declared required liveobject java interfaces 2. Implemented liveobject interface for LiveObjectsPlugin 3. Declared batch specific public interface methods --- .../java/io/ably/lib/realtime/Channel.java | 6 +- .../java/io/ably/lib/realtime/Channel.java | 5 +- .../java/io/ably/lib/objects/LiveCounter.java | 42 +++++++ .../java/io/ably/lib/objects/LiveMap.java | 82 ++++++++++++++ .../java/io/ably/lib/objects/LiveObjects.java | 107 ++++++++++++++++++ .../ably/lib/objects/LiveObjectsPlugin.java | 25 ++++ .../ably/lib/objects/batch/BatchContext.java | 17 +++ .../objects/batch/BatchContextBuilder.java | 14 +++ .../objects/batch/BatchContextLiveMap.java | 63 +++++++++++ .../io/ably/lib/realtime/AblyRealtime.java | 26 ++++- .../io/ably/lib/realtime/ChannelBase.java | 16 ++- live-objects/build.gradle.kts | 21 ++++ live-objects/gradle.properties | 4 + .../io/ably/lib/objects/DefaultLiveObjects.kt | 50 ++++++++ .../lib/objects/DefaultLiveObjectsPlugin.kt | 14 +++ settings.gradle.kts | 1 + 16 files changed, 487 insertions(+), 6 deletions(-) create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveCounter.java create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveMap.java create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveObjects.java create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java create mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java create mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java create mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java create mode 100644 live-objects/build.gradle.kts create mode 100644 live-objects/gradle.properties create mode 100644 live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt create mode 100644 live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt diff --git a/android/src/main/java/io/ably/lib/realtime/Channel.java b/android/src/main/java/io/ably/lib/realtime/Channel.java index daafe5188..baf086cbc 100644 --- a/android/src/main/java/io/ably/lib/realtime/Channel.java +++ b/android/src/main/java/io/ably/lib/realtime/Channel.java @@ -3,6 +3,8 @@ import io.ably.lib.types.AblyException; import io.ably.lib.types.ChannelOptions; import io.ably.lib.push.PushChannel; +import io.ably.lib.objects.LiveObjectsPlugin; + public class Channel extends ChannelBase { /** @@ -12,8 +14,8 @@ public class Channel extends ChannelBase { */ public final PushChannel push; - Channel(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { - super(ably, name, options); + Channel(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { + super(ably, name, options, liveObjectsPlugin); this.push = ((io.ably.lib.rest.AblyRest) ably).channels.get(name, options).push; } diff --git a/java/src/main/java/io/ably/lib/realtime/Channel.java b/java/src/main/java/io/ably/lib/realtime/Channel.java index 9c7f64995..b48c929b1 100644 --- a/java/src/main/java/io/ably/lib/realtime/Channel.java +++ b/java/src/main/java/io/ably/lib/realtime/Channel.java @@ -1,11 +1,12 @@ package io.ably.lib.realtime; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.types.AblyException; import io.ably.lib.types.ChannelOptions; public class Channel extends ChannelBase { - Channel(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { - super(ably, name, options); + Channel(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { + super(ably, name, options, liveObjectsPlugin); } public interface MessageListener extends ChannelBase.MessageListener {} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java new file mode 100644 index 000000000..490f60d2f --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java @@ -0,0 +1,42 @@ +package io.ably.lib.objects; + +import io.ably.lib.types.Callback; + +/** + * The LiveCounter interface provides methods to interact with a live counter. + * It allows incrementing, decrementing, and retrieving the current value of the counter, + * both synchronously and asynchronously. + */ +public interface LiveCounter { + + /** + * Increments the value of the counter by 1. + */ + void increment(); + + /** + * Increments the value of the counter by 1 asynchronously. + * + * @param callback the callback to be invoked upon completion of the operation. + */ + void incrementAsync(Callback callback); + + /** + * Decrements the value of the counter by 1. + */ + void decrement(); + + /** + * Decrements the value of the counter by 1 asynchronously. + * + * @param callback the callback to be invoked upon completion of the operation. + */ + void decrementAsync(Callback callback); + + /** + * Retrieves the current value of the counter. + * + * @return the current value of the counter as a Long. + */ + Long value(); +} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveMap.java b/lib/src/main/java/io/ably/lib/objects/LiveMap.java new file mode 100644 index 000000000..056c152dd --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveMap.java @@ -0,0 +1,82 @@ +package io.ably.lib.objects; + +import io.ably.lib.types.Callback; + +import java.util.Map; + +/** + * The LiveMap interface provides methods to interact with a live, real-time map structure. + * It supports both synchronous and asynchronous operations for managing key-value pairs. + */ +public interface LiveMap { + + /** + * Retrieves the value associated with the specified key. + * + * @param keyName the key whose associated value is to be returned. + * @return the value associated with the specified key, or null if the key does not exist. + */ + Object get(String keyName); + + /** + * Retrieves all entries (key-value pairs) in the map. + * + * @return an iterable collection of all entries in the map. + */ + Iterable> entries(); + + /** + * Retrieves all keys in the map. + * + * @return an iterable collection of all keys in the map. + */ + Iterable keys(); + + /** + * Retrieves all values in the map. + * + * @return an iterable collection of all values in the map. + */ + Iterable values(); + + /** + * Sets the specified key to the given value in the map. + * + * @param keyName the key to be set. + * @param value the value to be associated with the key. + */ + void set(String keyName, Object value); + + /** + * Removes the specified key and its associated value from the map. + * + * @param keyName the key to be removed. + * @param value the value associated with the key to be removed. + */ + void remove(String keyName, Object value); + + /** + * Retrieves the number of entries in the map. + * + * @return the size of the map. + */ + Long size(); + + /** + * Asynchronously sets the specified key to the given value in the map. + * + * @param keyName the key to be set. + * @param value the value to be associated with the key. + * @param callback the callback to handle the result or any errors. + */ + void setAsync(String keyName, Object value, Callback callback); + + /** + * Asynchronously removes the specified key and its associated value from the map. + * + * @param keyName the key to be removed. + * @param value the value associated with the key to be removed. + * @param callback the callback to handle the result or any errors. + */ + void removeAsync(String keyName, Object value, Callback callback); +} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java new file mode 100644 index 000000000..5feff5615 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java @@ -0,0 +1,107 @@ +package io.ably.lib.objects; + +import io.ably.lib.objects.batch.BatchContextBuilder; +import io.ably.lib.types.Callback; + +import java.util.Map; + +/** + * The LiveObjects interface provides methods to interact with live data objects, + * such as maps and counters, in a real-time environment. It supports both synchronous + * and asynchronous operations for retrieving and creating live objects. + */ +public interface LiveObjects { + + /** + * Retrieves the root LiveMap object. + * + * @return the root LiveMap instance. + */ + LiveMap getRoot(); + + /** + * Initiates a batch operation and provides a BatchContext through a callback. + * + * @param batchContextCallback the callback to handle the BatchContext or error. + */ + void batch(BatchContextBuilder batchContextCallback); + + /** + * Creates a new LiveMap based on an existing LiveMap. + * + * @param liveMap the existing LiveMap to base the new LiveMap on. + * @return the newly created LiveMap instance. + */ + LiveMap createMap(LiveMap liveMap); + + /** + * Creates a new LiveMap based on a LiveCounter. + * + * @param liveCounter the LiveCounter to base the new LiveMap on. + * @return the newly created LiveMap instance. + */ + LiveMap createMap(LiveCounter liveCounter); + + /** + * Creates a new LiveMap based on a standard Java Map. + * + * @param map the Java Map to base the new LiveMap on. + * @return the newly created LiveMap instance. + */ + LiveMap createMap(Map map); + + /** + * Creates a new LiveCounter with an initial value. + * + * @param initialValue the initial value of the LiveCounter. + * @return the newly created LiveCounter instance. + */ + LiveCounter createCounter(Long initialValue); + + /** + * Asynchronously retrieves the root LiveMap object. + * + * @param callback the callback to handle the result or error. + */ + void getRootAsync(Callback callback); + + /** + * Initiates a batch operation asynchronously. + * + * @param batchContextCallback the BatchContextBuilder to build the BatchContext. + * @param callback the Callback to handle the completion or error of the batch operation. + */ + void batchAsync(BatchContextBuilder batchContextCallback, Callback callback); + + /** + * Asynchronously creates a new LiveMap based on an existing LiveMap. + * + * @param liveMap the existing LiveMap to base the new LiveMap on. + * @param callback the callback to handle the result or error. + */ + void createMapAsync(LiveMap liveMap, Callback callback); + + /** + * Asynchronously creates a new LiveMap based on a LiveCounter. + * + * @param liveCounter the LiveCounter to base the new LiveMap on. + * @param callback the callback to handle the result or error. + */ + void createMapAsync(LiveCounter liveCounter, Callback callback); + + /** + * Asynchronously creates a new LiveMap based on a standard Java Map. + * + * @param map the Java Map to base the new LiveMap on. + * @param callback the callback to handle the result or error. + */ + void createMapAsync(Map map, Callback callback); + + /** + * Asynchronously creates a new LiveCounter with an initial value. + * + * @param initialValue the initial value of the LiveCounter. + * @param callback the callback to handle the result or error. + */ + void createCounterAsync(Long initialValue, Callback callback); +} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java new file mode 100644 index 000000000..eff365eed --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java @@ -0,0 +1,25 @@ +package io.ably.lib.objects; + +/** + * The LiveObjectsPlugin interface provides a mechanism to retrieve instances of LiveObjects + * associated with specific channel names. This allows for interaction with live data objects + * in a real-time environment. + */ +public interface LiveObjectsPlugin { + + /** + * Retrieves an instance of LiveObjects associated with the specified channel name. + * + * @param channelName the name of the channel for which the LiveObjects instance is to be retrieved. + * @return the LiveObjects instance associated with the specified channel name. + */ + LiveObjects getInstance(String channelName); + + + /** + * Disposes of the LiveObjects instance associated with the specified channel name. + * + * @param channelName the name of the channel whose LiveObjects instance is to be removed. + */ + void dispose(String channelName); +} diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java new file mode 100644 index 000000000..2c84809f3 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java @@ -0,0 +1,17 @@ +package io.ably.lib.objects.batch; + + +/** + * The BatchContext interface represents the context for batch operations + * on live data objects. It provides access to the root LiveMap, which serves + * as the entry point for interacting with the batch context. + */ +public interface BatchContext { + + /** + * Retrieves the root LiveMap associated with this batch context. + * + * @return the root LiveMap instance. + */ + BatchContextLiveMap getRoot(); +} diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java new file mode 100644 index 000000000..16e7014f7 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java @@ -0,0 +1,14 @@ +package io.ably.lib.objects.batch; + +/** + * A functional interface for building and handling a BatchContext.* + */ +@FunctionalInterface +public interface BatchContextBuilder { + /** + * Builds and handles the provided BatchContext. + * + * @param batchContext the BatchContext to handle. + */ + void build(BatchContext batchContext); +} diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java new file mode 100644 index 000000000..ea64a9642 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java @@ -0,0 +1,63 @@ +package io.ably.lib.objects.batch; + +import java.util.Map; + +/** + * The BatchContextLiveMap interface provides methods to interact with a live map + * in the context of batch operations. It allows retrieving, modifying, and querying + * key-value pairs in the map. + */ +public interface BatchContextLiveMap { + + /** + * Retrieves the value associated with the specified key. + * + * @param keyName the name of the key whose value is to be retrieved. + * @return the value associated with the specified key, or null if the key does not exist. + */ + Object get(String keyName); + + /** + * Retrieves all entries (key-value pairs) in the live map. + * + * @return an iterable collection of map entries. + */ + Iterable> entries(); + + /** + * Retrieves all keys in the live map. + * + * @return an iterable collection of keys. + */ + Iterable keys(); + + /** + * Retrieves all values in the live map. + * + * @return an iterable collection of values. + */ + Iterable values(); + + /** + * Sets the specified key to the given value in the live map. + * + * @param keyName the name of the key to set. + * @param value the value to associate with the specified key. + */ + void set(String keyName, Object value); + + /** + * Removes the specified key-value pair from the live map. + * + * @param keyName the name of the key to remove. + * @param value the value associated with the key to remove. + */ + void remove(String keyName, Object value); + + /** + * Retrieves the number of entries in the live map. + * + * @return the size of the live map as a Long. + */ + Long size(); +} diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 5c29a0dea..644ebf9b1 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -1,10 +1,12 @@ package io.ably.lib.realtime; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; @@ -40,6 +42,13 @@ public class AblyRealtime extends AblyRest { */ public final Channels channels; + /** + * A nullable reference to the LiveObjects plugin. + *

+ * This field is initialized only if the LiveObjects plugin is present in the classpath. + */ + private final LiveObjectsPlugin liveObjectsPlugin; + /** * Constructs a Realtime client object using an Ably API key or token string. *

@@ -72,6 +81,8 @@ public AblyRealtime(ClientOptions options) throws AblyException { } } + liveObjectsPlugin = tryInitializeLiveObjectsPlugin(); + if(options.autoConnect) connection.connect(); } @@ -168,6 +179,16 @@ public interface Channels extends ReadOnlyMap { void release(String channelName); } + private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() { + try { + Class liveObjectsImplementation = Class.forName("io.ably.lib.objects.DefaultLiveObjectsPlugin"); + return (LiveObjectsPlugin) liveObjectsImplementation.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + return null; + } + } + private class InternalChannels extends InternalMap implements Channels, ConnectionManager.Channels { /** * Get the named channel; if it does not already exist, @@ -187,7 +208,7 @@ public Channel get(final String channelName, final ChannelOptions channelOptions // We're not using computeIfAbsent because that requires Java 1.8. // Hence there's the slight inefficiency of creating newChannel when it may not be // needed because there is an existingChannel. - final Channel newChannel = new Channel(AblyRealtime.this, channelName, channelOptions); + final Channel newChannel = new Channel(AblyRealtime.this, channelName, channelOptions, liveObjectsPlugin); final Channel existingChannel = map.putIfAbsent(channelName, newChannel); if (existingChannel != null) { @@ -214,6 +235,9 @@ public void release(String channelName) { Log.e(TAG, "Unexpected exception detaching channel; channelName = " + channelName, e); } } + if (liveObjectsPlugin != null) { + liveObjectsPlugin.dispose(channelName); + } } @Override diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 028f6b23c..efa3a0ae4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -13,6 +13,8 @@ import io.ably.lib.http.Http; import io.ably.lib.http.HttpCore; import io.ably.lib.http.HttpUtils; +import io.ably.lib.objects.LiveObjects; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.transport.ConnectionManager; import io.ably.lib.transport.ConnectionManager.QueuedMessage; import io.ably.lib.transport.Defaults; @@ -91,6 +93,17 @@ public abstract class ChannelBase extends EventEmitter') to your dependency tree", 400, 40000) + ); + } + return liveObjectsPlugin.getInstance(name); + } + /*** * internal * @@ -1285,7 +1298,7 @@ else if(stateChange.current.equals(failureState)) { } } - ChannelBase(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { + ChannelBase(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { Log.v(TAG, "RealtimeChannel(); channel = " + name); this.ably = ably; this.name = name; @@ -1295,6 +1308,7 @@ else if(stateChange.current.equals(failureState)) { this.attachResume = false; state = ChannelState.initialized; this.decodingContext = new DecodingContext(); + this.liveObjectsPlugin = liveObjectsPlugin; } void onChannelMessage(ProtocolMessage msg) { diff --git a/live-objects/build.gradle.kts b/live-objects/build.gradle.kts new file mode 100644 index 000000000..a6733a2cf --- /dev/null +++ b/live-objects/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `java-library` + alias(libs.plugins.kotlin.jvm) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":java")) + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + explicitApi() +} diff --git a/live-objects/gradle.properties b/live-objects/gradle.properties new file mode 100644 index 000000000..29fa6bdb7 --- /dev/null +++ b/live-objects/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=live-objects +POM_NAME=Live Objects plugin for Ably Pub/Sub SDK +POM_DESCRIPTION=Live Objects plugin for Ably Pub/Sub SDK +POM_PACKAGING=jar diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt new file mode 100644 index 000000000..da34966e3 --- /dev/null +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt @@ -0,0 +1,50 @@ +package io.ably.lib.objects + +import io.ably.lib.objects.batch.BatchContext +import io.ably.lib.types.Callback + +internal class DefaultLiveObjects(private val channelName: String): LiveObjects { + override fun getRoot(): LiveMap { + TODO("Not yet implemented") + } + + override fun batch(batchContextCallback: Callback?) { + TODO("Not yet implemented") + } + + override fun createMap(liveMap: LiveMap?): LiveMap { + TODO("Not yet implemented") + } + + override fun createMap(liveCounter: LiveCounter?): LiveMap { + TODO("Not yet implemented") + } + + override fun createMap(map: MutableMap?): LiveMap { + TODO("Not yet implemented") + } + + override fun createCounter(initialValue: Long?): LiveCounter { + TODO("Not yet implemented") + } + + override fun getRootAsync(callback: Callback) { + TODO("Not yet implemented") + } + + override fun createMapAsync(liveMap: LiveMap?, callback: Callback?) { + TODO("Not yet implemented") + } + + override fun createMapAsync(liveCounter: LiveCounter?, callback: Callback?) { + TODO("Not yet implemented") + } + + override fun createMapAsync(map: MutableMap?, callback: Callback?) { + TODO("Not yet implemented") + } + + override fun createCounterAsync(initialValue: Long?, callback: Callback?) { + TODO("Not yet implemented") + } +} diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt new file mode 100644 index 000000000..4bf6f6b36 --- /dev/null +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt @@ -0,0 +1,14 @@ +package io.ably.lib.objects + +public class DefaultLiveObjectsPlugin : LiveObjectsPlugin { + + private val cache = mutableMapOf() + + override fun getInstance(channelName: String): LiveObjects { + return cache.getOrPut(channelName) { DefaultLiveObjects(channelName) } + } + + override fun dispose(channelName: String) { + cache.remove(channelName) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7ccfd6f3f..6d7d6ba8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,3 +15,4 @@ include("network-client-core") include("network-client-default") include("network-client-okhttp") include("pubsub-adapter") +include("live-objects") From bce4ba988bff5ea36d89d276c3aee450f4c10b57 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 20 May 2025 17:15:18 +0530 Subject: [PATCH 2/5] 1. Added jetbrains-annoations dependency to clearly define interface methods 2. Added internet connectivity check to test setup --- android/build.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ java/build.gradle.kts | 1 + .../java/io/ably/lib/objects/LiveCounter.java | 8 +++-- .../java/io/ably/lib/objects/LiveMap.java | 27 +++++++++----- .../java/io/ably/lib/objects/LiveObjects.java | 36 ++++++++++++------- .../ably/lib/objects/LiveObjectsPlugin.java | 7 ++-- .../ably/lib/objects/batch/BatchContext.java | 2 ++ .../objects/batch/BatchContextBuilder.java | 6 ++-- .../objects/batch/BatchContextLiveMap.java | 27 ++++++++++---- .../io/ably/lib/realtime/AblyRealtime.java | 1 + .../io/ably/lib/objects/DefaultLiveObjects.kt | 26 ++++++++------ .../lib/objects/DefaultLiveObjectsPlugin.kt | 8 +++-- 13 files changed, 103 insertions(+), 49 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index b4a5071d7..fb70f02e0 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -51,6 +51,7 @@ android { dependencies { api(libs.gson) implementation(libs.bundles.common) + compileOnly(libs.jetbrains) testImplementation(libs.bundles.tests) implementation(project(":network-client-core")) runtimeOnly(project(":network-client-default")) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ebca67ac..f1e77a7c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ test-retry = "1.6.0" kotlin = "2.1.10" coroutine = "1.9.0" turbine = "1.2.0" +jetbrains-annoations = "26.0.2" [libraries] gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } @@ -47,6 +48,7 @@ okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhtt coroutine-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutine" } coroutine-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } +jetbrains = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annoations" } [bundles] common = ["msgpack", "vcdiff-core"] diff --git a/java/build.gradle.kts b/java/build.gradle.kts index 45b0c4e39..33c89a2f8 100644 --- a/java/build.gradle.kts +++ b/java/build.gradle.kts @@ -20,6 +20,7 @@ tasks.withType { dependencies { api(libs.gson) implementation(libs.bundles.common) + compileOnly(libs.jetbrains) implementation(project(":network-client-core")) if (findProperty("okhttp") == null) { runtimeOnly(project(":network-client-default")) diff --git a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java index 490f60d2f..3c8ea410c 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java @@ -1,6 +1,8 @@ package io.ably.lib.objects; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Contract; /** * The LiveCounter interface provides methods to interact with a live counter. @@ -19,7 +21,7 @@ public interface LiveCounter { * * @param callback the callback to be invoked upon completion of the operation. */ - void incrementAsync(Callback callback); + void incrementAsync(@NotNull Callback callback); /** * Decrements the value of the counter by 1. @@ -31,12 +33,14 @@ public interface LiveCounter { * * @param callback the callback to be invoked upon completion of the operation. */ - void decrementAsync(Callback callback); + void decrementAsync(@NotNull Callback callback); /** * Retrieves the current value of the counter. * * @return the current value of the counter as a Long. */ + @NotNull + @Contract(pure = true) // Indicates this method does not modify the state of the object. Long value(); } diff --git a/lib/src/main/java/io/ably/lib/objects/LiveMap.java b/lib/src/main/java/io/ably/lib/objects/LiveMap.java index 056c152dd..465f10e7c 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveMap.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveMap.java @@ -1,6 +1,10 @@ package io.ably.lib.objects; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; import java.util.Map; @@ -16,13 +20,16 @@ public interface LiveMap { * @param keyName the key whose associated value is to be returned. * @return the value associated with the specified key, or null if the key does not exist. */ - Object get(String keyName); + @Nullable + Object get(@NotNull String keyName); /** * Retrieves all entries (key-value pairs) in the map. * * @return an iterable collection of all entries in the map. */ + @NotNull + @Unmodifiable Iterable> entries(); /** @@ -30,6 +37,8 @@ public interface LiveMap { * * @return an iterable collection of all keys in the map. */ + @NotNull + @Unmodifiable Iterable keys(); /** @@ -37,6 +46,8 @@ public interface LiveMap { * * @return an iterable collection of all values in the map. */ + @NotNull + @Unmodifiable Iterable values(); /** @@ -45,21 +56,22 @@ public interface LiveMap { * @param keyName the key to be set. * @param value the value to be associated with the key. */ - void set(String keyName, Object value); + void set(@NotNull String keyName, @NotNull Object value); /** * Removes the specified key and its associated value from the map. * * @param keyName the key to be removed. - * @param value the value associated with the key to be removed. */ - void remove(String keyName, Object value); + void remove(@NotNull String keyName); /** * Retrieves the number of entries in the map. * * @return the size of the map. */ + @Contract(pure = true) // Indicates this method does not modify the state of the object. + @NotNull Long size(); /** @@ -69,14 +81,13 @@ public interface LiveMap { * @param value the value to be associated with the key. * @param callback the callback to handle the result or any errors. */ - void setAsync(String keyName, Object value, Callback callback); + void setAsync(@NotNull String keyName, @NotNull Object value, @NotNull Callback callback); /** * Asynchronously removes the specified key and its associated value from the map. * - * @param keyName the key to be removed. - * @param value the value associated with the key to be removed. + * @param keyName the key to be removed. * @param callback the callback to handle the result or any errors. */ - void removeAsync(String keyName, Object value, Callback callback); + void removeAsync(@NotNull String keyName, @NotNull Callback callback); } diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java index 5feff5615..f51e900bf 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java @@ -2,6 +2,8 @@ import io.ably.lib.objects.batch.BatchContextBuilder; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.NotNull; + import java.util.Map; @@ -9,6 +11,9 @@ * The LiveObjects interface provides methods to interact with live data objects, * such as maps and counters, in a real-time environment. It supports both synchronous * and asynchronous operations for retrieving and creating live objects. + * + *

Implementations of this interface must be thread-safe as they may be accessed + * from multiple threads concurrently. */ public interface LiveObjects { @@ -17,14 +22,15 @@ public interface LiveObjects { * * @return the root LiveMap instance. */ + @NotNull LiveMap getRoot(); /** * Initiates a batch operation and provides a BatchContext through a callback. * - * @param batchContextCallback the callback to handle the BatchContext or error. + * @param batchContextCallback the builder to configure the batch operation. */ - void batch(BatchContextBuilder batchContextCallback); + void batch(@NotNull BatchContextBuilder batchContextCallback); /** * Creates a new LiveMap based on an existing LiveMap. @@ -32,7 +38,8 @@ public interface LiveObjects { * @param liveMap the existing LiveMap to base the new LiveMap on. * @return the newly created LiveMap instance. */ - LiveMap createMap(LiveMap liveMap); + @NotNull + LiveMap createMap(@NotNull LiveMap liveMap); /** * Creates a new LiveMap based on a LiveCounter. @@ -40,7 +47,8 @@ public interface LiveObjects { * @param liveCounter the LiveCounter to base the new LiveMap on. * @return the newly created LiveMap instance. */ - LiveMap createMap(LiveCounter liveCounter); + @NotNull + LiveMap createMap(@NotNull LiveCounter liveCounter); /** * Creates a new LiveMap based on a standard Java Map. @@ -48,7 +56,8 @@ public interface LiveObjects { * @param map the Java Map to base the new LiveMap on. * @return the newly created LiveMap instance. */ - LiveMap createMap(Map map); + @NotNull + LiveMap createMap(@NotNull Map map); /** * Creates a new LiveCounter with an initial value. @@ -56,22 +65,23 @@ public interface LiveObjects { * @param initialValue the initial value of the LiveCounter. * @return the newly created LiveCounter instance. */ - LiveCounter createCounter(Long initialValue); + @NotNull + LiveCounter createCounter(@NotNull Long initialValue); /** * Asynchronously retrieves the root LiveMap object. * * @param callback the callback to handle the result or error. */ - void getRootAsync(Callback callback); + void getRootAsync(@NotNull Callback<@NotNull LiveMap> callback); /** * Initiates a batch operation asynchronously. * - * @param batchContextCallback the BatchContextBuilder to build the BatchContext. + * @param batchContextCallback the builder to configure the batch operation. * @param callback the Callback to handle the completion or error of the batch operation. */ - void batchAsync(BatchContextBuilder batchContextCallback, Callback callback); + void batchAsync(@NotNull BatchContextBuilder batchContextCallback, @NotNull Callback callback); /** * Asynchronously creates a new LiveMap based on an existing LiveMap. @@ -79,7 +89,7 @@ public interface LiveObjects { * @param liveMap the existing LiveMap to base the new LiveMap on. * @param callback the callback to handle the result or error. */ - void createMapAsync(LiveMap liveMap, Callback callback); + void createMapAsync(@NotNull LiveMap liveMap, @NotNull Callback<@NotNull LiveMap> callback); /** * Asynchronously creates a new LiveMap based on a LiveCounter. @@ -87,7 +97,7 @@ public interface LiveObjects { * @param liveCounter the LiveCounter to base the new LiveMap on. * @param callback the callback to handle the result or error. */ - void createMapAsync(LiveCounter liveCounter, Callback callback); + void createMapAsync(@NotNull LiveCounter liveCounter, @NotNull Callback<@NotNull LiveMap> callback); /** * Asynchronously creates a new LiveMap based on a standard Java Map. @@ -95,7 +105,7 @@ public interface LiveObjects { * @param map the Java Map to base the new LiveMap on. * @param callback the callback to handle the result or error. */ - void createMapAsync(Map map, Callback callback); + void createMapAsync(@NotNull Map map, @NotNull Callback<@NotNull LiveMap> callback); /** * Asynchronously creates a new LiveCounter with an initial value. @@ -103,5 +113,5 @@ public interface LiveObjects { * @param initialValue the initial value of the LiveCounter. * @param callback the callback to handle the result or error. */ - void createCounterAsync(Long initialValue, Callback callback); + void createCounterAsync(@NotNull Long initialValue, @NotNull Callback<@NotNull LiveCounter> callback); } diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java index eff365eed..438312bed 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java @@ -1,5 +1,7 @@ package io.ably.lib.objects; +import org.jetbrains.annotations.NotNull; + /** * The LiveObjectsPlugin interface provides a mechanism to retrieve instances of LiveObjects * associated with specific channel names. This allows for interaction with live data objects @@ -13,13 +15,12 @@ public interface LiveObjectsPlugin { * @param channelName the name of the channel for which the LiveObjects instance is to be retrieved. * @return the LiveObjects instance associated with the specified channel name. */ - LiveObjects getInstance(String channelName); - + LiveObjects getInstance(@NotNull String channelName); /** * Disposes of the LiveObjects instance associated with the specified channel name. * * @param channelName the name of the channel whose LiveObjects instance is to be removed. */ - void dispose(String channelName); + void dispose(@NotNull String channelName); } diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java index 2c84809f3..d319d992f 100644 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java @@ -1,5 +1,6 @@ package io.ably.lib.objects.batch; +import org.jetbrains.annotations.NotNull; /** * The BatchContext interface represents the context for batch operations @@ -13,5 +14,6 @@ public interface BatchContext { * * @return the root LiveMap instance. */ + @NotNull BatchContextLiveMap getRoot(); } diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java index 16e7014f7..6b452cbc4 100644 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java @@ -1,7 +1,9 @@ package io.ably.lib.objects.batch; +import org.jetbrains.annotations.NotNull; + /** - * A functional interface for building and handling a BatchContext.* + * A functional interface for building and handling a BatchContext. */ @FunctionalInterface public interface BatchContextBuilder { @@ -10,5 +12,5 @@ public interface BatchContextBuilder { * * @param batchContext the BatchContext to handle. */ - void build(BatchContext batchContext); + void build(@NotNull BatchContext batchContext); } diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java index ea64a9642..f1168ee64 100644 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java +++ b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java @@ -1,5 +1,10 @@ package io.ably.lib.objects.batch; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + import java.util.Map; /** @@ -15,27 +20,34 @@ public interface BatchContextLiveMap { * @param keyName the name of the key whose value is to be retrieved. * @return the value associated with the specified key, or null if the key does not exist. */ - Object get(String keyName); + @Nullable + Object get(@NotNull String keyName); /** * Retrieves all entries (key-value pairs) in the live map. * - * @return an iterable collection of map entries. + * @return an unmodifiable iterable collection of map entries. */ + @NotNull + @Unmodifiable Iterable> entries(); /** * Retrieves all keys in the live map. * - * @return an iterable collection of keys. + * @return an unmodifiable iterable collection of keys. */ + @NotNull + @Unmodifiable Iterable keys(); /** * Retrieves all values in the live map. * - * @return an iterable collection of values. + * @return an unmodifiable iterable collection of values. */ + @NotNull + @Unmodifiable Iterable values(); /** @@ -44,20 +56,21 @@ public interface BatchContextLiveMap { * @param keyName the name of the key to set. * @param value the value to associate with the specified key. */ - void set(String keyName, Object value); + void set(@NotNull String keyName, @NotNull Object value); /** * Removes the specified key-value pair from the live map. * * @param keyName the name of the key to remove. - * @param value the value associated with the key to remove. */ - void remove(String keyName, Object value); + void remove(@NotNull String keyName); /** * Retrieves the number of entries in the live map. * * @return the size of the live map as a Long. */ + @NotNull + @Contract(pure = true) // Indicates this method does not modify the state of the object. Long size(); } diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 644ebf9b1..1ee57a190 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -185,6 +185,7 @@ private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() { return (LiveObjectsPlugin) liveObjectsImplementation.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + Log.w(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e); return null; } } diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt index da34966e3..101157d27 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt @@ -1,6 +1,6 @@ package io.ably.lib.objects -import io.ably.lib.objects.batch.BatchContext +import io.ably.lib.objects.batch.BatchContextBuilder import io.ably.lib.types.Callback internal class DefaultLiveObjects(private val channelName: String): LiveObjects { @@ -8,43 +8,47 @@ internal class DefaultLiveObjects(private val channelName: String): LiveObjects TODO("Not yet implemented") } - override fun batch(batchContextCallback: Callback?) { + override fun batch(batchContextCallback: BatchContextBuilder) { TODO("Not yet implemented") } - override fun createMap(liveMap: LiveMap?): LiveMap { + override fun createMap(liveMap: LiveMap): LiveMap { TODO("Not yet implemented") } - override fun createMap(liveCounter: LiveCounter?): LiveMap { + override fun createMap(liveCounter: LiveCounter): LiveMap { TODO("Not yet implemented") } - override fun createMap(map: MutableMap?): LiveMap { + override fun createMap(map: MutableMap): LiveMap { TODO("Not yet implemented") } - override fun createCounter(initialValue: Long?): LiveCounter { + override fun getRootAsync(callback: Callback) { TODO("Not yet implemented") } - override fun getRootAsync(callback: Callback) { + override fun batchAsync(batchContextCallback: BatchContextBuilder, callback: Callback) { + TODO("Not yet implemented") + } + + override fun createMapAsync(liveMap: LiveMap, callback: Callback) { TODO("Not yet implemented") } - override fun createMapAsync(liveMap: LiveMap?, callback: Callback?) { + override fun createMapAsync(liveCounter: LiveCounter, callback: Callback) { TODO("Not yet implemented") } - override fun createMapAsync(liveCounter: LiveCounter?, callback: Callback?) { + override fun createMapAsync(map: MutableMap, callback: Callback) { TODO("Not yet implemented") } - override fun createMapAsync(map: MutableMap?, callback: Callback?) { + override fun createCounterAsync(initialValue: Long, callback: Callback) { TODO("Not yet implemented") } - override fun createCounterAsync(initialValue: Long?, callback: Callback?) { + override fun createCounter(initialValue: Long): LiveCounter { TODO("Not yet implemented") } } diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt index 4bf6f6b36..8d038de4a 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt @@ -1,14 +1,16 @@ package io.ably.lib.objects +import java.util.concurrent.ConcurrentHashMap + public class DefaultLiveObjectsPlugin : LiveObjectsPlugin { - private val cache = mutableMapOf() + private val liveObjects = ConcurrentHashMap() override fun getInstance(channelName: String): LiveObjects { - return cache.getOrPut(channelName) { DefaultLiveObjects(channelName) } + return liveObjects.getOrPut(channelName) { DefaultLiveObjects(channelName) } } override fun dispose(channelName: String) { - cache.remove(channelName) + liveObjects.remove(channelName) } } From d81cb249fc259dbc7ecc68417ee857afd1055864 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 21 May 2025 16:30:15 +0530 Subject: [PATCH 3/5] 1. Updated public doc for LiveObjects, LiveMap and LiveCounter 2. Added live objects specific channel modes and flags to protocol message --- .../java/io/ably/lib/objects/LiveCounter.java | 10 +++++ .../java/io/ably/lib/objects/LiveMap.java | 22 ++++++++++ .../java/io/ably/lib/objects/LiveObjects.java | 42 +++++++++++++++++++ .../java/io/ably/lib/types/ChannelMode.java | 22 +++++++++- .../io/ably/lib/types/ProtocolMessage.java | 17 ++++++-- 5 files changed, 109 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java index 3c8ea410c..05c40d3ef 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java @@ -13,11 +13,19 @@ public interface LiveCounter { /** * Increments the value of the counter by 1. + * Send a COUNTER_INC operation to the realtime system to increment a value on this LiveCounter object. + * This does not modify the underlying data of this LiveCounter object. Instead, the change will be applied when + * the published COUNTER_INC operation is echoed back to the client and applied to the object following the regular + * operation application procedure. */ void increment(); /** * Increments the value of the counter by 1 asynchronously. + * Send a COUNTER_INC operation to the realtime system to increment a value on this LiveCounter object. + * This does not modify the underlying data of this LiveCounter object. Instead, the change will be applied when + * the published COUNTER_INC operation is echoed back to the client and applied to the object following the regular + * operation application procedure. * * @param callback the callback to be invoked upon completion of the operation. */ @@ -25,11 +33,13 @@ public interface LiveCounter { /** * Decrements the value of the counter by 1. + * An alias for calling {@link LiveCounter#increment()} with a negative amount. */ void decrement(); /** * Decrements the value of the counter by 1 asynchronously. + * An alias for calling {@link LiveCounter#increment()} with a negative amount. * * @param callback the callback to be invoked upon completion of the operation. */ diff --git a/lib/src/main/java/io/ably/lib/objects/LiveMap.java b/lib/src/main/java/io/ably/lib/objects/LiveMap.java index 465f10e7c..63509787a 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveMap.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveMap.java @@ -16,6 +16,12 @@ public interface LiveMap { /** * Retrieves the value associated with the specified key. + * If this map object is tombstoned (deleted), `undefined` is returned. + * If no entry is associated with the specified key, `undefined` is returned. + * If map entry is tombstoned (deleted), `undefined` is returned. + * If the value associated with the provided key is an objectId string of another LiveObject, a reference to that LiveObject + * is returned, provided it exists in the local pool and is not tombstoned. Otherwise, `undefined` is returned. + * If the value is not an objectId, then that value is returned. * * @param keyName the key whose associated value is to be returned. * @return the value associated with the specified key, or null if the key does not exist. @@ -52,6 +58,10 @@ public interface LiveMap { /** * Sets the specified key to the given value in the map. + * Send a MAP_SET operation to the realtime system to set a key on this LiveMap object to a specified value. + * This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when + * the published MAP_SET operation is echoed back to the client and applied to the object following the regular + * operation application procedure. * * @param keyName the key to be set. * @param value the value to be associated with the key. @@ -60,6 +70,10 @@ public interface LiveMap { /** * Removes the specified key and its associated value from the map. + * Send a MAP_REMOVE operation to the realtime system to tombstone a key on this LiveMap object. + * This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when + * the published MAP_REMOVE operation is echoed back to the client and applied to the object following the regular + * operation application procedure. * * @param keyName the key to be removed. */ @@ -76,6 +90,10 @@ public interface LiveMap { /** * Asynchronously sets the specified key to the given value in the map. + * Send a MAP_SET operation to the realtime system to set a key on this LiveMap object to a specified value. + * This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when + * the published MAP_SET operation is echoed back to the client and applied to the object following the regular + * operation application procedure. * * @param keyName the key to be set. * @param value the value to be associated with the key. @@ -85,6 +103,10 @@ public interface LiveMap { /** * Asynchronously removes the specified key and its associated value from the map. + * Send a MAP_REMOVE operation to the realtime system to tombstone a key on this LiveMap object. + * This does not modify the underlying data of this LiveMap object. Instead, the change will be applied when + * the published MAP_REMOVE operation is echoed back to the client and applied to the object following the regular + * operation application procedure. * * @param keyName the key to be removed. * @param callback the callback to handle the result or any errors. diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java index f51e900bf..d78120dc8 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java @@ -19,6 +19,9 @@ public interface LiveObjects { /** * Retrieves the root LiveMap object. + * When called without a type variable, we return a default root type which is based on globally defined interface for Objects feature. + * A user can provide an explicit type for the getRoot method to explicitly set the type structure on this particular channel. + * This is useful when working with multiple channels with different underlying data structure. * * @return the root LiveMap instance. */ @@ -27,6 +30,8 @@ public interface LiveObjects { /** * Initiates a batch operation and provides a BatchContext through a callback. + * Provides access to the synchronous write API for Objects that can be used to batch multiple operations + * together in a single channel message. * * @param batchContextCallback the builder to configure the batch operation. */ @@ -34,6 +39,10 @@ public interface LiveObjects { /** * Creates a new LiveMap based on an existing LiveMap. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param liveMap the existing LiveMap to base the new LiveMap on. * @return the newly created LiveMap instance. @@ -43,6 +52,10 @@ public interface LiveObjects { /** * Creates a new LiveMap based on a LiveCounter. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param liveCounter the LiveCounter to base the new LiveMap on. * @return the newly created LiveMap instance. @@ -52,6 +65,10 @@ public interface LiveObjects { /** * Creates a new LiveMap based on a standard Java Map. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param map the Java Map to base the new LiveMap on. * @return the newly created LiveMap instance. @@ -61,6 +78,10 @@ public interface LiveObjects { /** * Creates a new LiveCounter with an initial value. + * Send a COUNTER_CREATE operation to the realtime system to create a new counter object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed COUNTER_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param initialValue the initial value of the LiveCounter. * @return the newly created LiveCounter instance. @@ -70,6 +91,9 @@ public interface LiveObjects { /** * Asynchronously retrieves the root LiveMap object. + * When called without a type variable, we return a default root type which is based on globally defined interface for Objects feature. + * A user can provide an explicit type for the getRoot method to explicitly set the type structure on this particular channel. + * This is useful when working with multiple channels with different underlying data structure. * * @param callback the callback to handle the result or error. */ @@ -77,6 +101,8 @@ public interface LiveObjects { /** * Initiates a batch operation asynchronously. + * Provides access to the synchronous write API for Objects that can be used to batch multiple operations + * together in a single channel message. * * @param batchContextCallback the builder to configure the batch operation. * @param callback the Callback to handle the completion or error of the batch operation. @@ -85,6 +111,10 @@ public interface LiveObjects { /** * Asynchronously creates a new LiveMap based on an existing LiveMap. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param liveMap the existing LiveMap to base the new LiveMap on. * @param callback the callback to handle the result or error. @@ -93,6 +123,10 @@ public interface LiveObjects { /** * Asynchronously creates a new LiveMap based on a LiveCounter. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param liveCounter the LiveCounter to base the new LiveMap on. * @param callback the callback to handle the result or error. @@ -101,6 +135,10 @@ public interface LiveObjects { /** * Asynchronously creates a new LiveMap based on a standard Java Map. + * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed MAP_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param map the Java Map to base the new LiveMap on. * @param callback the callback to handle the result or error. @@ -109,6 +147,10 @@ public interface LiveObjects { /** * Asynchronously creates a new LiveCounter with an initial value. + * Send a COUNTER_CREATE operation to the realtime system to create a new counter object in the pool. + * Once the ACK message is received, the method returns the object from the local pool if it got created due to + * the echoed COUNTER_CREATE operation, or if it wasn't received yet, the method creates a new object locally + * using the provided data and returns it. * * @param initialValue the initial value of the LiveCounter. * @param callback the callback to handle the result or error. diff --git a/lib/src/main/java/io/ably/lib/types/ChannelMode.java b/lib/src/main/java/io/ably/lib/types/ChannelMode.java index 26d26ac8f..f20636933 100644 --- a/lib/src/main/java/io/ably/lib/types/ChannelMode.java +++ b/lib/src/main/java/io/ably/lib/types/ChannelMode.java @@ -24,7 +24,27 @@ public enum ChannelMode { /** * The client can receive presence messages. */ - presence_subscribe(Flag.presence_subscribe); + presence_subscribe(Flag.presence_subscribe), + + /** + * The client can publish object messages. + */ + object_publish(Flag.object_publish), + + /** + * The client can subscribe to object messages. + */ + object_subscribe(Flag.object_subscribe), + + /** + * The client can publish annotation messages. + */ + annotation_publish(Flag.annotation_publish), + + /** + * The client can subscribe to annotation messages. + */ + annotation_subscribe(Flag.annotation_subscribe); private final int mask; diff --git a/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java b/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java index 1d1d3bc69..986a8628b 100644 --- a/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java +++ b/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java @@ -45,7 +45,11 @@ public enum Action { presence, message, sync, - auth; + auth, + activate, + object, + object_sync, + annotation; public int getValue() { return ordinal(); } public static Action findByValue(int value) { return values()[value]; } @@ -57,12 +61,19 @@ public enum Flag { has_backlog(1), resumed(2), attach_resume(5), - + /* Has object flag */ + has_objects(7), /* Channel mode flags */ presence(16), publish(17), subscribe(18), - presence_subscribe(19); + presence_subscribe(19), + /* Annotation flags */ + annotation_publish(21), + annotation_subscribe(22), + /* Object flags */ + object_subscribe(24), + object_publish(25); private final int mask; From d87b365f4e65eddc8971ae2db54c98a54f8d1391 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 21 May 2025 17:54:53 +0530 Subject: [PATCH 4/5] 1. Added coroutinex as a runtime and test dependency to liveobjects 2. Added bridging interfaces to send and receive protocol messages 3. Added impl. for dispose method --- .../ably/lib/objects/LiveObjectsPlugin.java | 13 ++++--- .../lib/plugins/PluginConnectionAdapter.java | 25 +++++++++++++ .../io/ably/lib/plugins/PluginInstance.java | 25 +++++++++++++ .../io/ably/lib/realtime/AblyRealtime.java | 15 +++++--- .../java/io/ably/lib/realtime/Connection.java | 5 +-- .../ably/lib/transport/ConnectionManager.java | 29 +++++++++++++-- .../test/realtime/ConnectionManagerTest.java | 2 +- live-objects/build.gradle.kts | 3 ++ .../io/ably/lib/objects/DefaultLiveObjects.kt | 5 +++ .../lib/objects/DefaultLiveObjectsPlugin.kt | 35 +++++++++++++++++-- 10 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 lib/src/main/java/io/ably/lib/plugins/PluginConnectionAdapter.java create mode 100644 lib/src/main/java/io/ably/lib/plugins/PluginInstance.java diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java index 438312bed..29350e7a9 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java @@ -1,16 +1,19 @@ package io.ably.lib.objects; +import io.ably.lib.plugins.PluginInstance; import org.jetbrains.annotations.NotNull; /** - * The LiveObjectsPlugin interface provides a mechanism to retrieve instances of LiveObjects - * associated with specific channel names. This allows for interaction with live data objects - * in a real-time environment. + * The LiveObjectsPlugin interface provides a mechanism for managing and interacting with + * live data objects in a real-time environment. It allows for the retrieval, disposal, and + * management of LiveObjects instances associated with specific channel names. */ -public interface LiveObjectsPlugin { +public interface LiveObjectsPlugin extends PluginInstance { /** * Retrieves an instance of LiveObjects associated with the specified channel name. + * This method ensures that a LiveObjects instance is available for the given channel, + * creating one if it does not already exist. * * @param channelName the name of the channel for which the LiveObjects instance is to be retrieved. * @return the LiveObjects instance associated with the specified channel name. @@ -19,6 +22,8 @@ public interface LiveObjectsPlugin { /** * Disposes of the LiveObjects instance associated with the specified channel name. + * This method removes the LiveObjects instance for the given channel, releasing any + * resources associated with it. * * @param channelName the name of the channel whose LiveObjects instance is to be removed. */ diff --git a/lib/src/main/java/io/ably/lib/plugins/PluginConnectionAdapter.java b/lib/src/main/java/io/ably/lib/plugins/PluginConnectionAdapter.java new file mode 100644 index 000000000..5283d2120 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/plugins/PluginConnectionAdapter.java @@ -0,0 +1,25 @@ +package io.ably.lib.plugins; + +import io.ably.lib.realtime.CompletionListener; +import io.ably.lib.types.AblyException; +import io.ably.lib.types.ProtocolMessage; + +/** + * The PluginConnectionAdapter interface defines a contract for managing real-time communication + * between plugins and the Ably Realtime system. Implementations of this interface are responsible + * for sending protocol messages to their intended recipients, optionally queuing events, and + * notifying listeners of the operation's outcome. + */ +public interface PluginConnectionAdapter { + + /** + * Sends a protocol message to its intended recipient. + * This method transmits a protocol message, allowing for queuing events if necessary, + * and notifies the provided listener upon the success or failure of the send operation. + * + * @param msg the protocol message to send. + * @param listener a listener to be notified of the success or failure of the send operation. + * @throws AblyException if an error occurs during the send operation. + */ + void send(ProtocolMessage msg, CompletionListener listener) throws AblyException; +} diff --git a/lib/src/main/java/io/ably/lib/plugins/PluginInstance.java b/lib/src/main/java/io/ably/lib/plugins/PluginInstance.java new file mode 100644 index 000000000..23055f901 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/plugins/PluginInstance.java @@ -0,0 +1,25 @@ +package io.ably.lib.plugins; + +import io.ably.lib.types.ProtocolMessage; +import org.jetbrains.annotations.NotNull; + +/** + * The ProtocolMessageHandler interface defines a contract for handling protocol messages. + * Implementations of this interface are responsible for processing incoming protocol messages + * and performing the necessary actions based on the message content. + */ +public interface PluginInstance { + /** + * Handles a protocol message. + * This method is invoked whenever a protocol message is received, allowing the implementation + * to process the message and take appropriate actions. + * + * @param message the protocol message to handle. + */ + void handle(@NotNull ProtocolMessage message); + + /** + * Disposes of the plugin instance and all underlying resources. + */ + void dispose(); +} diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 1ee57a190..9768049b1 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -7,6 +7,7 @@ import java.util.Map; import io.ably.lib.objects.LiveObjectsPlugin; +import io.ably.lib.plugins.PluginConnectionAdapter; import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; @@ -71,7 +72,10 @@ public AblyRealtime(ClientOptions options) throws AblyException { super(options); final InternalChannels channels = new InternalChannels(); this.channels = channels; - connection = new Connection(this, channels, platformAgentProvider); + + liveObjectsPlugin = tryInitializeLiveObjectsPlugin(); + + connection = new Connection(this, channels, platformAgentProvider, liveObjectsPlugin); if (!StringUtils.isNullOrEmpty(options.recover)) { RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); @@ -81,8 +85,6 @@ public AblyRealtime(ClientOptions options) throws AblyException { } } - liveObjectsPlugin = tryInitializeLiveObjectsPlugin(); - if(options.autoConnect) connection.connect(); } @@ -119,6 +121,9 @@ public void close() { } connection.close(); + if (liveObjectsPlugin != null) { + liveObjectsPlugin.dispose(); + } } /** @@ -182,7 +187,9 @@ public interface Channels extends ReadOnlyMap { private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() { try { Class liveObjectsImplementation = Class.forName("io.ably.lib.objects.DefaultLiveObjectsPlugin"); - return (LiveObjectsPlugin) liveObjectsImplementation.getDeclaredConstructor().newInstance(); + return (LiveObjectsPlugin) liveObjectsImplementation + .getDeclaredConstructor(PluginConnectionAdapter.class) + .newInstance(this.connection.connectionManager); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { Log.w(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e); diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index c1ca65c70..3ba28a434 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -1,5 +1,6 @@ package io.ably.lib.realtime; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.realtime.ConnectionStateListener.ConnectionStateChange; import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; @@ -122,10 +123,10 @@ public void close() { * internal *****************/ - Connection(AblyRealtime ably, ConnectionManager.Channels channels, PlatformAgentProvider platformAgentProvider) throws AblyException { + Connection(AblyRealtime ably, ConnectionManager.Channels channels, PlatformAgentProvider platformAgentProvider, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { this.ably = ably; this.state = ConnectionState.initialized; - this.connectionManager = new ConnectionManager(ably, this, channels, platformAgentProvider); + this.connectionManager = new ConnectionManager(ably, this, channels, platformAgentProvider, liveObjectsPlugin); } public void onConnectionStateChange(ConnectionStateChange stateChange) { diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 26bd74cb7..f6f20e9eb 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -14,6 +14,8 @@ import io.ably.lib.debug.DebugOptions; import io.ably.lib.debug.DebugOptions.RawProtocolListener; import io.ably.lib.http.HttpHelpers; +import io.ably.lib.objects.LiveObjectsPlugin; +import io.ably.lib.plugins.PluginConnectionAdapter; import io.ably.lib.realtime.AblyRealtime; import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.CompletionListener; @@ -35,7 +37,7 @@ import io.ably.lib.util.PlatformAgentProvider; import io.ably.lib.util.ReconnectionStrategy; -public class ConnectionManager implements ConnectListener { +public class ConnectionManager implements ConnectListener, PluginConnectionAdapter { final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); /************************************************************** @@ -79,6 +81,13 @@ public class ConnectionManager implements ConnectListener { */ private boolean cleaningUpAfterEnteringTerminalState = false; + /** + * A nullable reference to the LiveObjects plugin. + *

+ * This field is initialized only if the LiveObjects plugin is present in the classpath. + */ + private final LiveObjectsPlugin liveObjectsPlugin; + /** * Methods on the channels map owned by the {@link AblyRealtime} instance * which the {@link ConnectionManager} needs access to. @@ -764,11 +773,12 @@ public void run() { * ConnectionManager ***********************/ - public ConnectionManager(final AblyRealtime ably, final Connection connection, final Channels channels, final PlatformAgentProvider platformAgentProvider) throws AblyException { + public ConnectionManager(final AblyRealtime ably, final Connection connection, final Channels channels, final PlatformAgentProvider platformAgentProvider, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { this.ably = ably; this.connection = connection; this.channels = channels; this.platformAgentProvider = platformAgentProvider; + this.liveObjectsPlugin = liveObjectsPlugin; ClientOptions options = ably.options; this.hosts = new Hosts(options.realtimeHost, Defaults.HOST_REALTIME, options); @@ -1220,6 +1230,16 @@ public void onMessage(ITransport transport, ProtocolMessage message) throws Ably case auth: addAction(new ReauthAction()); break; + case object: + case object_sync: + if (liveObjectsPlugin != null) { + try { + liveObjectsPlugin.handle(message); + } catch (Throwable t) { + Log.e(TAG, "LiveObjectsPlugin threw while handling message", t); + } + } + break; default: onChannelMessage(message); } @@ -1667,6 +1687,11 @@ public QueuedMessage(ProtocolMessage msg, CompletionListener listener) { } } + @Override + public void send(ProtocolMessage msg, CompletionListener listener) throws AblyException { + this.send(msg, true, listener); + } + public void send(ProtocolMessage msg, boolean queueEvents, CompletionListener listener) throws AblyException { State state; synchronized(this) { diff --git a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java index 383270153..eaaaead97 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java @@ -137,7 +137,7 @@ public void connectionmanager_fallback_none_withoutconnection() throws AblyExcep Connection connection = Mockito.mock(Connection.class); final ConnectionManager.Channels channels = Mockito.mock(ConnectionManager.Channels.class); - ConnectionManager connectionManager = new ConnectionManager(ably, connection, channels, new EmptyPlatformAgentProvider()) { + ConnectionManager connectionManager = new ConnectionManager(ably, connection, channels, new EmptyPlatformAgentProvider(), null) { @Override protected boolean checkConnectivity() { return false; diff --git a/live-objects/build.gradle.kts b/live-objects/build.gradle.kts index a6733a2cf..745a9a47c 100644 --- a/live-objects/build.gradle.kts +++ b/live-objects/build.gradle.kts @@ -10,6 +10,9 @@ repositories { dependencies { implementation(project(":java")) testImplementation(kotlin("test")) + implementation(libs.coroutine.core) + + testImplementation(libs.coroutine.test) } tasks.test { diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt index 101157d27..1253c9bab 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt @@ -51,4 +51,9 @@ internal class DefaultLiveObjects(private val channelName: String): LiveObjects override fun createCounter(initialValue: Long): LiveCounter { TODO("Not yet implemented") } + + fun dispose() { + // Dispose of any resources associated with this LiveObjects instance + // For example, close any open connections or clean up references + } } diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt index 8d038de4a..277d4df31 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt @@ -1,16 +1,47 @@ package io.ably.lib.objects +import io.ably.lib.plugins.PluginConnectionAdapter +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.types.ErrorInfo +import io.ably.lib.types.ProtocolMessage +import kotlinx.coroutines.CompletableDeferred import java.util.concurrent.ConcurrentHashMap -public class DefaultLiveObjectsPlugin : LiveObjectsPlugin { +public class DefaultLiveObjectsPlugin(private val pluginConnectionAdapter: PluginConnectionAdapter) : LiveObjectsPlugin { - private val liveObjects = ConcurrentHashMap() + private val liveObjects = ConcurrentHashMap() override fun getInstance(channelName: String): LiveObjects { return liveObjects.getOrPut(channelName) { DefaultLiveObjects(channelName) } } + public suspend fun send(message: ProtocolMessage) { + val deferred = CompletableDeferred() + pluginConnectionAdapter.send(message, object : CompletionListener { + override fun onSuccess() { + deferred.complete(Unit) + } + + override fun onError(reason: ErrorInfo) { + deferred.completeExceptionally(Exception(reason.message)) + } + }) + deferred.await() + } + + override fun handle(message: ProtocolMessage) { + TODO("Not yet implemented") + } + override fun dispose(channelName: String) { + liveObjects[channelName]?.dispose() liveObjects.remove(channelName) } + + override fun dispose() { + liveObjects.values.forEach { + it.dispose() + } + liveObjects.clear() + } } From cd7d07590ca7371e460562e4a0295fe9630311a6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 22 May 2025 18:01:01 +0530 Subject: [PATCH 5/5] 1. Added blocking and non-blocking annotations to sync and async methods 2. Removed liveobject batching operation specific interfaces --- .../java/io/ably/lib/objects/LiveCounter.java | 6 ++ .../java/io/ably/lib/objects/LiveMap.java | 6 ++ .../java/io/ably/lib/objects/LiveObjects.java | 32 +++----- .../ably/lib/objects/LiveObjectsPlugin.java | 1 + .../ably/lib/objects/batch/BatchContext.java | 19 ----- .../objects/batch/BatchContextBuilder.java | 16 ---- .../objects/batch/BatchContextLiveMap.java | 76 ------------------- .../io/ably/lib/realtime/AblyRealtime.java | 2 +- .../io/ably/lib/realtime/ChannelBase.java | 3 +- .../io/ably/lib/objects/DefaultLiveObjects.kt | 9 --- 10 files changed, 28 insertions(+), 142 deletions(-) delete mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java delete mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java delete mode 100644 lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java diff --git a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java index 05c40d3ef..fd44b853c 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveCounter.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveCounter.java @@ -1,6 +1,8 @@ package io.ably.lib.objects; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NonBlocking; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Contract; @@ -18,6 +20,7 @@ public interface LiveCounter { * the published COUNTER_INC operation is echoed back to the client and applied to the object following the regular * operation application procedure. */ + @Blocking void increment(); /** @@ -29,12 +32,14 @@ public interface LiveCounter { * * @param callback the callback to be invoked upon completion of the operation. */ + @NonBlocking void incrementAsync(@NotNull Callback callback); /** * Decrements the value of the counter by 1. * An alias for calling {@link LiveCounter#increment()} with a negative amount. */ + @Blocking void decrement(); /** @@ -43,6 +48,7 @@ public interface LiveCounter { * * @param callback the callback to be invoked upon completion of the operation. */ + @NonBlocking void decrementAsync(@NotNull Callback callback); /** diff --git a/lib/src/main/java/io/ably/lib/objects/LiveMap.java b/lib/src/main/java/io/ably/lib/objects/LiveMap.java index 63509787a..7ba4433f9 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveMap.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveMap.java @@ -1,6 +1,8 @@ package io.ably.lib.objects; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NonBlocking; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,6 +68,7 @@ public interface LiveMap { * @param keyName the key to be set. * @param value the value to be associated with the key. */ + @Blocking void set(@NotNull String keyName, @NotNull Object value); /** @@ -77,6 +80,7 @@ public interface LiveMap { * * @param keyName the key to be removed. */ + @Blocking void remove(@NotNull String keyName); /** @@ -99,6 +103,7 @@ public interface LiveMap { * @param value the value to be associated with the key. * @param callback the callback to handle the result or any errors. */ + @NonBlocking void setAsync(@NotNull String keyName, @NotNull Object value, @NotNull Callback callback); /** @@ -111,5 +116,6 @@ public interface LiveMap { * @param keyName the key to be removed. * @param callback the callback to handle the result or any errors. */ + @NonBlocking void removeAsync(@NotNull String keyName, @NotNull Callback callback); } diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java index d78120dc8..adf05df6e 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java @@ -1,7 +1,8 @@ package io.ably.lib.objects; -import io.ably.lib.objects.batch.BatchContextBuilder; import io.ably.lib.types.Callback; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NonBlocking; import org.jetbrains.annotations.NotNull; @@ -25,18 +26,10 @@ public interface LiveObjects { * * @return the root LiveMap instance. */ + @Blocking @NotNull LiveMap getRoot(); - /** - * Initiates a batch operation and provides a BatchContext through a callback. - * Provides access to the synchronous write API for Objects that can be used to batch multiple operations - * together in a single channel message. - * - * @param batchContextCallback the builder to configure the batch operation. - */ - void batch(@NotNull BatchContextBuilder batchContextCallback); - /** * Creates a new LiveMap based on an existing LiveMap. * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. @@ -47,6 +40,7 @@ public interface LiveObjects { * @param liveMap the existing LiveMap to base the new LiveMap on. * @return the newly created LiveMap instance. */ + @Blocking @NotNull LiveMap createMap(@NotNull LiveMap liveMap); @@ -60,6 +54,7 @@ public interface LiveObjects { * @param liveCounter the LiveCounter to base the new LiveMap on. * @return the newly created LiveMap instance. */ + @Blocking @NotNull LiveMap createMap(@NotNull LiveCounter liveCounter); @@ -73,6 +68,7 @@ public interface LiveObjects { * @param map the Java Map to base the new LiveMap on. * @return the newly created LiveMap instance. */ + @Blocking @NotNull LiveMap createMap(@NotNull Map map); @@ -86,6 +82,7 @@ public interface LiveObjects { * @param initialValue the initial value of the LiveCounter. * @return the newly created LiveCounter instance. */ + @Blocking @NotNull LiveCounter createCounter(@NotNull Long initialValue); @@ -97,18 +94,9 @@ public interface LiveObjects { * * @param callback the callback to handle the result or error. */ + @NonBlocking void getRootAsync(@NotNull Callback<@NotNull LiveMap> callback); - /** - * Initiates a batch operation asynchronously. - * Provides access to the synchronous write API for Objects that can be used to batch multiple operations - * together in a single channel message. - * - * @param batchContextCallback the builder to configure the batch operation. - * @param callback the Callback to handle the completion or error of the batch operation. - */ - void batchAsync(@NotNull BatchContextBuilder batchContextCallback, @NotNull Callback callback); - /** * Asynchronously creates a new LiveMap based on an existing LiveMap. * Send a MAP_CREATE operation to the realtime system to create a new map object in the pool. @@ -119,6 +107,7 @@ public interface LiveObjects { * @param liveMap the existing LiveMap to base the new LiveMap on. * @param callback the callback to handle the result or error. */ + @NonBlocking void createMapAsync(@NotNull LiveMap liveMap, @NotNull Callback<@NotNull LiveMap> callback); /** @@ -131,6 +120,7 @@ public interface LiveObjects { * @param liveCounter the LiveCounter to base the new LiveMap on. * @param callback the callback to handle the result or error. */ + @NonBlocking void createMapAsync(@NotNull LiveCounter liveCounter, @NotNull Callback<@NotNull LiveMap> callback); /** @@ -143,6 +133,7 @@ public interface LiveObjects { * @param map the Java Map to base the new LiveMap on. * @param callback the callback to handle the result or error. */ + @NonBlocking void createMapAsync(@NotNull Map map, @NotNull Callback<@NotNull LiveMap> callback); /** @@ -155,5 +146,6 @@ public interface LiveObjects { * @param initialValue the initial value of the LiveCounter. * @param callback the callback to handle the result or error. */ + @NonBlocking void createCounterAsync(@NotNull Long initialValue, @NotNull Callback<@NotNull LiveCounter> callback); } diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java index 29350e7a9..cad3e9f59 100644 --- a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java @@ -18,6 +18,7 @@ public interface LiveObjectsPlugin extends PluginInstance { * @param channelName the name of the channel for which the LiveObjects instance is to be retrieved. * @return the LiveObjects instance associated with the specified channel name. */ + @NotNull LiveObjects getInstance(@NotNull String channelName); /** diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java deleted file mode 100644 index d319d992f..000000000 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.ably.lib.objects.batch; - -import org.jetbrains.annotations.NotNull; - -/** - * The BatchContext interface represents the context for batch operations - * on live data objects. It provides access to the root LiveMap, which serves - * as the entry point for interacting with the batch context. - */ -public interface BatchContext { - - /** - * Retrieves the root LiveMap associated with this batch context. - * - * @return the root LiveMap instance. - */ - @NotNull - BatchContextLiveMap getRoot(); -} diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java deleted file mode 100644 index 6b452cbc4..000000000 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextBuilder.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.ably.lib.objects.batch; - -import org.jetbrains.annotations.NotNull; - -/** - * A functional interface for building and handling a BatchContext. - */ -@FunctionalInterface -public interface BatchContextBuilder { - /** - * Builds and handles the provided BatchContext. - * - * @param batchContext the BatchContext to handle. - */ - void build(@NotNull BatchContext batchContext); -} diff --git a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java b/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java deleted file mode 100644 index f1168ee64..000000000 --- a/lib/src/main/java/io/ably/lib/objects/batch/BatchContextLiveMap.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.ably.lib.objects.batch; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.Map; - -/** - * The BatchContextLiveMap interface provides methods to interact with a live map - * in the context of batch operations. It allows retrieving, modifying, and querying - * key-value pairs in the map. - */ -public interface BatchContextLiveMap { - - /** - * Retrieves the value associated with the specified key. - * - * @param keyName the name of the key whose value is to be retrieved. - * @return the value associated with the specified key, or null if the key does not exist. - */ - @Nullable - Object get(@NotNull String keyName); - - /** - * Retrieves all entries (key-value pairs) in the live map. - * - * @return an unmodifiable iterable collection of map entries. - */ - @NotNull - @Unmodifiable - Iterable> entries(); - - /** - * Retrieves all keys in the live map. - * - * @return an unmodifiable iterable collection of keys. - */ - @NotNull - @Unmodifiable - Iterable keys(); - - /** - * Retrieves all values in the live map. - * - * @return an unmodifiable iterable collection of values. - */ - @NotNull - @Unmodifiable - Iterable values(); - - /** - * Sets the specified key to the given value in the live map. - * - * @param keyName the name of the key to set. - * @param value the value to associate with the specified key. - */ - void set(@NotNull String keyName, @NotNull Object value); - - /** - * Removes the specified key-value pair from the live map. - * - * @param keyName the name of the key to remove. - */ - void remove(@NotNull String keyName); - - /** - * Retrieves the number of entries in the live map. - * - * @return the size of the live map as a Long. - */ - @NotNull - @Contract(pure = true) // Indicates this method does not modify the state of the object. - Long size(); -} diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 9768049b1..92e0fdbd8 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -192,7 +192,7 @@ private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() { .newInstance(this.connection.connectionManager); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - Log.w(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e); + Log.i(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e); return null; } } diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index efa3a0ae4..16470f1d6 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -98,7 +98,8 @@ public abstract class ChannelBase extends EventEmitter') to your dependency tree", 400, 40000) + new ErrorInfo("LiveObjects plugin hasn't been installed, " + + "add runtimeOnly('io.ably:live-objects:') to your dependency tree", 400, 40019) ); } return liveObjectsPlugin.getInstance(name); diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt index 1253c9bab..2fc70a2a9 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt @@ -1,6 +1,5 @@ package io.ably.lib.objects -import io.ably.lib.objects.batch.BatchContextBuilder import io.ably.lib.types.Callback internal class DefaultLiveObjects(private val channelName: String): LiveObjects { @@ -8,10 +7,6 @@ internal class DefaultLiveObjects(private val channelName: String): LiveObjects TODO("Not yet implemented") } - override fun batch(batchContextCallback: BatchContextBuilder) { - TODO("Not yet implemented") - } - override fun createMap(liveMap: LiveMap): LiveMap { TODO("Not yet implemented") } @@ -28,10 +23,6 @@ internal class DefaultLiveObjects(private val channelName: String): LiveObjects TODO("Not yet implemented") } - override fun batchAsync(batchContextCallback: BatchContextBuilder, callback: Callback) { - TODO("Not yet implemented") - } - override fun createMapAsync(liveMap: LiveMap, callback: Callback) { TODO("Not yet implemented") }