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
30 changes: 30 additions & 0 deletions lib/src/main/java/io/ably/lib/object/Subscription.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.ably.lib.object;

/**
* Represents a registration for receiving events from a subscribe operation.
* Provides a way to clean up and remove a subscription when it is no longer
* needed.
*
* <p>Example usage:
* <pre>
* {@code
* Subscription s = pathObject.subscribe(event -> { ... });
* // Later, when done with the subscription
* s.unsubscribe();
* }
* </pre>
*
* <p>Spec: SUB1
*/
public interface Subscription {

/**
* Deregisters the listener that was registered by the corresponding
* {@code subscribe} call. Once called, the listener will not be invoked for
* any subsequent events and references to it are cleaned up. Calling this
* method more than once is a no-op.
*
* <p>Spec: SUB2a, SUB2b
*/
void unsubscribe();
}
28 changes: 28 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,28 @@
package io.ably.lib.object;

/**
* The type of a value resolved by a {@code PathObject} or wrapped by an
* {@code Instance} in the LiveObjects graph.
*
* <p>Spec: RTTS2
*/
public enum ValueType {
/** Corresponds to the {@code String} primitive. Spec: RTTS2a1 */
STRING,
/** Corresponds to the {@code Number} primitive. Spec: RTTS2a2 */
NUMBER,
/** Corresponds to the {@code Boolean} primitive. Spec: RTTS2a3 */
BOOLEAN,
/** Corresponds to the {@code Binary} primitive. Spec: RTTS2a4 */
BINARY,
/** Corresponds to the {@code JsonObject} primitive. Spec: RTTS2a5 */
JSON_OBJECT,
/** Corresponds to the {@code JsonArray} primitive. Spec: RTTS2a6 */
JSON_ARRAY,
/** Corresponds to a {@code LiveMap} object. Spec: RTTS2a7 */
LIVE_MAP,
/** Corresponds to a {@code LiveCounter} object. Spec: RTTS2a8 */
LIVE_COUNTER,
/** Returned when path resolution fails or the resolved value has none of the known types; never produced by an {@code Instance} in normal operation. Spec: RTTS2a9 */
UNKNOWN,
}
149 changes: 149 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,149 @@
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 org.jetbrains.annotations.NotNull;

