Skip to content

Commit c3d4b3a

Browse files
committed
feat(context): Improve null handling and Javadoc
Improve Javadoc about usage and thread safety Improve null handling and document parameters and results Explicit reference of ContextScope Improve tests to cover all context implementations
1 parent 37acd7b commit c3d4b3a

17 files changed

+297
-140
lines changed

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

Lines changed: 47 additions & 16 deletions
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 immutables (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,7 +77,7 @@ 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+
* @return the attached context; {@link #root()} if there is none.
5781
*/
5882
static Context from(Object carrier) {
5983
return binder().from(carrier);
@@ -67,7 +91,7 @@ default void attachTo(Object carrier) {
6791
/**
6892
* Detaches the context attached to the given carrier object, leaving it context-less.
6993
*
70-
* @return Previously attached context; {@link #root()} if there was none
94+
* @return the previously attached context; {@link #root()} if there was none.
7195
*/
7296
static Context detachFrom(Object carrier) {
7397
return binder().detachFrom(carrier);
@@ -76,24 +100,31 @@ static Context detachFrom(Object carrier) {
76100
/**
77101
* Gets the value stored in this context under the given key.
78102
*
79-
* @return Value stored under the key; {@code null} if there is no value.
103+
* @return the value stored under the key; {@code null} if there is none.
80104
*/
81105
@Nullable
82106
<T> T get(ContextKey<T> key);
83107

84108
/**
85-
* Creates a new context from the same elements, except the key is now mapped to the given value.
109+
* Creates a copy of this context with the given key-value set.
110+
*
111+
* <p>Existing value with the given key will be replaced, and mapping to a {@code null} value will
112+
* remove the key-value from the context copy.
86113
*
87-
* @return New context with the key-value mapping.
114+
* @return a new context with the key-value set.
88115
*/
89-
<T> Context with(ContextKey<T> key, T value);
116+
<T> Context with(ContextKey<T> key, @Nullable T value);
90117

91118
/**
92-
* Creates a new context from the same elements, except the implicit key is mapped to this value.
119+
* Creates a copy of this context with the implicit key is mapped to the value.
93120
*
94-
* @return New context with the implicitly keyed value.
121+
* @return a new context with the implicitly keyed value set.
122+
* @see #with(ContextKey, Object)
95123
*/
96124
default Context with(ImplicitContextKeyed value) {
125+
if (value == null) {
126+
return null;
127+
}
97128
return value.storeInto(this);
98129
}
99130
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
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+
* @return the attached context; {@link Context#root()} if there is none.
1012
*/
1113
Context from(Object carrier);
1214

@@ -16,11 +18,15 @@ public interface ContextBinder {
1618
/**
1719
* Detaches the context attached to the given carrier object, leaving it context-less.
1820
*
19-
* @return Previously attached context; {@link Context#root()} if there was none
21+
* @return the previously attached context; {@link Context#root()} if there was none.
2022
*/
2123
Context detachFrom(Object carrier);
2224

23-
/** Requests use of a custom {@link ContextBinder}. */
25+
/**
26+
* Requests use of a custom {@link ContextBinder}.
27+
*
28+
* @param binder the binder to use (will replace any other binder in use).
29+
*/
2430
static void register(ContextBinder binder) {
2531
ContextProviders.customBinder = binder;
2632
}

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

Lines changed: 10 additions & 4 deletions
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

Lines changed: 11 additions & 6 deletions
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

Lines changed: 0 additions & 1 deletion
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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package datadog.context;
22

3+
import javax.annotation.Nullable;
4+
import javax.annotation.ParametersAreNonnullByDefault;
5+
36
/** {@link Context} containing no values. */
7+
@ParametersAreNonnullByDefault
48
final class EmptyContext implements Context {
59
static final Context INSTANCE = new EmptyContext();
610

711
@Override
12+
@Nullable
813
public <T> T get(ContextKey<T> key) {
914
return null;
1015
}
1116

1217
@Override
13-
public <T> Context with(ContextKey<T> key, T value) {
18+
public <T> Context with(ContextKey<T> key, @Nullable T value) {
19+
if (key == null || value == null) {
20+
return this;
21+
}
1422
return new SingletonContext(key.index, value);
1523
}
1624
}
Lines changed: 6 additions & 2 deletions
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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import static java.util.Arrays.copyOfRange;
55

66
import java.util.Arrays;
7+
import javax.annotation.Nullable;
8+
import javax.annotation.ParametersAreNonnullByDefault;
79

810
/** {@link Context} containing many values. */
11+
@ParametersAreNonnullByDefault
912
final class IndexedContext implements Context {
1013
private final Object[] store;
1114

@@ -14,18 +17,24 @@ final class IndexedContext implements Context {
1417
}
1518

1619
@Override
20+
@Nullable
1721
@SuppressWarnings("unchecked")
1822
public <T> T get(ContextKey<T> key) {
23+
if (key == null) {
24+
return null;
25+
}
1926
int index = key.index;
2027
return index < store.length ? (T) store[index] : null;
2128
}
2229

2330
@Override
24-
public <T> Context with(ContextKey<T> key, T value) {
31+
public <T> Context with(ContextKey<T> key, @Nullable T value) {
32+
if (key == null) {
33+
return this;
34+
}
2535
int index = key.index;
2636
Object[] newStore = copyOfRange(store, 0, max(store.length, index + 1));
2737
newStore[index] = value;
28-
2938
return new IndexedContext(newStore);
3039
}
3140

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import static java.lang.Math.max;
44

55
import java.util.Objects;
6+
import javax.annotation.Nullable;
7+
import javax.annotation.ParametersAreNonnullByDefault;
68

79
/** {@link Context} containing a single value. */
10+
@ParametersAreNonnullByDefault
811
final class SingletonContext implements Context {
912
private final int index;
1013
private final Object value;
@@ -15,19 +18,25 @@ final class SingletonContext implements Context {
1518
}
1619

1720
@Override
21+
@Nullable
1822
@SuppressWarnings("unchecked")
1923
public <V> V get(ContextKey<V> key) {
20-
return index == key.index ? (V) value : null;
24+
return key != null && index == key.index ? (V) this.value : null;
2125
}
2226

2327
@Override
24-
public <V> Context with(ContextKey<V> secondKey, V secondValue) {
28+
public <V> Context with(ContextKey<V> secondKey, @Nullable V secondValue) {
29+
if (secondKey == null) {
30+
return this;
31+
}
2532
int secondIndex = secondKey.index;
26-
if (index == secondIndex) {
27-
return new SingletonContext(index, secondValue);
33+
if (this.index == secondIndex) {
34+
return secondValue == null
35+
? new EmptyContext()
36+
: new SingletonContext(this.index, secondValue);
2837
} else {
29-
Object[] store = new Object[max(index, secondIndex) + 1];
30-
store[index] = value;
38+
Object[] store = new Object[max(this.index, secondIndex) + 1];
39+
store[this.index] = this.value;
3140
store[secondIndex] = secondValue;
3241
return new IndexedContext(store);
3342
}

0 commit comments

Comments
 (0)