Skip to content

Commit 4f35df8

Browse files
authored
Merge pull request #1213 from ably/feature/path-based-liveobjects-final-public-api
[LiveObjects] Implement path-based LiveObjects public API
2 parents 0e5e684 + 11e87a7 commit 4f35df8

48 files changed

Lines changed: 2671 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.ably.lib.object;
2+
3+
/**
4+
* Represents a registration for receiving events from a subscribe operation.
5+
* Provides a way to clean up and remove a subscription when it is no longer
6+
* needed.
7+
*
8+
* <p>Example usage:
9+
* <pre>
10+
* {@code
11+
* Subscription s = pathObject.subscribe(event -> { ... });
12+
* // Later, when done with the subscription
13+
* s.unsubscribe();
14+
* }
15+
* </pre>
16+
*
17+
* <p>Spec: SUB1
18+
*/
19+
public interface Subscription {
20+
21+
/**
22+
* Deregisters the listener that was registered by the corresponding
23+
* {@code subscribe} call. Once called, the listener will not be invoked for
24+
* any subsequent events and references to it are cleaned up. Calling this
25+
* method more than once is a no-op.
26+
*
27+
* <p>Spec: SUB2a, SUB2b
28+
*/
29+
void unsubscribe();
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.ably.lib.object;
2+
3+
/**
4+
* The type of a value resolved by a {@code PathObject} or wrapped by an
5+
* {@code Instance} in the LiveObjects graph.
6+
*
7+
* <p>Spec: RTTS2
8+
*/
9+
public enum ValueType {
10+
/** Corresponds to the {@code String} primitive. Spec: RTTS2a1 */
11+
STRING,
12+
/** Corresponds to the {@code Number} primitive. Spec: RTTS2a2 */
13+
NUMBER,
14+
/** Corresponds to the {@code Boolean} primitive. Spec: RTTS2a3 */
15+
BOOLEAN,
16+
/** Corresponds to the {@code Binary} primitive. Spec: RTTS2a4 */
17+
BINARY,
18+
/** Corresponds to the {@code JsonObject} primitive. Spec: RTTS2a5 */
19+
JSON_OBJECT,
20+
/** Corresponds to the {@code JsonArray} primitive. Spec: RTTS2a6 */
21+
JSON_ARRAY,
22+
/** Corresponds to a {@code LiveMap} object. Spec: RTTS2a7 */
23+
LIVE_MAP,
24+
/** Corresponds to a {@code LiveCounter} object. Spec: RTTS2a8 */
25+
LIVE_COUNTER,
26+
/** 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 */
27+
UNKNOWN,
28+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package io.ably.lib.object.instance;
2+
3+
import com.google.gson.JsonElement;
4+
import io.ably.lib.object.ValueType;
5+
import io.ably.lib.object.instance.types.BinaryInstance;
6+
import io.ably.lib.object.instance.types.BooleanInstance;
7+
import io.ably.lib.object.instance.types.JsonArrayInstance;
8+
import io.ably.lib.object.instance.types.JsonObjectInstance;
9+
import io.ably.lib.object.instance.types.LiveCounterInstance;
10+
import io.ably.lib.object.instance.types.LiveMapInstance;
11+
import io.ably.lib.object.instance.types.NumberInstance;
12+
import io.ably.lib.object.instance.types.StringInstance;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
/**
16+
* A direct-reference view of a single resolved LiveObject ({@code LiveMap} or
17+
* {@code LiveCounter}) or primitive value.
18+
*
19+
* <p>Unlike {@code PathObject}, which re-resolves its path on every call, an
20+
* {@code Instance} is identity-addressed: it is bound to a specific underlying value
21+
* and dereferenced in O(1), regardless of where that value sits in the graph. Read
22+
* operations validate the access API preconditions and fail with an
23+
* {@code AblyException} if they are not satisfied.
24+
*
25+
* <p>This base type exposes only the methods whose behaviour is independent of the
26+
* wrapped type; everything else - including {@code subscribe} (RTTS7b) - is
27+
* partitioned onto the sub-types. Use the {@code as*} helpers to obtain a sub-type
28+
* view without type validation, or discriminate via {@link #getType()}.
29+
*
30+
* <p>Spec: RTINS1, RTTS7
31+
*
32+
* @see LiveMapInstance
33+
* @see LiveCounterInstance
34+
* @see InstanceListener
35+
*/
36+
public interface Instance {
37+
38+
/**
39+
* Returns the {@link ValueType} of the value wrapped by this instance. Use this
40+
* instead of dedicated {@code isLiveMap}/{@code isLiveCounter}/etc. checks.
41+
*
42+
* <p>An {@code Instance} is always constructed from a resolved value, so this never
43+
* returns {@link ValueType#UNKNOWN} in normal operation.
44+
*
45+
* <p>Spec: RTTS8a
46+
*
47+
* @return the wrapped value type
48+
*/
49+
@NotNull ValueType getType();
50+
51+
/**
52+
* Returns a JSON-serializable, recursively compacted snapshot of the wrapped value.
53+
* Behaves identically to {@code PathObject#compactJson} except that it operates on
54+
* the wrapped value directly instead of resolving a path. An {@code Instance} is
55+
* always bound to a resolved value, so this always returns a non-null result;
56+
* failures of the access API preconditions are signalled via {@code AblyException}.
57+
*
58+
* <p>Spec: RTINS11 / RTINS11c (universal non-null invariant - Instance is bound
59+
* to an already-resolved value, so the path-resolution failure mode of
60+
* PathObject#compactJson does not apply) / RTTS7a (typed-SDK signature reflects
61+
* the universal invariant)
62+
*
63+
* @return the compacted JSON snapshot
64+
*/
65+
@NotNull JsonElement compactJson();
66+
67+
/**
68+
* Returns this instance wrapped as a {@link LiveMapInstance}.
69+
*
70+
* <p>Best-effort cast; does not validate the underlying type. Read operations on
71+
* the returned wrapper are always permitted; write/terminal operations will fail
72+
* at call time if the wrapped value is not a {@code LiveMap}.
73+
*
74+
* <p>Spec: RTTS9a
75+
*
76+
* @return a {@link LiveMapInstance} view of this instance
77+
*/
78+
@NotNull LiveMapInstance asLiveMap();
79+
80+
/**
81+
* Returns this instance wrapped as a {@link LiveCounterInstance}.
82+
* Best-effort cast; does not validate the underlying type.
83+
*
84+
* <p>Spec: RTTS9b
85+
*
86+
* @return a {@link LiveCounterInstance} view of this instance
87+
*/
88+
@NotNull LiveCounterInstance asLiveCounter();
89+
90+
/**
91+
* Returns this instance wrapped as a {@link NumberInstance}.
92+
* Best-effort cast; does not validate the underlying type.
93+
*
94+
* <p>Spec: RTTS9c
95+
*
96+
* @return a {@link NumberInstance} view of this instance
97+
*/
98+
@NotNull NumberInstance asNumber();
99+
100+
/**
101+
* Returns this instance wrapped as a {@link StringInstance}.
102+
* Best-effort cast; does not validate the underlying type.
103+
*
104+
* <p>Spec: RTTS9c
105+
*
106+
* @return a {@link StringInstance} view of this instance
107+
*/
108+
@NotNull StringInstance asString();
109+
110+
/**
111+
* Returns this instance wrapped as a {@link BooleanInstance}.
112+
* Best-effort cast; does not validate the underlying type.
113+
*
114+
* <p>Spec: RTTS9c
115+
*
116+
* @return a {@link BooleanInstance} view of this instance
117+
*/
118+
@NotNull BooleanInstance asBoolean();
119+
120+
/**
121+
* Returns this instance wrapped as a {@link BinaryInstance}.
122+
* Best-effort cast; does not validate the underlying type.
123+
*
124+
* <p>Spec: RTTS9c
125+
*
126+
* @return a {@link BinaryInstance} view of this instance
127+
*/
128+
@NotNull BinaryInstance asBinary();
129+
130+
/**
131+
* Returns this instance wrapped as a {@link JsonObjectInstance}.
132+
* Best-effort cast; does not validate the underlying type.
133+
*
134+
* <p>Spec: RTTS9c
135+
*
136+
* @return a {@link JsonObjectInstance} view of this instance
137+
*/
138+
@NotNull JsonObjectInstance asJsonObject();
139+
140+
/**
141+
* Returns this instance wrapped as a {@link JsonArrayInstance}.
142+
* Best-effort cast; does not validate the underlying type.
143+
*
144+
* <p>Spec: RTTS9c
145+
*
146+
* @return a {@link JsonArrayInstance} view of this instance
147+
*/
148+
@NotNull JsonArrayInstance asJsonArray();
149+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.ably.lib.object.instance;
2+
3+
import io.ably.lib.object.instance.types.LiveCounterInstance;
4+
import io.ably.lib.object.instance.types.LiveMapInstance;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
/**
8+
* Listener interface for instance subscriptions created via
9+
* {@link LiveMapInstance#subscribe(InstanceListener)} or
10+
* {@link LiveCounterInstance#subscribe(InstanceListener)}.
11+
*
12+
* <p>Spec: RTINS16a1
13+
*/
14+
public interface InstanceListener {
15+
16+
/**
17+
* Invoked when the wrapped LiveObject is modified.
18+
*
19+
* @param event the event describing the change
20+
*/
21+
void onUpdated(@NotNull InstanceSubscriptionEvent event);
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.ably.lib.object.instance;
2+
3+
import io.ably.lib.object.instance.types.LiveCounterInstance;
4+
import io.ably.lib.object.instance.types.LiveMapInstance;
5+
import io.ably.lib.object.message.ObjectMessage;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
/**
10+
* Event delivered to {@link InstanceListener#onUpdated(InstanceSubscriptionEvent)} when
11+
* the LiveObject wrapped by a subscribed {@link LiveMapInstance} or
12+
* {@link LiveCounterInstance} is updated.
13+
*
14+
* <p>Spec: RTINS16e
15+
*/
16+
public interface InstanceSubscriptionEvent {
17+
18+
/**
19+
* Returns an {@link Instance} wrapping the LiveObject that was updated.
20+
*
21+
* <p>Spec: RTINS16e1
22+
*
23+
* @return the updated instance
24+
*/
25+
@NotNull Instance getObject();
26+
27+
/**
28+
* Returns the {@link ObjectMessage} describing the operation that caused this
29+
* event, if any. The value is present whenever the underlying update carried an
30+
* object message with an operation; otherwise it is {@code null}.
31+
*
32+
* <p>Spec: RTINS16e2 / PAOM1
33+
*
34+
* @return the source {@code ObjectMessage}, or {@code null} if unavailable
35+
*/
36+
@Nullable ObjectMessage getMessage();
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* The identity-addressed view of the LiveObjects graph.
3+
* {@link io.ably.lib.object.instance.Instance} wraps a specific resolved
4+
* LiveObject or primitive value and dereferences it in O(1), following the
5+
* object wherever it sits in the graph. Type-specific operations live on the
6+
* sub-types in {@link io.ably.lib.object.instance.types}; instance
7+
* subscriptions use {@link io.ably.lib.object.instance.InstanceListener} and
8+
* {@link io.ably.lib.object.instance.InstanceSubscriptionEvent}.
9+
*
10+
* <p>Spec: RTINS1-RTINS16, RTTS7-RTTS9
11+
*/
12+
package io.ably.lib.object.instance;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.ably.lib.object.instance.types;
2+
3+
import io.ably.lib.object.instance.Instance;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
/**
7+
* A read-only {@link Instance} bound to a binary primitive value
8+
* (a {@code byte[]}).
9+
* Primitive instances are anonymous (no object id) and deliberately do not expose
10+
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
11+
* methods - only {@code value()} - per RTTS10c.
12+
*
13+
* <p>Spec: RTTS10c
14+
*/
15+
public interface BinaryInstance extends Instance {
16+
17+
/**
18+
* Returns the wrapped binary value.
19+
*
20+
* <p>Spec: RTINS4 / RTTS10c
21+
*
22+
* @return the wrapped bytes
23+
*/
24+
byte @NotNull [] value();
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.ably.lib.object.instance.types;
2+
3+
import io.ably.lib.object.instance.Instance;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
/**
7+
* A read-only {@link Instance} bound to a {@code Boolean} primitive value.
8+
* Primitive instances are anonymous (no object id) and deliberately do not expose
9+
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
10+
* methods - only {@code value()} - per RTTS10c.
11+
*
12+
* <p>Spec: RTTS10c
13+
*/
14+
public interface BooleanInstance extends Instance {
15+
16+
/**
17+
* Returns the wrapped boolean.
18+
*
19+
* <p>Spec: RTINS4 / RTTS10c
20+
*
21+
* @return the wrapped boolean value
22+
*/
23+
@NotNull
24+
Boolean value();
25+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.ably.lib.object.instance.types;
2+
3+
import com.google.gson.JsonArray;
4+
import io.ably.lib.object.instance.Instance;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
/**
8+
* A read-only {@link Instance} bound to a {@link JsonArray} primitive value.
9+
* Primitive instances are anonymous (no object id) and deliberately do not expose
10+
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
11+
* methods - only {@code value()} - per RTTS10c.
12+
*
13+
* <p>Spec: RTTS10c
14+
*/
15+
public interface JsonArrayInstance extends Instance {
16+
17+
/**
18+
* Returns the wrapped JSON array.
19+
*
20+
* <p>Spec: RTINS4 / RTTS10c
21+
*
22+
* @return the wrapped JsonArray value
23+
*/
24+
@NotNull
25+
JsonArray value();
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.ably.lib.object.instance.types;
2+
3+
import com.google.gson.JsonObject;
4+
import io.ably.lib.object.instance.Instance;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
/**
8+
* A read-only {@link Instance} bound to a {@link JsonObject} primitive value.
9+
* Primitive instances are anonymous (no object id) and deliberately do not expose
10+
* {@code subscribe}, {@code set}, {@code remove} or any other id/iteration/write
11+
* methods - only {@code value()} - per RTTS10c.
12+
*
13+
* <p>Spec: RTTS10c
14+
*/
15+
public interface JsonObjectInstance extends Instance {
16+
17+
/**
18+
* Returns the wrapped JSON object.
19+
*
20+
* <p>Spec: RTINS4 / RTTS10c
21+
*
22+
* @return the wrapped JsonObject value
23+
*/
24+
@NotNull
25+
JsonObject value();
26+
}

0 commit comments

Comments
 (0)