Skip to content

Commit 0dc9c08

Browse files
feat(context): Improve null handling and Javadoc (#8129)
Improve Javadoc about usage and thread safety Improve null handling using defensive checks, and document parameters and results Improve tests to cover all context implementations
1 parent 7d4150e commit 0dc9c08

17 files changed

+305
-141
lines changed

components/context/src/main/java/datadog/context/Context.java

+61-18
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,44 @@
44
import static datadog.context.ContextProviders.manager;
55

66
import javax.annotation.Nullable;
7+
import javax.annotation.ParametersAreNonnullByDefault;
78

89
/**
910
* Immutable context scoped to an execution unit or carrier object.
1011
*
11-
* <p>Each element of the context is accessible by its {@link ContextKey}. Keys represents product
12-
* or functional areas and should be created sparingly. Elements in the context may themselves be
13-
* mutable.
12+
* <p>There are three ways to get a Context instance:
13+
*
14+
* <ul>
15+
* <li>The first one is to retrieve the one from the current execution unit using {@link
16+
* #current()}. A Context instance can be marked as current using {@link #attach()} within the
17+
* execution unit.
18+
* <li>The second one is to retrieve one from a carrier object using {@link #from(Object
19+
* carrier)}. A Context instance would need to be attached to the carrier first using {@link
20+
* #attachTo(Object carrier)} attached.
21+
* <li>Finally, the third option is to get the default root Context instance calling {@link
22+
* #root()}.
23+
* </ul>
24+
*
25+
* <p>When there is no context attached to the current execution unit, {@link #current()} will
26+
* return the root context. Similarly, {@link #from(Object carrier)} will return the root context
27+
* when there is no context attached to the carrier.
28+
*
29+
* <p>From a {@link Context} instance, each value is stored and retrieved by its {@link ContextKey},
30+
* using {@link #with(ContextKey key, Object value)} to store a value (creating a new immutable
31+
* {@link Context} instance), and {@link #get(ContextKey)} to retrieve it. {@link ContextKey}s
32+
* represent product of functional areas, and should be created sparingly.
33+
*
34+
* <p>{@link Context} instances are thread safe as they are immutable (including their {@link
35+
* ContextKey}) but the value they hold may themselves be mutable.
36+
*
37+
* @see ContextKey
1438
*/
39+
@ParametersAreNonnullByDefault
1540
public interface Context {
16-
1741
/**
1842
* Returns the root context.
1943
*
20-
* <p>This is the initial local context that all contexts extend.
44+
* @return the initial local context that all contexts extend.
2145
*/
2246
static Context root() {
2347
return manager().root();
@@ -26,7 +50,7 @@ static Context root() {
2650
/**
2751
* Returns the context attached to the current execution unit.
2852
*
29-
* @return Attached context; {@link #root()} if there is none
53+
* @return the attached context; {@link #root()} if there is none.
3054
*/
3155
static Context current() {
3256
return manager().current();
@@ -35,7 +59,7 @@ static Context current() {
3559
/**
3660
* Attaches this context to the current execution unit.
3761
*
38-
* @return Scope to be closed when the context is invalid.
62+
* @return a scope to be closed when the context is invalid.
3963
*/
4064
default ContextScope attach() {
4165
return manager().attach(this);
@@ -44,7 +68,7 @@ default ContextScope attach() {
4468
/**
4569
* Swaps this context with the one attached to current execution unit.
4670
*
47-
* @return Previously attached context; {@link #root()} if there was none
71+
* @return the previously attached context; {@link #root()} if there was none.
4872
*/
4973
default Context swap() {
5074
return manager().swap(this);
@@ -53,21 +77,27 @@ default Context swap() {
5377
/**
5478
* Returns the context attached to the given carrier object.
5579
*
56-
* @return Attached context; {@link #root()} if there is none
80+
* @param carrier the carrier object to get the context from.
81+
* @return the attached context; {@link #root()} if there is none.
5782
*/
5883
static Context from(Object carrier) {
5984
return binder().from(carrier);
6085
}
6186

62-
/** Attaches this context to the given carrier object. */
87+
/**
88+
* Attaches this context to the given carrier object.
89+
*
90+
* @param carrier the object to carry the context.
91+
*/
6392
default void attachTo(Object carrier) {
6493
binder().attachTo(carrier, this);
6594
}
6695

6796
/**
6897
* Detaches the context attached to the given carrier object, leaving it context-less.
6998
*
70-
* @return Previously attached context; {@link #root()} if there was none
99+
* @param carrier the carrier object to detach its context from.
100+
* @return the previously attached context; {@link #root()} if there was none.
71101
*/
72102
static Context detachFrom(Object carrier) {
73103
return binder().detachFrom(carrier);
@@ -76,24 +106,37 @@ static Context detachFrom(Object carrier) {
76106
/**
77107
* Gets the value stored in this context under the given key.
78108
*
79-
* @return Value stored under the key; {@code null} if there is no value.
109+
* @param <T> the type of the value.
110+
* @param key the key used to store the value.
111+
* @return the value stored under the key; {@code null} if there is none.
80112
*/
81113
@Nullable
82114
<T> T get(ContextKey<T> key);
83115

84116
/**
85-
* Creates a new context from the same elements, except the key is now mapped to the given value.
117+
* Creates a copy of this context with the given key-value set.
118+
*
119+
* <p>Existing value with the given key will be replaced, and mapping to a {@code null} value will
120+
* remove the key-value from the context copy.
86121
*
87-
* @return New context with the key-value mapping.
122+
* @param <T> the type of the value.
123+
* @param key the key to store the value.
124+
* @param value the value to store.
125+
* @return a new context with the key-value set.
88126
*/
89-
<T> Context with(ContextKey<T> key, T value);
127+
<T> Context with(ContextKey<T> key, @Nullable T value);
90128

91129
/**
92-
* Creates a new context from the same elements, except the implicit key is mapped to this value.
130+
* Creates a copy of this context with the implicit key is mapped to the value.
93131
*
94-
* @return New context with the implicitly keyed value.
132+
* @param value the value to store.
133+
* @return a new context with the implicitly keyed value set.
134+
* @see #with(ContextKey, Object)
95135
*/
96-
default Context with(ImplicitContextKeyed value) {
136+
default Context with(@Nullable ImplicitContextKeyed value) {
137+
if (value == null) {
138+
return root();
139+
}
97140
return value.storeInto(this);
98141
}
99142
}

components/context/src/main/java/datadog/context/ContextBinder.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
package datadog.context;
22

3+
import javax.annotation.ParametersAreNonnullByDefault;
4+
35
/** Binds context to carrier objects. */
6+
@ParametersAreNonnullByDefault
47
public interface ContextBinder {
5-
68
/**
79
* Returns the context attached to the given carrier object.
810
*
9-
* @return Attached context; {@link Context#root()} if there is none
11+
* @param carrier the carrier object to get the context from.
12+
* @return the attached context; {@link Context#root()} if there is none.
1013
*/
1114
Context from(Object carrier);
1215

13-
/** Attaches the given context to the given carrier object. */
16+
/**
17+
* Attaches the given context to the given carrier object.
18+
*
19+
* @param carrier the object to carry the context.
20+
* @param context the context to attach.
21+
*/
1422
void attachTo(Object carrier, Context context);
1523

1624
/**
1725
* Detaches the context attached to the given carrier object, leaving it context-less.
1826
*
19-
* @return Previously attached context; {@link Context#root()} if there was none
27+
* @param carrier the carrier object to detach its context from.
28+
* @return the previously attached context; {@link Context#root()} if there was none.
2029
*/
2130
Context detachFrom(Object carrier);
2231

23-
/** Requests use of a custom {@link ContextBinder}. */
32+
/**
33+
* Requests use of a custom {@link ContextBinder}.
34+
*
35+
* @param binder the binder to use (will replace any other binder in use).
36+
*/
2437
static void register(ContextBinder binder) {
2538
ContextProviders.customBinder = binder;
2639
}

components/context/src/main/java/datadog/context/ContextKey.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,35 @@
1010
*/
1111
public final class ContextKey<T> {
1212
private static final AtomicInteger NEXT_INDEX = new AtomicInteger(0);
13-
13+
/** The key name, for debugging purpose only . */
1414
private final String name;
15+
/** The key unique context, related to {@link IndexedContext} implementation. */
1516
final int index;
1617

1718
private ContextKey(String name) {
1819
this.name = name;
1920
this.index = NEXT_INDEX.getAndIncrement();
2021
}
2122

22-
/** Creates a new key with the given name. */
23+
/**
24+
* Creates a new key with the given name.
25+
*
26+
* @param name the key name, for debugging purpose only.
27+
* @return the newly created unique key.
28+
*/
2329
public static <T> ContextKey<T> named(String name) {
2430
return new ContextKey<>(name);
2531
}
2632

2733
@Override
2834
public int hashCode() {
29-
return index;
35+
return this.index;
3036
}
3137

3238
// we want identity equality, so no need to override equals()
3339

3440
@Override
3541
public String toString() {
36-
return name;
42+
return this.name;
3743
}
3844
}

components/context/src/main/java/datadog/context/ContextManager.java

+11-6
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,41 @@
22

33
/** Manages context across execution units. */
44
public interface ContextManager {
5-
65
/**
76
* Returns the root context.
87
*
9-
* <p>This is the initial local context that all contexts extend.
8+
* @return the initial local context that all contexts extend.
109
*/
1110
Context root();
1211

1312
/**
1413
* Returns the context attached to the current execution unit.
1514
*
16-
* @return Attached context; {@link #root()} if there is none
15+
* @return the attached context; {@link #root()} if there is none.
1716
*/
1817
Context current();
1918

2019
/**
2120
* Attaches the given context to the current execution unit.
2221
*
23-
* @return Scope to be closed when the context is invalid.
22+
* @param context the context to attach.
23+
* @return a scope to be closed when the context is invalid.
2424
*/
2525
ContextScope attach(Context context);
2626

2727
/**
2828
* Swaps the given context with the one attached to current execution unit.
2929
*
30-
* @return Previously attached context; {@link #root()} if there was none
30+
* @param context the context to swap.
31+
* @return the previously attached context; {@link #root()} if there was none.
3132
*/
3233
Context swap(Context context);
3334

34-
/** Requests use of a custom {@link ContextManager}. */
35+
/**
36+
* Requests use of a custom {@link ContextManager}.
37+
*
38+
* @param manager the manager to use (will replace any other manager in use).
39+
*/
3540
static void register(ContextManager manager) {
3641
ContextProviders.customManager = manager;
3742
}

components/context/src/main/java/datadog/context/ContextScope.java

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
/** Controls the validity of context attached to an execution unit. */
44
public interface ContextScope extends AutoCloseable {
5-
65
/** Returns the context controlled by this scope. */
76
Context context();
87

Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
package datadog.context;
22

3+
import static java.util.Objects.requireNonNull;
4+
5+
import javax.annotation.Nullable;
6+
import javax.annotation.ParametersAreNonnullByDefault;
7+
38
/** {@link Context} containing no values. */
9+
@ParametersAreNonnullByDefault
410
final class EmptyContext implements Context {
511
static final Context INSTANCE = new EmptyContext();
612

713
@Override
14+
@Nullable
815
public <T> T get(ContextKey<T> key) {
916
return null;
1017
}
1118

1219
@Override
13-
public <T> Context with(ContextKey<T> key, T value) {
20+
public <T> Context with(ContextKey<T> key, @Nullable T value) {
21+
requireNonNull(key, "Context key cannot be null");
22+
if (value == null) {
23+
return this;
24+
}
1425
return new SingletonContext(key.index, value);
1526
}
1627
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package datadog.context;
22

3+
import javax.annotation.ParametersAreNonnullByDefault;
4+
35
/** {@link Context} value that has its own implicit {@link ContextKey}. */
6+
@ParametersAreNonnullByDefault
47
public interface ImplicitContextKeyed {
5-
68
/**
79
* Creates a new context with this value under its chosen key.
810
*
9-
* @return New context with the implicitly keyed value.
11+
* @param context the context to copy the original values from.
12+
* @return the new context with the implicitly keyed value.
13+
* @see Context#with(ImplicitContextKeyed)
1014
*/
1115
Context storeInto(Context context);
1216
}

components/context/src/main/java/datadog/context/IndexedContext.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import static java.lang.Math.max;
44
import static java.util.Arrays.copyOfRange;
5+
import static java.util.Objects.requireNonNull;
56

67
import java.util.Arrays;
8+
import javax.annotation.Nullable;
9+
import javax.annotation.ParametersAreNonnullByDefault;
710

811
/** {@link Context} containing many values. */
12+
@ParametersAreNonnullByDefault
913
final class IndexedContext implements Context {
1014
private final Object[] store;
1115

@@ -14,18 +18,20 @@ final class IndexedContext implements Context {
1418
}
1519

1620
@Override
21+
@Nullable
1722
@SuppressWarnings("unchecked")
1823
public <T> T get(ContextKey<T> key) {
24+
requireNonNull(key, "Context key cannot be null");
1925
int index = key.index;
2026
return index < store.length ? (T) store[index] : null;
2127
}
2228

2329
@Override
24-
public <T> Context with(ContextKey<T> key, T value) {
30+
public <T> Context with(ContextKey<T> key, @Nullable T value) {
31+
requireNonNull(key, "Context key cannot be null");
2532
int index = key.index;
2633
Object[] newStore = copyOfRange(store, 0, max(store.length, index + 1));
2734
newStore[index] = value;
28-
2935
return new IndexedContext(newStore);
3036
}
3137

0 commit comments

Comments
 (0)