Skip to content

Commit f3e0686

Browse files
committed
fixup: pr feedback
Signed-off-by: Todd Baert <[email protected]>
1 parent f518c31 commit f3e0686

File tree

6 files changed

+218
-209
lines changed

6 files changed

+218
-209
lines changed

benchmark.txt

+122-122
Large diffs are not rendered by default.

src/main/java/dev/openfeature/sdk/EvaluationContext.java

+41
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package dev.openfeature.sdk;
22

3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Map.Entry;
6+
import java.util.function.Function;
7+
38
/**
49
* The EvaluationContext is a container for arbitrary contextual data
510
* that can be used as a basis for dynamic evaluation.
@@ -19,4 +24,40 @@ public interface EvaluationContext extends Structure {
1924
* @return resulting merged context
2025
*/
2126
EvaluationContext merge(EvaluationContext overridingContext);
27+
28+
/**
29+
* Recursively merges the base Value map with the overriding map.
30+
*
31+
* @param <T> Structure type
32+
* @param newStructure function to create the right structure(s) for Values
33+
* @param base base map to merge
34+
* @param overriding overriding map to merge
35+
* @return resulting merged map
36+
*/
37+
static <T extends Structure> Map<String, Value> mergeMaps(Function<Map<String, Value>, Structure> newStructure,
38+
Map<String, Value> base,
39+
Map<String, Value> overriding) {
40+
41+
if (base == null || base.isEmpty()) {
42+
return overriding;
43+
}
44+
if (overriding == null || overriding.isEmpty()) {
45+
return base;
46+
}
47+
48+
final Map<String, Value> merged = new HashMap<>(base);
49+
for (Entry<String, Value> overridingEntry : overriding.entrySet()) {
50+
String key = overridingEntry.getKey();
51+
if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) {
52+
Structure mergedValue = merged.get(key).asStructure();
53+
Structure overridingValue = overridingEntry.getValue().asStructure();
54+
Map<String, Value> newMap = mergeMaps(newStructure, mergedValue.asUnmodifiableMap(),
55+
overridingValue.asUnmodifiableMap());
56+
merged.put(key, new Value(newStructure.apply(newMap)));
57+
} else {
58+
merged.put(key, overridingEntry.getValue());
59+
}
60+
}
61+
return merged;
62+
}
2263
}

