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/ObjectType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.ably.lib.object;

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

import com.google.gson.JsonElement;
import io.ably.lib.object.ObjectType;
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;
import org.jetbrains.annotations.Nullable;

/**
* 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 identified by its object id (for live objects) 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.
*
* <p>Spec: RTINS1
*/
public interface LiveObjectInstance {

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

/**
* Returns the object id of the wrapped LiveObject, or {@code null} when the wrapped
* value is a primitive. Only {@link LiveMapInstance} and {@link LiveCounterInstance}
* ever return a non-null id.
*
* <p>Spec: RTINS3
*
* @return the wrapped object's id, or {@code null} for primitive instances
*/
@Nullable String getId();

/**
* 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.
*
* <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);

/**
* Unsubscribes the specified listener previously registered via
* {@link #subscribe(Listener)}. No-op if the listener is not currently subscribed.
*
* @param listener the listener to remove
*/
@NonBlocking
void unsubscribe(@NotNull Listener listener);

/**
* Removes all listeners previously registered on this instance.
*/
@NonBlocking
void unsubscribeAll();

/**
* 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 LiveObjectInstance#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 LiveObjectInstance} that was updated.
*
* @return the updated instance
*/
@NotNull LiveObjectInstance getInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.ably.lib.object.instance.types;

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

/**
* A read-only {@link LiveObjectInstance} bound to a binary primitive value
* (a {@code byte[]}).
*
* <p>{@link #getId()} always returns {@code null} for primitive instances, and
* subscribe operations are not supported.
*/
public interface BinaryInstance extends LiveObjectInstance {

/**
* 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,23 @@
package io.ably.lib.object.instance.types;

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

/**
* A read-only {@link LiveObjectInstance} bound to a {@code Boolean} primitive value.
*
* <p>{@link #getId()} always returns {@code null} for primitive instances, and
* subscribe operations are not supported.
*/
public interface BooleanInstance extends LiveObjectInstance {

/**
* 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,24 @@
package io.ably.lib.object.instance.types;

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

/**
* A read-only {@link LiveObjectInstance} bound to a {@link JsonArray} primitive value.
*
* <p>{@link #getId()} always returns {@code null} for primitive instances, and
* subscribe operations are not supported.
*/
public interface JsonArrayInstance extends LiveObjectInstance {

/**
* 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,24 @@
package io.ably.lib.object.instance.types;

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

/**
* A read-only {@link LiveObjectInstance} bound to a {@link JsonObject} primitive value.
*
* <p>{@link #getId()} always returns {@code null} for primitive instances, and
* subscribe operations are not supported.
*/
public interface JsonObjectInstance extends LiveObjectInstance {

/**
* 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,72 @@
package io.ably.lib.object.instance.types;

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

import java.util.concurrent.CompletableFuture;

/**
* A {@link LiveObjectInstance} 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 LiveObjectInstance {

/**
* 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