1
1
package com .launchdarkly .sdk .server ;
2
2
3
+ import com .google .common .collect .ImmutableMap ;
4
+ import com .google .common .collect .Maps ;
3
5
import com .google .gson .TypeAdapter ;
4
6
import com .google .gson .annotations .JsonAdapter ;
5
7
import com .google .gson .stream .JsonReader ;
10
12
import com .launchdarkly .sdk .server .interfaces .LDClientInterface ;
11
13
12
14
import java .io .IOException ;
13
- import java .util .Collections ;
14
15
import java .util .HashMap ;
15
16
import java .util .Map ;
16
17
import java .util .Objects ;
36
37
*/
37
38
@ JsonAdapter (FeatureFlagsState .JsonSerialization .class )
38
39
public 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 ;
41
41
private final boolean valid ;
42
42
43
43
static class FlagMetadata {
44
+ final LDValue value ;
44
45
final Integer variation ;
45
46
final EvaluationReason reason ;
46
47
final Integer version ;
47
48
final Boolean trackEvents ;
48
49
final Long debugEventsUntilDate ;
49
50
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 );
52
54
this .variation = variation ;
53
55
this .reason = reason ;
54
56
this .version = version ;
@@ -60,7 +62,8 @@ static class FlagMetadata {
60
62
public boolean equals (Object other ) {
61
63
if (other instanceof FlagMetadata ) {
62
64
FlagMetadata o = (FlagMetadata )other ;
63
- return Objects .equals (variation , o .variation ) &&
65
+ return value .equals (o .value ) &&
66
+ Objects .equals (variation , o .variation ) &&
64
67
Objects .equals (reason , o .reason ) &&
65
68
Objects .equals (version , o .version ) &&
66
69
Objects .equals (trackEvents , o .trackEvents ) &&
@@ -75,13 +78,27 @@ public int hashCode() {
75
78
}
76
79
}
77
80
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 ;
82
83
this .valid = valid ;
83
84
}
84
85
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
+
85
102
/**
86
103
* Returns true if this object contains a valid snapshot of feature flag state, or false if the
87
104
* state could not be computed (for instance, because the client was offline or there was no user).
@@ -98,7 +115,8 @@ public boolean isValid() {
98
115
* {@code null} if there was no such flag
99
116
*/
100
117
public LDValue getFlagValue (String key ) {
101
- return flagValues .get (key );
118
+ FlagMetadata data = flagMetadata .get (key );
119
+ return data == null ? null : data .value ;
102
120
}
103
121
104
122
/**
@@ -115,64 +133,100 @@ public EvaluationReason getFlagReason(String key) {
115
133
* Returns a map of flag keys to flag values. If a flag would have evaluated to the default value,
116
134
* its value will be null.
117
135
* <p>
136
+ * The returned map is unmodifiable.
137
+ * <p>
118
138
* Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client.
119
139
* Instead, serialize the FeatureFlagsState object to JSON using {@code Gson.toJson()} or {@code Gson.toJsonTree()}.
120
140
* @return an immutable map of flag keys to JSON values
121
141
*/
122
142
public Map <String , LDValue > toValuesMap () {
123
- return flagValues ;
143
+ return Maps . transformValues ( flagMetadata , v -> v . value ) ;
124
144
}
125
145
126
146
@ Override
127
147
public boolean equals (Object other ) {
128
148
if (other instanceof FeatureFlagsState ) {
129
149
FeatureFlagsState o = (FeatureFlagsState )other ;
130
- return flagValues .equals (o .flagValues ) &&
131
- flagMetadata .equals (o .flagMetadata ) &&
150
+ return flagMetadata .equals (o .flagMetadata ) &&
132
151
valid == o .valid ;
133
152
}
134
153
return false ;
135
154
}
136
155
137
156
@ Override
138
157
public int hashCode () {
139
- return Objects .hash (flagValues , flagMetadata , valid );
158
+ return Objects .hash (flagMetadata , valid );
140
159
}
141
160
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 ();
145
172
private final boolean saveReasons ;
146
173
private final boolean detailsOnlyForTrackedFlags ;
147
174
private boolean valid = true ;
148
175
149
- Builder (FlagsStateOption ... options ) {
176
+ private Builder (FlagsStateOption ... options ) {
150
177
saveReasons = FlagsStateOption .hasOption (options , FlagsStateOption .WITH_REASONS );
151
178
detailsOnlyForTrackedFlags = FlagsStateOption .hasOption (options , FlagsStateOption .DETAILS_ONLY_FOR_TRACKED_FLAGS );
152
179
}
153
180
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 ) {
155
188
this .valid = valid ;
156
189
return this ;
157
190
}
158
191
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 ());
163
203
final boolean wantDetails = !detailsOnlyForTrackedFlags || flagIsTracked ;
164
204
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 (),
165
220
eval .isDefault () ? null : eval .getVariationIndex (),
166
- ( saveReasons && wantDetails ) ? eval .getReason () : null ,
167
- wantDetails ? flag .getVersion () : null ,
221
+ eval .getReason (),
222
+ flag .getVersion (),
168
223
flag .isTrackEvents (),
169
- flag .getDebugEventsUntilDate ());
170
- flagMetadata .put (flag .getKey (), data );
171
- return this ;
224
+ flag .getDebugEventsUntilDate ()
225
+ );
172
226
}
173
227
174
228
FeatureFlagsState build () {
175
- return new FeatureFlagsState (flagValues , flagMetadata , valid );
229
+ return new FeatureFlagsState (flagMetadata . build () , valid );
176
230
}
177
231
}
178
232
@@ -181,9 +235,9 @@ static class JsonSerialization extends TypeAdapter<FeatureFlagsState> {
181
235
public void write (JsonWriter out , FeatureFlagsState state ) throws IOException {
182
236
out .beginObject ();
183
237
184
- for (Map .Entry <String , LDValue > entry : state .flagValues .entrySet ()) {
238
+ for (Map .Entry <String , FlagMetadata > entry : state .flagMetadata .entrySet ()) {
185
239
out .name (entry .getKey ());
186
- gsonInstance ().toJson (entry .getValue (), LDValue .class , out );
240
+ gsonInstance ().toJson (entry .getValue (). value , LDValue .class , out );
187
241
}
188
242
189
243
out .name ("$flagsState" );
@@ -229,7 +283,7 @@ public void write(JsonWriter out, FeatureFlagsState state) throws IOException {
229
283
@ Override
230
284
public FeatureFlagsState read (JsonReader in ) throws IOException {
231
285
Map <String , LDValue > flagValues = new HashMap <>();
232
- Map <String , FlagMetadata > flagMetadata = new HashMap <>();
286
+ Map <String , FlagMetadata > flagMetadataWithoutValues = new HashMap <>();
233
287
boolean valid = true ;
234
288
in .beginObject ();
235
289
while (in .hasNext ()) {
@@ -239,7 +293,7 @@ public FeatureFlagsState read(JsonReader in) throws IOException {
239
293
while (in .hasNext ()) {
240
294
String metaName = in .nextName ();
241
295
FlagMetadata meta = gsonInstance ().fromJson (in , FlagMetadata .class );
242
- flagMetadata .put (metaName , meta );
296
+ flagMetadataWithoutValues .put (metaName , meta );
243
297
}
244
298
in .endObject ();
245
299
} else if (name .equals ("$valid" )) {
@@ -250,7 +304,22 @@ public FeatureFlagsState read(JsonReader in) throws IOException {
250
304
}
251
305
}
252
306
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 );
254
323
}
255
324
}
256
325
}
0 commit comments