src/main/java/dev/openfeature/sdk/ImmutableContext.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
/**
1212
* The EvaluationContext is a container for arbitrary contextual data
1313
* that can be used as a basis for dynamic evaluation.
14-
* The ImmutableContext is an EvaluationContext implementation which is threadsafe, and whose attributes can
14+
* The ImmutableContext is an EvaluationContext implementation which is
15+
* threadsafe, and whose attributes can
1516
* not be modified after instantiation.
1617
*/
1718
@ToString
@@ -22,7 +23,8 @@ public final class ImmutableContext implements EvaluationContext {
2223
private final ImmutableStructure structure;
2324

2425
/**
25-
* Create an immutable context with an empty targeting_key and attributes provided.
26+
* Create an immutable context with an empty targeting_key and attributes
27+
* provided.
2628
*/
2729
public ImmutableContext() {
2830
this(new HashMap<>());
@@ -70,7 +72,8 @@ public String getTargetingKey() {
7072
}
7173

7274
/**
73-
* Merges this EvaluationContext object with the passed EvaluationContext, overriding in case of conflict.
75+
* Merges this EvaluationContext object with the passed EvaluationContext,
76+
* overriding in case of conflict.
7477
*
7578
* @param overridingContext overriding context
7679
* @return new, resulting merged context
@@ -85,16 +88,16 @@ public EvaluationContext merge(EvaluationContext overridingContext) {
8588
}
8689

8790
return new ImmutableContext(
88-
this.merge(ImmutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap()));
91+
EvaluationContext.mergeMaps(ImmutableStructure::new, this.asUnmodifiableMap(),
92+
overridingContext.asUnmodifiableMap()));
8993
}
9094

9195
@SuppressWarnings("all")
9296
private static class DelegateExclusions {
9397
@ExcludeFromGeneratedCoverageReport
94-
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
98+
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
9599
Map<String, Value> base,
96100
Map<String, Value> overriding) {
97-
98101
return null;
99102
}
100103
}

src/main/java/dev/openfeature/sdk/MutableContext.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public MutableContext(String targetingKey) {
3333
}
3434

3535
public MutableContext(Map<String, Value> attributes) {
36-
this("", new HashMap<>(attributes));
36+
this(null, new HashMap<>(attributes));
3737
}
3838

3939
/**
@@ -121,7 +121,7 @@ public EvaluationContext merge(EvaluationContext overridingContext) {
121121
return overridingContext;
122122
}
123123

124-
Map<String, Value> merged = this.merge(
124+
Map<String, Value> merged = EvaluationContext.mergeMaps(
125125
MutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap());
126126
return new MutableContext(merged);
127127
}

src/main/java/dev/openfeature/sdk/OpenFeatureClient.java

+44-41
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package dev.openfeature.sdk;
22

3-
import dev.openfeature.sdk.exceptions.*;
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.Collections;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.List;
9+
import java.util.function.Consumer;
10+
11+
import dev.openfeature.sdk.exceptions.ExceptionUtils;
12+
import dev.openfeature.sdk.exceptions.FatalError;
13+
import dev.openfeature.sdk.exceptions.GeneralError;
14+
import dev.openfeature.sdk.exceptions.OpenFeatureError;
15+
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
416
import dev.openfeature.sdk.internal.AutoCloseableLock;
517
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
618
import dev.openfeature.sdk.internal.ObjectUtils;
719
import lombok.Getter;
820
import lombok.extern.slf4j.Slf4j;
921

10-
import java.util.*;
11-
import java.util.function.Consumer;
12-
1322
/**
1423
* OpenFeature Client implementation.
1524
* You should not instantiate this or reference this class.
@@ -19,8 +28,8 @@
1928
* @deprecated // TODO: eventually we will make this non-public. See issue #872
2029
*/
2130
@Slf4j
22-
@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable",
23-
"unchecked", "rawtypes"})
31+
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable",
32+
"unchecked", "rawtypes" })
2433
@Deprecated() // TODO: eventually we will make this non-public. See issue #872
2534
public class OpenFeatureClient implements Client {
2635

@@ -39,18 +48,18 @@ public class OpenFeatureClient implements Client {
3948
* Deprecated public constructor. Use OpenFeature.API.getClient() instead.
4049
*
4150
* @param openFeatureAPI Backing global singleton
42-
* @param domain An identifier which logically binds clients with providers (used by observability tools).
51+
* @param domain An identifier which logically binds clients with
52+
* providers (used by observability tools).
4353
* @param version Version of the client (used by observability tools).
4454
* @deprecated Do not use this constructor. It's for internal use only.
45-
* Clients created using it will not run event handlers.
46-
* Use the OpenFeatureAPI's getClient factory method instead.
55+
* Clients created using it will not run event handlers.
56+
* Use the OpenFeatureAPI's getClient factory method instead.
4757
*/
4858
@Deprecated() // TODO: eventually we will make this non-public. See issue #872
4959
public OpenFeatureClient(
5060
OpenFeatureAPI openFeatureAPI,
5161
String domain,
52-
String version
53-
) {
62+
String version) {
5463
this.openfeatureApi = openFeatureAPI;
5564
this.domain = domain;
5665
this.version = version;
@@ -106,7 +115,7 @@ public EvaluationContext getEvaluationContext() {
106115
}
107116

108117
private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
109-
EvaluationContext ctx, FlagEvaluationOptions options) {
118+
EvaluationContext ctx, FlagEvaluationOptions options) {
110119
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
111120
() -> FlagEvaluationOptions.builder().build());
112121
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
@@ -182,29 +191,23 @@ private static <T> void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvalu
182191
* @return merged evaluation context
183192
*/
184193
private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) {
185-
// avoid any unnecessary context instantiations and stream usage here; this is call with every evaluation.
186194
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext();
187195
final EvaluationContext clientContext = this.getEvaluationContext();
188196
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext();
189-
final List<EvaluationContext> contextsToMerge = new ArrayList<>();
190-
if (apiContext != null) {
191-
contextsToMerge.add(apiContext);
192-
}
193-
if (transactionContext != null) {
194-
contextsToMerge.add(transactionContext);
195-
}
196-
if (clientContext != null) {
197-
contextsToMerge.add(clientContext);
198-
}
199-
if (invocationContext != null) {
200-
contextsToMerge.add(invocationContext);
201-
}
197+
return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext);
198+
}
202199

203-
EvaluationContext merged = new ImmutableContext();
204-
for (EvaluationContext evaluationContext : contextsToMerge) {
205-
merged = merged.merge(evaluationContext);
200+
private EvaluationContext mergeContextMaps(EvaluationContext... contexts) {
201+
// avoid any unnecessary context instantiations and stream usage here; this is
202+
// called with every evaluation.
203+
Map merged = new HashMap<>();
204+
for (EvaluationContext evaluationContext : contexts) {
205+
if (evaluationContext != null && !evaluationContext.isEmpty()) {
206+
merged = EvaluationContext.mergeMaps(ImmutableStructure::new, merged,
207+
evaluationContext.asUnmodifiableMap());
208+
}
206209
}
207-
return merged;
210+
return new ImmutableContext(merged);
208211
}
209212

210213
private <T> ProviderEvaluation<?> createProviderEvaluation(
@@ -241,7 +244,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte
241244

242245
@Override
243246
public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
244-
FlagEvaluationOptions options) {
247+
FlagEvaluationOptions options) {
245248
return getBooleanDetails(key, defaultValue, ctx, options).getValue();
246249
}
247250

@@ -257,7 +260,7 @@ public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defa
257260

258261
@Override
259262
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
260-
FlagEvaluationOptions options) {
263+
FlagEvaluationOptions options) {
261264
return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
262265
}
263266

