Skip to content

Commit 3471b02

Browse files
authored
Merge pull request #1217 from ably/chore/liveobjects-add-basic-implementation
[AIT-928] Added basic impl. for PathObject and Instance interfaces
2 parents 18485d6 + 3e8f70c commit 3471b02

45 files changed

Lines changed: 1821 additions & 48 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/src/main/java/io/ably/lib/object/ValueType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public enum ValueType {
2323
LIVE_MAP,
2424
/** Corresponds to a {@code LiveCounter} object. Spec: RTTS2a8 */
2525
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 */
26+
/** Returned by {@code PathObject#getType()} only when a value is present but matches none of the known types. Never produced by an {@code Instance} in normal operation. Spec: RTTS2a9 */
2727
UNKNOWN,
2828
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.ably.lib.object.adapter;
2+
3+
import io.ably.lib.realtime.ChannelBase;
4+
import io.ably.lib.realtime.Connection;
5+
import io.ably.lib.types.AblyException;
6+
import io.ably.lib.types.ClientOptions;
7+
import org.jetbrains.annotations.Blocking;
8+
import org.jetbrains.annotations.NotNull;
9+
10+
/**
11+
* Bridges the path-based LiveObjects implementation to the core Ably client, exposing the
12+
* client configuration, connection and channel state it needs without coupling it to the
13+
* concrete {@link io.ably.lib.realtime.AblyRealtime} type.
14+
*
15+
* <p>This is the adapter for the path-based {@code io.ably.lib.object} API and is intentionally
16+
* kept independent of the legacy {@code io.ably.lib.objects} package.
17+
*/
18+
public interface AblyClientAdapter {
19+
/**
20+
* Retrieves the client options configured for the Ably client.
21+
* Used to access client configuration parameters such as echoMessages setting
22+
* that affect the behavior of Objects operations.
23+
*
24+
* @return the client options containing configuration parameters
25+
*/
26+
@NotNull ClientOptions getClientOptions();
27+
28+
/**
29+
* Retrieves the connection instance for handling connection state and operations.
30+
* Used to check connection status, obtain error information, and manage
31+
* message transmission across the Ably connection.
32+
*
33+
* @return the connection instance
34+
*/
35+
@NotNull Connection getConnection();
36+
37+
/**
38+
* Retrieves the current time in milliseconds from the Ably server.
39+
* Spec: RTO16
40+
*/
41+
@Blocking
42+
long getTime() throws AblyException;
43+
44+
/**
45+
* Retrieves the channel instance for the specified channel name.
46+
* If the channel does not exist, an AblyException is thrown.
47+
*
48+
* @param channelName the name of the channel to retrieve
49+
* @return the ChannelBase instance for the specified channel
50+
* @throws AblyException if the channel is not found or cannot be retrieved
51+
*/
52+
@NotNull ChannelBase getChannel(@NotNull String channelName) throws AblyException;
53+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.ably.lib.object.adapter;
2+
3+
import io.ably.lib.realtime.AblyRealtime;
4+
import io.ably.lib.realtime.Channel;
5+
import io.ably.lib.realtime.ChannelBase;
6+
import io.ably.lib.realtime.Connection;
7+
import io.ably.lib.types.AblyException;
8+
import io.ably.lib.types.ClientOptions;
9+
import io.ably.lib.types.ErrorInfo;
10+
import io.ably.lib.types.ReadOnlyMap;
11+
import io.ably.lib.util.Log;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
/**
15+
* Default {@link AblyClientAdapter} implementation backed by an {@link AblyRealtime} client.
16+
* Holding the {@code AblyRealtime} reference gives the path-based LiveObjects implementation
17+
* access to the full client configuration and runtime state it may need.
18+
*/
19+
public class Adapter implements AblyClientAdapter {
20+
private final AblyRealtime ably;
21+
private static final String TAG = AblyClientAdapter.class.getName();
22+
23+
public Adapter(@NotNull AblyRealtime ably) {
24+
this.ably = ably;
25+
}
26+
27+
@Override
28+
public @NotNull ClientOptions getClientOptions() {
29+
return ably.options;
30+
}
31+
32+
@Override
33+
public @NotNull Connection getConnection() {
34+
return ably.connection;
35+
}
36+
37+
@Override
38+
public long getTime() throws AblyException {
39+
return ably.time();
40+
}
41+
42+
@Override
43+
public @NotNull ChannelBase getChannel(@NotNull String channelName) throws AblyException {
44+
// Look up via the read-only map view. Channels#get(String) would create the channel if
45+
// absent; ReadOnlyMap only exposes get(Object), which returns null atomically for an
46+
// unknown channel instead of silently recreating it.
47+
final ReadOnlyMap<String, Channel> channels = ably.channels;
48+
final ChannelBase channel = channels.get(channelName);
49+
if (channel == null) {
50+
Log.e(TAG, "getChannel(): channel not found: " + channelName);
51+
ErrorInfo errorInfo = new ErrorInfo("Channel not found: " + channelName, 404);
52+
throw AblyException.fromErrorInfo(errorInfo);
53+
}
54+
return channel;
55+
}
56+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Adapter layer bridging the path-based LiveObjects implementation to the core Ably client.
3+
* {@link io.ably.lib.object.adapter.AblyClientAdapter} is the abstraction the implementation
4+
* depends on; {@link io.ably.lib.object.adapter.Adapter} is the default implementation backed
5+
* by an {@link io.ably.lib.realtime.AblyRealtime} client.
6+
*
7+
* <p>This package is intentionally independent of the legacy {@code io.ably.lib.objects}
8+
* package so the path-based API can evolve on its own.
9+
*/
10+
package io.ably.lib.object.adapter;

lib/src/main/java/io/ably/lib/object/instance/Instance.java

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717
* {@code LiveCounter}) or primitive value.
1818
*
1919
* <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.
20+
* {@code Instance} is identity-addressed: it wraps an already-resolved value (typically
21+
* obtained from a {@code PathObject}), so its type is fixed and known for the lifetime
22+
* of the instance, and it is dereferenced in O(1) regardless of where that value sits
23+
* in the graph. Read operations validate the access API preconditions and fail with an
24+
* {@code AblyException} if those are not satisfied.
2425
*
2526
* <p>This base type exposes only the methods whose behaviour is independent of the
2627
* wrapped type; everything else - including {@code subscribe} (RTTS7b) - is
2728
* 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+
* view, or discriminate via {@link #getType()}. Because the wrapped type is fixed and
30+
* known, a mismatched {@code as*} cast fails fast with an {@link IllegalStateException}
31+
* rather than returning a best-effort view (contrast {@code PathObject}, whose casts
32+
* never throw).
2933
*
3034
* <p>Spec: RTINS1, RTTS7
3135
*
@@ -65,85 +69,101 @@ public interface Instance {
6569
@NotNull JsonElement compactJson();
6670

6771
/**
68-
* Returns this instance wrapped as a {@link LiveMapInstance}.
72+
* Returns this instance viewed as a {@link LiveMapInstance}.
6973
*
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}.
74+
* <p>Because an {@code Instance} wraps an already-resolved value of a known, fixed
75+
* type, this fails fast: it throws {@link IllegalStateException} if the wrapped value
76+
* is not a {@code LiveMap}, rather than returning a best-effort view. Use
77+
* {@link #getType()} to discriminate the type before casting.
7378
*
74-
* <p>Spec: RTTS9a
79+
* <p>Spec: RTTS9a / RTTS9d
7580
*
7681
* @return a {@link LiveMapInstance} view of this instance
82+
* @throws IllegalStateException if the wrapped value is not a {@code LiveMap}
7783
*/
7884
@NotNull LiveMapInstance asLiveMap();
7985

8086
/**
81-
* Returns this instance wrapped as a {@link LiveCounterInstance}.
82-
* Best-effort cast; does not validate the underlying type.
87+
* Returns this instance viewed as a {@link LiveCounterInstance}.
88+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
89+
* {@code LiveCounter}.
8390
*
84-
* <p>Spec: RTTS9b
91+
* <p>Spec: RTTS9b / RTTS9d
8592
*
8693
* @return a {@link LiveCounterInstance} view of this instance
94+
* @throws IllegalStateException if the wrapped value is not a {@code LiveCounter}
8795
*/
8896
@NotNull LiveCounterInstance asLiveCounter();
8997

9098
/**
91-
* Returns this instance wrapped as a {@link NumberInstance}.
92-
* Best-effort cast; does not validate the underlying type.
99+
* Returns this instance viewed as a {@link NumberInstance}.
100+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
101+
* {@code Number}.
93102
*
94-
* <p>Spec: RTTS9c
103+
* <p>Spec: RTTS9c / RTTS9d
95104
*
96105
* @return a {@link NumberInstance} view of this instance
106+
* @throws IllegalStateException if the wrapped value is not a {@code Number}
97107
*/
98108
@NotNull NumberInstance asNumber();
99109

100110
/**
101-
* Returns this instance wrapped as a {@link StringInstance}.
102-
* Best-effort cast; does not validate the underlying type.
111+
* Returns this instance viewed as a {@link StringInstance}.
112+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
113+
* {@code String}.
103114
*
104-
* <p>Spec: RTTS9c
115+
* <p>Spec: RTTS9c / RTTS9d
105116
*
106117
* @return a {@link StringInstance} view of this instance
118+
* @throws IllegalStateException if the wrapped value is not a {@code String}
107119
*/
108120
@NotNull StringInstance asString();
109121

110122
/**
111-
* Returns this instance wrapped as a {@link BooleanInstance}.
112-
* Best-effort cast; does not validate the underlying type.
123+
* Returns this instance viewed as a {@link BooleanInstance}.
124+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
125+
* {@code Boolean}.
113126
*
114-
* <p>Spec: RTTS9c
127+
* <p>Spec: RTTS9c / RTTS9d
115128
*
116129
* @return a {@link BooleanInstance} view of this instance
130+
* @throws IllegalStateException if the wrapped value is not a {@code Boolean}
117131
*/
118132
@NotNull BooleanInstance asBoolean();
119133

120134
/**
121-
* Returns this instance wrapped as a {@link BinaryInstance}.
122-
* Best-effort cast; does not validate the underlying type.
135+
* Returns this instance viewed as a {@link BinaryInstance}.
136+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
137+
* binary value.
123138
*
124-
* <p>Spec: RTTS9c
139+
* <p>Spec: RTTS9c / RTTS9d
125140
*
126141
* @return a {@link BinaryInstance} view of this instance
142+
* @throws IllegalStateException if the wrapped value is not a binary value
127143
*/
128144
@NotNull BinaryInstance asBinary();
129145

130146
/**
131-
* Returns this instance wrapped as a {@link JsonObjectInstance}.
132-
* Best-effort cast; does not validate the underlying type.
147+
* Returns this instance viewed as a {@link JsonObjectInstance}.
148+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
149+
* JSON object.
133150
*
134-
* <p>Spec: RTTS9c
151+
* <p>Spec: RTTS9c / RTTS9d
135152
*
136153
* @return a {@link JsonObjectInstance} view of this instance
154+
* @throws IllegalStateException if the wrapped value is not a JSON object
137155
*/
138156
@NotNull JsonObjectInstance asJsonObject();
139157

140158
/**
141-
* Returns this instance wrapped as a {@link JsonArrayInstance}.
142-
* Best-effort cast; does not validate the underlying type.
159+
* Returns this instance viewed as a {@link JsonArrayInstance}.
160+
* Fails fast: throws {@link IllegalStateException} if the wrapped value is not a
161+
* JSON array.
143162
*
144-
* <p>Spec: RTTS9c
163+
* <p>Spec: RTTS9c / RTTS9d
145164
*
146165
* @return a {@link JsonArrayInstance} view of this instance
166+
* @throws IllegalStateException if the wrapped value is not a JSON array
147167
*/
148168
@NotNull JsonArrayInstance asJsonArray();
149169
}

lib/src/main/java/io/ably/lib/object/instance/types/BinaryInstance.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ably.lib.object.instance.types;
22

3+
import com.google.gson.JsonPrimitive;
34
import io.ably.lib.object.instance.Instance;
45
import org.jetbrains.annotations.NotNull;
56

@@ -22,4 +23,15 @@ public interface BinaryInstance extends Instance {
2223
* @return the wrapped bytes
2324
*/
2425
byte @NotNull [] value();
26+
27+
/**
28+
* Returns the compacted JSON snapshot of the wrapped value, narrowed to a
29+
* {@link JsonPrimitive}: binary compacts to a base64-encoded JSON string.
30+
*
31+
* <p>Spec: RTTS7a
32+
*
33+
* @return the compacted JSON primitive
34+
*/
35+
@Override
36+
@NotNull JsonPrimitive compactJson();
2537
}

lib/src/main/java/io/ably/lib/object/instance/types/BooleanInstance.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ably.lib.object.instance.types;
22

3+
import com.google.gson.JsonPrimitive;
34
import io.ably.lib.object.instance.Instance;
45
import org.jetbrains.annotations.NotNull;
56

@@ -22,4 +23,16 @@ public interface BooleanInstance extends Instance {
2223
*/
2324
@NotNull
2425
Boolean value();
26+
27+
/**
28+
* Returns the compacted JSON snapshot of the wrapped value, narrowed to a
29+
* {@link JsonPrimitive}: a {@code BooleanInstance} always compacts to a single
30+
* JSON primitive.
31+
*
32+
* <p>Spec: RTTS7a
33+
*
34+
* @return the compacted JSON primitive
35+
*/
36+
@Override
37+
@NotNull JsonPrimitive compactJson();
2538
}

lib/src/main/java/io/ably/lib/object/instance/types/JsonArrayInstance.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,15 @@ public interface JsonArrayInstance extends Instance {
2323
*/
2424
@NotNull
2525
JsonArray value();
26+
27+
/**
28+
* Returns the compacted JSON snapshot of the wrapped value, narrowed to a
29+
* {@link JsonArray}: a {@code JsonArrayInstance} always compacts to a JSON array.
30+
*
31+
* <p>Spec: RTTS7a
32+
*
33+
* @return the compacted JSON array
34+
*/
35+
@Override
36+
@NotNull JsonArray compactJson();
2637
}

lib/src/main/java/io/ably/lib/object/instance/types/JsonObjectInstance.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,15 @@ public interface JsonObjectInstance extends Instance {
2323
*/
2424
@NotNull
2525
JsonObject value();
26+
27+
/**
28+
* Returns the compacted JSON snapshot of the wrapped value, narrowed to a
29+
* {@link JsonObject}: a {@code JsonObjectInstance} always compacts to a JSON object.
30+
*
31+
* <p>Spec: RTTS7a
32+
*
33+
* @return the compacted JSON object
34+
*/
35+
@Override
36+
@NotNull JsonObject compactJson();
2637
}

lib/src/main/java/io/ably/lib/object/instance/types/LiveCounterInstance.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ably.lib.object.instance.types;
22

3+
import com.google.gson.JsonPrimitive;
34
import io.ably.lib.object.instance.Instance;
45
import io.ably.lib.object.instance.InstanceListener;
56
import io.ably.lib.object.Subscription;
@@ -37,6 +38,18 @@ public interface LiveCounterInstance extends Instance {
3738
@NotNull
3839
Double value();
3940

41+
/**
42+
* Returns the compacted JSON snapshot of the wrapped value, narrowed to a
43+
* {@link JsonPrimitive}: a {@code LiveCounterInstance} always compacts to a numeric
44+
* JSON primitive.
45+
*
46+
* <p>Spec: RTTS7a
47+
*
48+
* @return the compacted JSON primitive
49+
*/
50+
@Override
51+
@NotNull JsonPrimitive compactJson();
52+
4053
/**
4154
* Increments the wrapped {@code LiveCounter} by {@code 1}. Equivalent to
4255
* calling {@link #increment(Number)} with {@code 1}.

0 commit comments

Comments
 (0)