From 7d6d7d8ecb2213256b5f9aa85a51947d7c2b4f0d Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 26 Feb 2026 13:57:52 -0500 Subject: [PATCH 1/3] chore: commonizes several FDv2 related types --- .../com/launchdarkly/sdk/fdv2/ChangeSet.java | 104 ++++++++++++++++++ .../launchdarkly/sdk/fdv2/ChangeSetType.java | 21 ++++ .../com/launchdarkly/sdk/fdv2/Selector.java | 78 +++++++++++++ .../sdk/fdv2/SourceResultType.java | 18 +++ .../launchdarkly/sdk/fdv2/SourceSignal.java | 29 +++++ .../launchdarkly/sdk/fdv2/package-info.java | 7 ++ 6 files changed, 257 insertions(+) create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSet.java create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSetType.java create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceResultType.java create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceSignal.java create mode 100644 lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSet.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSet.java new file mode 100644 index 00000000..20c77bf0 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSet.java @@ -0,0 +1,104 @@ +package com.launchdarkly.sdk.fdv2; + +import java.util.Objects; + +/** + * Represents a set of changes to apply to a data store. + * + * @param the type of the data payload + */ +public final class ChangeSet { + private final ChangeSetType type; + private final Selector selector; + private final String environmentId; + private final T data; + private final boolean shouldPersist; + + /** + * Constructs a new ChangeSet. + *

+ * When implementing a custom data source, pass {@link Selector#EMPTY} for the {@code selector} + * parameter. Non-empty selectors are only meaningful for LaunchDarkly's own data sources. + * + * @param type the type of the changeset + * @param selector the selector for this change; null is normalized to {@link Selector#EMPTY} + * @param data the data payload + * @param environmentId the environment ID, or null if not available + * @param shouldPersist true if the data should be persisted to persistent stores + */ + public ChangeSet(ChangeSetType type, Selector selector, T data, String environmentId, boolean shouldPersist) { + this.type = type; + this.selector = selector != null ? selector : Selector.EMPTY; + this.data = data; + this.environmentId = environmentId; + this.shouldPersist = shouldPersist; + } + + /** + * Returns the type of the changeset. + * + * @return the changeset type + */ + public ChangeSetType getType() { + return type; + } + + /** + * Returns the selector for this change. Will not be null; may be {@link Selector#EMPTY}. + * + * @return the selector + */ + public Selector getSelector() { + return selector; + } + + /** + * Returns the environment ID associated with the change, or null if not available. + * + * @return the environment ID, or null + */ + public String getEnvironmentId() { + return environmentId; + } + + /** + * Returns the data payload for this changeset. + * + * @return the data + */ + public T getData() { + return data; + } + + /** + * Returns whether this data should be persisted to persistent stores. + * + * @return true if the data should be persisted, false otherwise + */ + public boolean shouldPersist() { + return shouldPersist; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ChangeSet) { + ChangeSet other = (ChangeSet) o; + return type == other.type + && shouldPersist == other.shouldPersist + && Objects.equals(selector, other.selector) + && Objects.equals(environmentId, other.environmentId) + && Objects.equals(data, other.data); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(type, selector, environmentId, data, shouldPersist); + } + + @Override + public String toString() { + return "ChangeSet(" + type + "," + selector + "," + environmentId + "," + data + "," + shouldPersist + ")"; + } +} diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSetType.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSetType.java new file mode 100644 index 00000000..68ecc7f4 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/ChangeSetType.java @@ -0,0 +1,21 @@ +package com.launchdarkly.sdk.fdv2; + +/** + * Indicates the type of a change set applied to a data store. + */ +public enum ChangeSetType { + /** + * Represents a full store update which replaces all data currently in the store. + */ + Full, + + /** + * Represents an incremental set of changes to be applied to the existing data in the store. + */ + Partial, + + /** + * Indicates that there are no changes; the changeset may still carry a selector to store. + */ + None +} diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java new file mode 100644 index 00000000..b48e80c7 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java @@ -0,0 +1,78 @@ +package com.launchdarkly.sdk.fdv2; + +/** + * Identifies a specific version of data in the LaunchDarkly backend, used to request incremental + * updates from a known point. + *

+ * A selector is either empty ({@link #EMPTY}) or contains a version number and state string that + * were provided by a LaunchDarkly data source. Empty selectors signal that the client has no + * existing data and requires a full payload. + *

+ * For SDK consumers implementing custom data sources: you should always use + * {@link #EMPTY} when constructing a {@link ChangeSet}. Non-empty selectors are set by + * LaunchDarkly's own data sources based on state received from the LaunchDarkly backend, and + * are not meaningful when constructed externally. + */ +public final class Selector { + private final boolean isEmpty; + private final int version; + private final String state; + + private Selector(int version, String state, boolean isEmpty) { + this.version = version; + this.state = state; + this.isEmpty = isEmpty; + } + + /** + * If true, then this selector is empty. An empty selector cannot be used as a basis for + * requesting incremental updates from a data source. + * + * @return whether the selector is empty + */ + public boolean isEmpty() { + return isEmpty; + } + + /** + * The version of the data associated with this selector. + * + * @return the version + */ + public int getVersion() { + return version; + } + + /** + * The state associated with the payload. + * + * @return the state identifier, or null if empty + */ + public String getState() { + return state; + } + + static Selector empty() { + return new Selector(0, null, true); + } + + /** + * Creates a new Selector with the given version and state. + *

+ * This method is intended for use by LaunchDarkly data sources only. + * Custom data source implementations should use {@link #EMPTY} instead. + * + * @param version the version number + * @param state the state identifier + * @return a new Selector instance + */ + public static Selector make(int version, String state) { + return new Selector(version, state, false); + } + + /** + * An empty selector instance. Custom data source implementations should always use this + * value when constructing a {@link ChangeSet}. + */ + public static final Selector EMPTY = empty(); +} diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceResultType.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceResultType.java new file mode 100644 index 00000000..7ea7ce58 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceResultType.java @@ -0,0 +1,18 @@ +package com.launchdarkly.sdk.fdv2; + +/** + * Indicates whether an FDv2 source result carries a change set or a status update. + */ +public enum SourceResultType { + /** + * The source has emitted a change set. This implies that the source is in a valid state. + */ + CHANGE_SET, + + /** + * The source is emitting a status update, indicating a transition from being valid to being + * in some kind of error or non-operational state. The source will emit a {@link #CHANGE_SET} + * if it becomes valid again. + */ + STATUS, +} diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceSignal.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceSignal.java new file mode 100644 index 00000000..24fe0bb1 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/SourceSignal.java @@ -0,0 +1,29 @@ +package com.launchdarkly.sdk.fdv2; + +/** + * Represents the state of an FDv2 data source when emitting a status result. + */ +public enum SourceSignal { + /** + * The data source has encountered an interruption and will attempt to reconnect. This is not + * intended to be used with an initializer; use {@link #TERMINAL_ERROR} instead. When used with + * an initializer it will still be treated as a terminal state. + */ + INTERRUPTED, + + /** + * The data source has been shut down and will not produce any further results. + */ + SHUTDOWN, + + /** + * The data source has encountered a terminal error and will not produce any further results. + */ + TERMINAL_ERROR, + + /** + * The data source has been instructed to disconnect (e.g. the server sent a goodbye message) + * and will not produce any further results. + */ + GOODBYE, +} diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java new file mode 100644 index 00000000..09719056 --- /dev/null +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java @@ -0,0 +1,7 @@ +/** + * Types for the FDv2 (Flag Delivery v2) data source protocol. + *

+ * This package contains the public types used by SDK data source implementations that support + * the FDv2 data sources. + */ +package com.launchdarkly.sdk.fdv2; From 430c038f6f20b4c2d9a01c834058d937559a946d Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 26 Feb 2026 15:36:43 -0500 Subject: [PATCH 2/3] tweaking package-info.java --- .../src/main/java/com/launchdarkly/sdk/fdv2/package-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java index 09719056..a14856aa 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/package-info.java @@ -1,5 +1,5 @@ /** - * Types for the FDv2 (Flag Delivery v2) data source protocol. + * Types for the FDv2 (Flag Delivery v2). *

* This package contains the public types used by SDK data source implementations that support * the FDv2 data sources. From 14c6425f935e78f1585402c8ec6bc13d56d94516 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 26 Feb 2026 15:56:08 -0500 Subject: [PATCH 3/3] adds equality methods to Selector --- .../com/launchdarkly/sdk/fdv2/Selector.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java index b48e80c7..ecde4f9b 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/fdv2/Selector.java @@ -1,5 +1,7 @@ package com.launchdarkly.sdk.fdv2; +import java.util.Objects; + /** * Identifies a specific version of data in the LaunchDarkly backend, used to request incremental * updates from a known point. @@ -75,4 +77,24 @@ public static Selector make(int version, String state) { * value when constructing a {@link ChangeSet}. */ public static final Selector EMPTY = empty(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Selector)) return false; + Selector other = (Selector) o; + return isEmpty == other.isEmpty + && version == other.version + && Objects.equals(state, other.state); + } + + @Override + public int hashCode() { + return Objects.hash(isEmpty, version, state); + } + + @Override + public String toString() { + return isEmpty ? "Selector(empty)" : "Selector(" + version + "," + state + ")"; + } }