@@ -273,7 +276,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext
273276

274277
@Override
275278
public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
276-
FlagEvaluationOptions options) {
279+
FlagEvaluationOptions options) {
277280
return getStringDetails(key, defaultValue, ctx, options).getValue();
278281
}
279282

@@ -289,7 +292,7 @@ public FlagEvaluationDetails<String> getStringDetails(String key, String default
289292

290293
@Override
291294
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx,
292-
FlagEvaluationOptions options) {
295+
FlagEvaluationOptions options) {
293296
return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
294297
}
295298

@@ -305,7 +308,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte
305308

306309
@Override
307310
public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
308-
FlagEvaluationOptions options) {
311+
FlagEvaluationOptions options) {
309312
return getIntegerDetails(key, defaultValue, ctx, options).getValue();
310313
}
311314

@@ -321,7 +324,7 @@ public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defa
321324

322325
@Override
323326
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
324-
FlagEvaluationOptions options) {
327+
FlagEvaluationOptions options) {
325328
return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
326329
}
327330

@@ -337,7 +340,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext
337340

338341
@Override
339342
public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
340-
FlagEvaluationOptions options) {
343+
FlagEvaluationOptions options) {
341344
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
342345
}
343346

@@ -353,7 +356,7 @@ public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double default
353356

354357
@Override
355358
public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx,
356-
FlagEvaluationOptions options) {
359+
FlagEvaluationOptions options) {
357360
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options);
358361
}
359362

@@ -369,7 +372,7 @@ public Value getObjectValue(String key, Value defaultValue, EvaluationContext ct
369372

370373
@Override
371374
public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx,
372-
FlagEvaluationOptions options) {
375+
FlagEvaluationOptions options) {
373376
return getObjectDetails(key, defaultValue, ctx, options).getValue();
374377
}
375378

@@ -380,13 +383,13 @@ public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultVa
380383

381384
@Override
382385
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue,
383-
EvaluationContext ctx) {
386+
EvaluationContext ctx) {
384387
return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
385388
}
386389

387390
@Override
388391
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue, EvaluationContext ctx,
389-
FlagEvaluationOptions options) {
392+
FlagEvaluationOptions options) {
390393
return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options);
391394
}
392395

src/main/java/dev/openfeature/sdk/Structure.java

-38
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import java.util.HashMap;
66
import java.util.Map;
77
import java.util.Set;
8-
import java.util.Map.Entry;
9-
import java.util.function.Function;
108
import java.util.stream.Collectors;
119

1210
import static dev.openfeature.sdk.Value.objectToValue;
@@ -115,42 +113,6 @@ default Object convertValue(Value value) {
115113
throw new ValueNotConvertableError();
116114
}
117115

118-
/**
119-
* Recursively merges the base map with the overriding map.
120-
*
121-
* @param <T> Structure type
122-
* @param newStructure function to create the right structure
123-
* @param base base map to merge
124-
* @param overriding overriding map to merge
125-
* @return resulting merged map
126-
*/
127-
default <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
128-
Map<String, Value> base,
129-
Map<String, Value> overriding) {
130-
131-
if (base.isEmpty()) {
132-
return overriding;
133-
}
134-
if (overriding.isEmpty()) {
135-
return base;
136-
}
137-
138-
final Map<String, Value> merged = new HashMap<>(base);
139-
for (Entry<String, Value> overridingEntry : overriding.entrySet()) {
140-
String key = overridingEntry.getKey();
141-
if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) {
142-
Structure mergedValue = merged.get(key).asStructure();
143-
Structure overridingValue = overridingEntry.getValue().asStructure();
144-
Map<String, Value> newMap = this.merge(newStructure, mergedValue.asUnmodifiableMap(),
145-
overridingValue.asUnmodifiableMap());
146-
merged.put(key, new Value(newStructure.apply(newMap)));
147-
} else {
148-
merged.put(key, overridingEntry.getValue());
149-
}
150-
}
151-
return merged;
152-
}
153-
154116
/**
155117
* Transform an object map to a {@link Structure} type.
156118
*

0 commit comments

Comments
 (0)