11package com .launchdarkly .sdk .server ;
22
3+ import com .google .common .collect .ImmutableMap ;
4+ import com .google .common .collect .Maps ;
35import com .google .gson .TypeAdapter ;
46import com .google .gson .annotations .JsonAdapter ;
57import com .google .gson .stream .JsonReader ;
1012import com .launchdarkly .sdk .server .interfaces .LDClientInterface ;
1113
1214import java .io .IOException ;
13- import java .util .Collections ;
1415import java .util .HashMap ;
1516import java .util .Map ;
1617import java .util .Objects ;
3637 */
3738@ JsonAdapter (FeatureFlagsState .JsonSerialization .class )
3839public final class FeatureFlagsState implements JsonSerializable {
39- private final Map <String , LDValue > flagValues ;
40- private final Map <String , FlagMetadata > flagMetadata ;
40+ private final ImmutableMap <String , FlagMetadata > flagMetadata ;
4141 private final boolean valid ;
4242
4343 static class FlagMetadata {
44+ final LDValue value ;
4445 final Integer variation ;
4546 final EvaluationReason reason ;
4647 final Integer version ;
4748 final Boolean trackEvents ;
4849 final Long debugEventsUntilDate ;
4950
50- FlagMetadata (Integer variation , EvaluationReason reason , Integer version , boolean trackEvents ,
51- Long debugEventsUntilDate ) {
51+ FlagMetadata (LDValue value , Integer variation , EvaluationReason reason , Integer version ,
52+ boolean trackEvents , Long debugEventsUntilDate ) {
53+ this .value = LDValue .normalize (value );
5254 this .variation = variation ;
5355 this .reason = reason ;
5456 this .version = version ;
@@ -60,7 +62,8 @@ static class FlagMetadata {
6062 public boolean equals (Object other ) {
6163 if (other instanceof FlagMetadata ) {
6264 FlagMetadata o = (FlagMetadata )other ;
63- return Objects .equals (variation , o .variation ) &&
65+ return value .equals (o .value ) &&
66+ Objects .equals (variation , o .variation ) &&
6467 Objects .equals (reason , o .reason ) &&
6568 Objects .equals (version , o .version ) &&
6669 Objects .equals (trackEvents , o .trackEvents ) &&
@@ -75,13 +78,27 @@ public int hashCode() {
7578 }
7679 }
7780
78- private FeatureFlagsState (Map <String , LDValue > flagValues ,
79- Map <String , FlagMetadata > flagMetadata , boolean valid ) {
80- this .flagValues = Collections .unmodifiableMap (flagValues );
81- this .flagMetadata = Collections .unmodifiableMap (flagMetadata );
81+ private FeatureFlagsState (ImmutableMap <String , FlagMetadata > flagMetadata , boolean valid ) {
82+ this .flagMetadata = flagMetadata ;
8283 this .valid = valid ;
8384 }
8485
86+ /**
87+ * Returns a {@link Builder} for creating instances.
88+ * <p>
89+ * Application code will not normally use this builder, since the SDK creates its own instances.
90+ * However, it may be useful in testing, to simulate values that might be returned by
91+ * {@link LDClient#allFlagsState(com.launchdarkly.sdk.LDUser, FlagsStateOption...)}.
92+ *
93+ * @param options the same {@link FlagsStateOption}s, if any, that would be passed to
94+ * {@link LDClient#allFlagsState(com.launchdarkly.sdk.LDUser, FlagsStateOption...)}
95+ * @return a builder object
96+ * @since 5.6.0
97+ */
98+ public static Builder builder (FlagsStateOption ... options ) {
99+ return new Builder (options );
100+ }
101+
85102 /**
86103 * Returns true if this object contains a valid snapshot of feature flag state, or false if the
87104 * state could not be computed (for instance, because the client was offline or there was no user).
@@ -98,7 +115,8 @@ public boolean isValid() {
98115 * {@code null} if there was no such flag
99116 */
100117 public LDValue getFlagValue (String key ) {
101- return flagValues .get (key );
118+ FlagMetadata data = flagMetadata .get (key );
119+ return data == null ? null : data .value ;
102120 }
103121
104122 /**
@@ -115,64 +133,100 @@ public EvaluationReason getFlagReason(String key) {
115133 * Returns a map of flag keys to flag values. If a flag would have evaluated to the default value,
116134 * its value will be null.
117135 * <p>
136+ * The returned map is unmodifiable.
137+ * <p>
118138 * Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client.
119139 * Instead, serialize the FeatureFlagsState object to JSON using {@code Gson.toJson()} or {@code Gson.toJsonTree()}.
120140 * @return an immutable map of flag keys to JSON values
121141 */
122142 public Map <String , LDValue > toValuesMap () {
123- return flagValues ;
143+ return Maps . transformValues ( flagMetadata , v -> v . value ) ;
124144 }
125145
126146 @ Override
127147 public boolean equals (Object other ) {
128148 if (other instanceof FeatureFlagsState ) {
129149 FeatureFlagsState o = (FeatureFlagsState )other ;
130- return flagValues .equals (o .flagValues ) &&
131- flagMetadata .equals (o .flagMetadata ) &&
150+ return flagMetadata .equals (o .flagMetadata ) &&
132151 valid == o .valid ;
133152 }
134153 return false ;
135154 }
136155
137156 @ Override
138157 public int hashCode () {
139- return Objects .hash (flagValues , flagMetadata , valid );
158+ return Objects .hash (flagMetadata , valid );
140159 }
141160
142- static class Builder {
143- private Map <String , LDValue > flagValues = new HashMap <>();
144- private Map <String , FlagMetadata > flagMetadata = new HashMap <>();
161+ /**
162+ * A builder for a {@link FeatureFlagsState} instance.
163+ * <p>
164+ * Application code will not normally use this builder, since the SDK creates its own instances.
165+ * However, it may be useful in testing, to simulate values that might be returned by
166+ * {@link LDClient#allFlagsState(com.launchdarkly.sdk.LDUser, FlagsStateOption...)}.
167+ *
168+ * @since 5.6.0
169+ */
170+ public static class Builder {
171+ private ImmutableMap .Builder <String , FlagMetadata > flagMetadata = ImmutableMap .builder ();
145172 private final boolean saveReasons ;
146173 private final boolean detailsOnlyForTrackedFlags ;
147174 private boolean valid = true ;
148175
149- Builder (FlagsStateOption ... options ) {
176+ private Builder (FlagsStateOption ... options ) {
150177 saveReasons = FlagsStateOption .hasOption (options , FlagsStateOption .WITH_REASONS );
151178 detailsOnlyForTrackedFlags = FlagsStateOption .hasOption (options , FlagsStateOption .DETAILS_ONLY_FOR_TRACKED_FLAGS );
152179 }
153180
154- Builder valid (boolean valid ) {
181+ /**
182+ * Sets the {@link FeatureFlagsState#isValid()} property. This is true by default.
183+ *
184+ * @param valid the new property value
185+ * @return the builder
186+ */
187+ public Builder valid (boolean valid ) {
155188 this .valid = valid ;
156189 return this ;
157190 }
158191
159- Builder addFlag (DataModel .FeatureFlag flag , Evaluator .EvalResult eval ) {
160- flagValues .put (flag .getKey (), eval .getValue ());
161- final boolean flagIsTracked = flag .isTrackEvents () ||
162- (flag .getDebugEventsUntilDate () != null && flag .getDebugEventsUntilDate () > System .currentTimeMillis ());
192+ public Builder add (
193+ String flagKey ,
194+ LDValue value ,
195+ Integer variationIndex ,
196+ EvaluationReason reason ,
197+ int flagVersion ,
198+ boolean trackEvents ,
199+ Long debugEventsUntilDate
200+ ) {
201+ final boolean flagIsTracked = trackEvents ||
202+ (debugEventsUntilDate != null && debugEventsUntilDate > System .currentTimeMillis ());
163203 final boolean wantDetails = !detailsOnlyForTrackedFlags || flagIsTracked ;
164204 FlagMetadata data = new FlagMetadata (
205+ value ,
206+ variationIndex ,
207+ (saveReasons && wantDetails ) ? reason : null ,
208+ wantDetails ? Integer .valueOf (flagVersion ) : null ,
209+ trackEvents ,
210+ debugEventsUntilDate
211+ );
212+ flagMetadata .put (flagKey , data );
213+ return this ;
214+ }
215+
216+ Builder addFlag (DataModel .FeatureFlag flag , Evaluator .EvalResult eval ) {
217+ return add (
218+ flag .getKey (),
219+ eval .getValue (),
165220 eval .isDefault () ? null : eval .getVariationIndex (),
166- ( saveReasons && wantDetails ) ? eval .getReason () : null ,
167- wantDetails ? flag .getVersion () : null ,
221+ eval .getReason (),
222+ flag .getVersion (),
168223 flag .isTrackEvents (),
169- flag .getDebugEventsUntilDate ());
170- flagMetadata .put (flag .getKey (), data );
171- return this ;
224+ flag .getDebugEventsUntilDate ()
225+ );
172226 }
173227
174228 FeatureFlagsState build () {
175- return new FeatureFlagsState (flagValues , flagMetadata , valid );
229+ return new FeatureFlagsState (flagMetadata . build () , valid );
176230 }
177231 }
178232
@@ -181,9 +235,9 @@ static class JsonSerialization extends TypeAdapter<FeatureFlagsState> {
181235 public void write (JsonWriter out , FeatureFlagsState state ) throws IOException {
182236 out .beginObject ();
183237
184- for (Map .Entry <String , LDValue > entry : state .flagValues .entrySet ()) {
238+ for (Map .Entry <String , FlagMetadata > entry : state .flagMetadata .entrySet ()) {
185239 out .name (entry .getKey ());
186- gsonInstance ().toJson (entry .getValue (), LDValue .class , out );
240+ gsonInstance ().toJson (entry .getValue (). value , LDValue .class , out );
187241 }
188242
189243 out .name ("$flagsState" );
@@ -229,7 +283,7 @@ public void write(JsonWriter out, FeatureFlagsState state) throws IOException {
229283 @ Override
230284 public FeatureFlagsState read (JsonReader in ) throws IOException {
231285 Map <String , LDValue > flagValues = new HashMap <>();
232- Map <String , FlagMetadata > flagMetadata = new HashMap <>();
286+ Map <String , FlagMetadata > flagMetadataWithoutValues = new HashMap <>();
233287 boolean valid = true ;
234288 in .beginObject ();
235289 while (in .hasNext ()) {
@@ -239,7 +293,7 @@ public FeatureFlagsState read(JsonReader in) throws IOException {
239293 while (in .hasNext ()) {
240294 String metaName = in .nextName ();
241295 FlagMetadata meta = gsonInstance ().fromJson (in , FlagMetadata .class );
242- flagMetadata .put (metaName , meta );
296+ flagMetadataWithoutValues .put (metaName , meta );
243297 }
244298 in .endObject ();
245299 } else if (name .equals ("$valid" )) {
@@ -250,7 +304,22 @@ public FeatureFlagsState read(JsonReader in) throws IOException {
250304 }
251305 }
252306 in .endObject ();
253- return new FeatureFlagsState (flagValues , flagMetadata , valid );
307+ ImmutableMap .Builder <String , FlagMetadata > allFlagMetadata = ImmutableMap .builder ();
308+ for (Map .Entry <String , LDValue > e : flagValues .entrySet ()) {
309+ FlagMetadata m0 = flagMetadataWithoutValues .get (e .getKey ());
310+ if (m0 != null ) {
311+ FlagMetadata m1 = new FlagMetadata (
312+ e .getValue (),
313+ m0 .variation ,
314+ m0 .reason ,
315+ m0 .version ,
316+ m0 .trackEvents != null && m0 .trackEvents .booleanValue (),
317+ m0 .debugEventsUntilDate
318+ );
319+ allFlagMetadata .put (e .getKey (), m1 );
320+ }
321+ }
322+ return new FeatureFlagsState (allFlagMetadata .build (), valid );
254323 }
255324 }
256325}
0 commit comments