/**
* A direct-reference view of a single resolved LiveObject ({@code LiveMap} or
* {@code LiveCounter}) or primitive value.
*
* <p>Unlike {@code PathObject}, which re-resolves its path on every call, an
* {@code Instance} is identity-addressed: it is bound to a specific underlying value
* and dereferenced in O(1), regardless of where that value sits in the graph. Read
* operations validate the access API preconditions and fail with an
* {@code AblyException} if they are not satisfied.
*
* <p>This base type exposes only the methods whose behaviour is independent of the
* wrapped type; everything else - including {@code subscribe} (RTTS7b) - is
* partitioned onto the sub-types. Use the {@code as*} helpers to obtain a sub-type
* view without type validation, or discriminate via {@link #getType()}.
*
* <p>Spec: RTINS1, RTTS7
*
* @see LiveMapInstance
* @see LiveCounterInstance
* @see InstanceListener
*/
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.
*
* <p>An {@code Instance} is always constructed from a resolved value, so this never
* returns {@link ValueType#UNKNOWN} in normal operation.
*
* <p>Spec: RTTS8a
*
* @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 / RTINS11c (universal non-null invariant - Instance is bound
* to an already-resolved value, so the path-resolution failure mode of
* PathObject#compactJson does not apply) / RTTS7a (typed-SDK signature reflects
* the universal invariant)
*
* @return the compacted JSON snapshot
*/
@NotNull JsonElement compactJson();

/**
* 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}.
*
* <p>Spec: RTTS9a
*
* @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.
*
* <p>Spec: RTTS9b
*
* @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.
*
* <p>Spec: RTTS9c
*
* @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.
*
* <p>Spec: RTTS9c
*
* @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.
*
* <p>Spec: RTTS9c
*
* @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.
*
* <p>Spec: RTTS9c
*
* @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.
*
* <p>Spec: RTTS9c
*
* @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.
*
* <p>Spec: RTTS9c
*
* @return a {@link JsonArrayInstance} view of this instance
*/
@NotNull JsonArrayInstance asJsonArray();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.ably.lib.object.instance;

import io.ably.lib.object.instance.types.LiveCounterInstance;
import io.ably.lib.object.instance.types.LiveMapInstance;
import org.jetbrains.annotations.NotNull;

/**
* Listener interface for instance subscriptions created via
* {@link LiveMapInstance#subscribe(InstanceListener)} or
* {@link LiveCounterInstance#subscribe(InstanceListener)}.
*
* <p>Spec: RTINS16a1
*/
public interface InstanceListener {

/**
* Invoked when the wrapped LiveObject is modified.
*
* @param event the event describing the change
*/
void onUpdated(@NotNull InstanceSubscriptionEvent event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.ably.lib.object.instance;

import io.ably.lib.object.instance.types.LiveCounterInstance;
import io.ably.lib.object.instance.types.LiveMapInstance;
import io.ably.lib.object.message.ObjectMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Event delivered to {@link InstanceListener#onUpdated(InstanceSubscriptionEvent)} when
* the LiveObject wrapped by a subscribed {@link LiveMapInstance} or
* {@link LiveCounterInstance} is updated.
*
* <p>Spec: RTINS16e
*/
public interface InstanceSubscriptionEvent {

/**
* Returns an {@link Instance} wrapping the LiveObject that was updated.
*
* <p>Spec: RTINS16e1
*
* @return the updated instance
*/
@NotNull Instance getObject();

/**
* Returns the {@link ObjectMessage} describing the operation that caused this
* event, if any. The value is present whenever the underlying update carried an
* object message with an operation; otherwise it is {@code null}.
*
* <p>Spec: RTINS16e2 / PAOM1
*
* @return the source {@code ObjectMessage}, or {@code null} if unavailable
*/
@Nullable ObjectMessage getMessage();
}
12 changes: 12 additions & 0 deletions lib/src/main/java/io/ably/lib/object/instance/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* The identity-addressed view of the LiveObjects graph.
* {@link io.ably.lib.object.instance.Instance} wraps a specific resolved
* LiveObject or primitive value and dereferences it in O(1), following the
* object wherever it sits in the graph. Type-specific operations live on the
* sub-types in {@link io.ably.lib.object.instance.types}; instance
* subscriptions use {@link io.ably.lib.object.instance.InstanceListener} and
* {@link io.ably.lib.object.instance.InstanceSubscriptionEvent}.
*
* <p>Spec: RTINS1-RTINS16, RTTS7-RTTS9
*/
package io.ably.lib.object.instance;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 deliberately do not expose
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
* methods - only {@code value()} - per RTTS10c.
*
* <p>Spec: RTTS10c
*/
public interface BinaryInstance extends Instance {

/**
* Returns the wrapped binary value.
*
* <p>Spec: RTINS4 / RTTS10c
*
* @return the wrapped bytes
*/
byte @NotNull [] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 deliberately do not expose
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
* methods - only {@code value()} - per RTTS10c.
*
* <p>Spec: RTTS10c
*/
public interface BooleanInstance extends Instance {

/**
* Returns the wrapped boolean.
*
* <p>Spec: RTINS4 / RTTS10c
*
* @return the wrapped boolean value
*/
@NotNull
Boolean value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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 deliberately do not expose
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
* methods - only {@code value()} - per RTTS10c.
*
* <p>Spec: RTTS10c
*/
public interface JsonArrayInstance extends Instance {

/**
* Returns the wrapped JSON array.
*
* <p>Spec: RTINS4 / RTTS10c
*
* @return the wrapped JsonArray value
*/
@NotNull
JsonArray value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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 deliberately do not expose
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
* methods - only {@code value()} - per RTTS10c.
*
* <p>Spec: RTTS10c
*/
public interface JsonObjectInstance extends Instance {

/**
* Returns the wrapped JSON object.
*
* <p>Spec: RTINS4 / RTTS10c
*
* @return the wrapped JsonObject value
*/
@NotNull
JsonObject value();
}
Loading
Loading