Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/src/main/java/io/ably/lib/object/ValueType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.ably.lib.object;

public enum ValueType {
STRING,
NUMBER,
BOOLEAN,
BINARY,
JSON_OBJECT,
JSON_ARRAY,
LIVE_MAP,
LIVE_COUNTER,
UNKNOWN,
}
169 changes: 169 additions & 0 deletions lib/src/main/java/io/ably/lib/object/instance/Instance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package io.ably.lib.object.instance;

import com.google.gson.JsonElement;
import io.ably.lib.object.ValueType;
import io.ably.lib.object.instance.types.BinaryInstance;
import io.ably.lib.object.instance.types.BooleanInstance;
import io.ably.lib.object.instance.types.JsonArrayInstance;
import io.ably.lib.object.instance.types.JsonObjectInstance;
import io.ably.lib.object.instance.types.LiveCounterInstance;
import io.ably.lib.object.instance.types.LiveMapInstance;
import io.ably.lib.object.instance.types.NumberInstance;
import io.ably.lib.object.instance.types.StringInstance;
import io.ably.lib.objects.ObjectsSubscription;
import org.jetbrains.annotations.NonBlocking;
import org.jetbrains.annotations.NotNull;

/**
* A direct-reference view of a single LiveObject (a {@code LiveMap} or {@code LiveCounter})
* or a primitive value. Unlike {@code PathObject}, which resolves a path lazily against
* the LiveObjects graph at every call, an {@code Instance} is bound to a specific
* underlying value and dereferenced in O(1).
*
* <p>Java exposes type-specific sub-types ({@link LiveMapInstance},
* {@link LiveCounterInstance}, and the primitive {@code *Instance} types). Use the
* {@code as*} helpers to obtain a sub-type wrapper without performing type validation.
* Only {@link LiveMapInstance} and {@link LiveCounterInstance} expose an object id
* (via their own {@code getId()} methods); primitive instances are anonymous.
*
* <p>Spec: RTINS1
*/
public interface Instance {

/**
* Returns the {@link ValueType} of the value wrapped by this instance. Use this
* instead of dedicated {@code isLiveMap}/{@code isLiveCounter}/etc. checks.
*
* @return the wrapped value type
*/
@NotNull ValueType getType();

/**
* Returns a JSON-serializable, recursively compacted snapshot of the wrapped value.
* Behaves identically to {@code PathObject#compactJson} except that it operates on
* the wrapped value directly instead of resolving a path. An {@code Instance} is
* always bound to a resolved value, so this always returns a non-null result;
* failures of the access API preconditions are signalled via {@code AblyException}.
*
* <p>Spec: RTINS11
*
* @return the compacted JSON snapshot
*/
@NotNull JsonElement compactJson();

/**
* Subscribes a listener for updates on the underlying LiveObject. The listener is
* invoked whenever the wrapped object is changed by a local or remote operation.
* Call {@link ObjectsSubscription#unsubscribe()} on the returned handle to stop
* receiving events for this listener.
*
* <p>Subscribe is not supported on primitive instances; implementations may throw
* when called on {@link NumberInstance}, {@link StringInstance},
* {@link BooleanInstance}, {@link BinaryInstance}, {@link JsonObjectInstance} or
* {@link JsonArrayInstance}.
*
* <p>Spec: RTINS16
*
* @param listener the listener to invoke on updates
* @return a subscription handle that can be used to unsubscribe this listener
*/
@NonBlocking
@NotNull ObjectsSubscription subscribe(@NotNull Listener listener);

/**
* Returns this instance wrapped as a {@link LiveMapInstance}.
*
* <p>Best-effort cast; does not validate the underlying type. Read operations on
* the returned wrapper are always permitted; write/terminal operations will fail
* at call time if the wrapped value is not a {@code LiveMap}.
*
* @return a {@link LiveMapInstance} view of this instance
*/
@NotNull LiveMapInstance asLiveMap();

/**
* Returns this instance wrapped as a {@link LiveCounterInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link LiveCounterInstance} view of this instance
*/
@NotNull LiveCounterInstance asLiveCounter();

/**
* Returns this instance wrapped as a {@link NumberInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link NumberInstance} view of this instance
*/
@NotNull NumberInstance asNumber();

/**
* Returns this instance wrapped as a {@link StringInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link StringInstance} view of this instance
*/
@NotNull StringInstance asString();

/**
* Returns this instance wrapped as a {@link BooleanInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link BooleanInstance} view of this instance
*/
@NotNull BooleanInstance asBoolean();

/**
* Returns this instance wrapped as a {@link BinaryInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link BinaryInstance} view of this instance
*/
@NotNull BinaryInstance asBinary();

/**
* Returns this instance wrapped as a {@link JsonObjectInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link JsonObjectInstance} view of this instance
*/
@NotNull JsonObjectInstance asJsonObject();

/**
* Returns this instance wrapped as a {@link JsonArrayInstance}.
* Best-effort cast; does not validate the underlying type.
*
* @return a {@link JsonArrayInstance} view of this instance
*/
@NotNull JsonArrayInstance asJsonArray();

/**
* Listener interface for {@link Instance#subscribe(Listener) instance
* subscriptions}.
*
* <p>Spec: RTINS16a1
*/
interface Listener {
/**
* Invoked when the wrapped LiveObject is modified.
*
* @param event the event describing the change
*/
void onUpdated(@NotNull SubscriptionEvent event);
}

/**
* Event delivered to {@link Listener#onUpdated(SubscriptionEvent)} when the wrapped
* LiveObject is updated.
*
* <p>Spec: RTINS16e
*/
interface SubscriptionEvent {
/**
* Returns the {@link Instance} that was updated.
*
* @return the updated instance
*/
@NotNull Instance getInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.ably.lib.object.instance.types;

import io.ably.lib.object.instance.Instance;
import org.jetbrains.annotations.NotNull;

/**
* A read-only {@link Instance} bound to a binary primitive value
* (a {@code byte[]}). Primitive instances are anonymous (no object id) and do not
* support subscribe.
*/
public interface BinaryInstance extends Instance {

/**
* Returns the wrapped binary value.
*
* <p>Spec: RTINS4
*
* @return the wrapped bytes
*/
byte @NotNull [] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.ably.lib.object.instance.types;

import io.ably.lib.object.instance.Instance;
import org.jetbrains.annotations.NotNull;

/**
* A read-only {@link Instance} bound to a {@code Boolean} primitive value.
* Primitive instances are anonymous (no object id) and do not support subscribe.
*/
public interface BooleanInstance extends Instance {

/**
* Returns the wrapped boolean.
*
* <p>Spec: RTINS4
*
* @return the wrapped boolean value
*/
@NotNull
Boolean value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.ably.lib.object.instance.types;

import com.google.gson.JsonArray;
import io.ably.lib.object.instance.Instance;
import org.jetbrains.annotations.NotNull;

/**
* A read-only {@link Instance} bound to a {@link JsonArray} primitive value.
* Primitive instances are anonymous (no object id) and do not support subscribe.
*/
public interface JsonArrayInstance extends Instance {

/**
* Returns the wrapped JSON array.
*
* <p>Spec: RTINS4
*
* @return the wrapped JsonArray value
*/
@NotNull
JsonArray value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.ably.lib.object.instance.types;

import com.google.gson.JsonObject;
import io.ably.lib.object.instance.Instance;
import org.jetbrains.annotations.NotNull;

/**
* A read-only {@link Instance} bound to a {@link JsonObject} primitive value.
* Primitive instances are anonymous (no object id) and do not support subscribe.
*/
public interface JsonObjectInstance extends Instance {

/**
* Returns the wrapped JSON object.
*
* <p>Spec: RTINS4
*
* @return the wrapped JsonObject value
*/
@NotNull
JsonObject value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.ably.lib.object.instance.types;

import io.ably.lib.object.instance.Instance;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.CompletableFuture;

/**
* A {@link Instance} bound to a {@code LiveCounter}. Provides type-safe
* access to counter operations such as {@link #value()}, {@link #increment(Number)}
* and {@link #decrement(Number)}.
*/
public interface LiveCounterInstance extends Instance {

/**
* Returns the object id of the wrapped {@code LiveCounter}.
*
* <p>Spec: RTINS3a
*
* @return the wrapped {@code LiveCounter}'s object id
*/
@NotNull
String getId();

/**
* Returns the current value of the wrapped {@code LiveCounter}.
*
* <p>Spec: RTINS4 / RTLC5
*
* @return the counter value
*/
@NotNull
Double value();

/**
* Increments the wrapped {@code LiveCounter} by {@code 1}. Equivalent to
* calling {@link #increment(Number)} with {@code 1}.
*
* <p>Spec: RTINS14a1 (default {@code amount} of {@code 1})
*
* @return a future that completes when the operation has been acknowledged
*/
@NotNull
CompletableFuture<Void> increment();

/**
* Increments the wrapped {@code LiveCounter} by {@code amount}.
*
* <p>Sends a {@code COUNTER_INC} operation to the realtime system; the local state
* is updated when the operation is echoed back.
*
* <p>Spec: RTINS14
*
* @param amount the amount to add (may be negative)
* @return a future that completes when the operation has been acknowledged
*/
@NotNull
CompletableFuture<Void> increment(@NotNull Number amount);

/**
* Decrements the wrapped {@code LiveCounter} by {@code 1}. Equivalent to
* calling {@link #decrement(Number)} with {@code 1}.
*
* <p>Spec: RTINS15a1 (default {@code amount} of {@code 1})
*
* @return a future that completes when the operation has been acknowledged
*/
@NotNull
CompletableFuture<Void> decrement();

/**
* Decrements the wrapped {@code LiveCounter} by {@code amount}. Equivalent to
* calling {@link #increment(Number)} with a negated value.
*
* <p>Spec: RTINS15
*
* @param amount the amount to subtract (may be negative)
* @return a future that completes when the operation has been acknowledged
*/
@NotNull
CompletableFuture<Void> decrement(@NotNull Number amount);
}
Loading
Loading