Skip to content

Commit 10580c3

Browse files
Add ContextMaps to let users create various ContextMaps
Motivation: If we plan to use `ContextMap` for more and more use-cases as a strictly typed variant of a `Map`, we need to open a way to create various versions of the `ContextMap`. Modifications: - Add `ContextMaps` utility class; - Add `SingletonContextMap` and `UnmodifiableContextMap`; - Move `NoopAsyncContextProvider.NoopContextMap` to `EmptyContextMap`; - Move `ConcurrentContextMap` and `CopyOnWriteContextMap` to context-api; - Copy `io.servicetalk.concurrent.internal.DefaultContextMap` to `io.servicetalk.context.api.DefaultContextMap`; - Deprecate `io.servicetalk.concurrent.internal.DefaultContextMap`; - Copy `io.servicetalk.concurrent.internal.ContextMapUtils` to `io.servicetalk.context.api.ContextMapUtils`; - Deprecate `io.servicetalk.concurrent.internal.ContextMapUtils`; Result: New public API to create various `ContextMap`s.
1 parent 7c85161 commit 10580c3

File tree

17 files changed

+824
-146
lines changed

17 files changed

+824
-146
lines changed

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AsyncContextMapThreadLocal.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717

1818
import io.servicetalk.context.api.ContextMap;
1919
import io.servicetalk.context.api.ContextMapHolder;
20+
import io.servicetalk.context.api.ContextMaps;
2021

2122
import static java.lang.ThreadLocal.withInitial;
2223

2324
final class AsyncContextMapThreadLocal {
2425
static final ThreadLocal<ContextMap> CONTEXT_THREAD_LOCAL = withInitial(AsyncContextMapThreadLocal::newContextMap);
2526

2627
private static ContextMap newContextMap() {
27-
return new CopyOnWriteContextMap();
28+
return ContextMaps.newCopyOnWriteMap();
2829
}
2930

3031
ContextMap get() {

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/NoopAsyncContextProvider.java

+2-122
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,18 @@
1818
import io.servicetalk.concurrent.CompletableSource;
1919
import io.servicetalk.concurrent.PublisherSource.Subscriber;
2020
import io.servicetalk.concurrent.SingleSource;
21-
import io.servicetalk.concurrent.internal.ContextMapUtils;
2221
import io.servicetalk.context.api.ContextMap;
22+
import io.servicetalk.context.api.ContextMaps;
2323

24-
import java.util.Map;
2524
import java.util.concurrent.Callable;
2625
import java.util.concurrent.CompletableFuture;
2726
import java.util.concurrent.Executor;
2827
import java.util.concurrent.ExecutorService;
2928
import java.util.concurrent.ScheduledExecutorService;
3029
import java.util.function.BiConsumer;
3130
import java.util.function.BiFunction;
32-
import java.util.function.BiPredicate;
3331
import java.util.function.Consumer;
3432
import java.util.function.Function;
35-
import javax.annotation.Nullable;
3633

3734
final class NoopAsyncContextProvider implements AsyncContextProvider {
3835
static final AsyncContextProvider INSTANCE = new NoopAsyncContextProvider();
@@ -43,7 +40,7 @@ private NoopAsyncContextProvider() {
4340

4441
@Override
4542
public ContextMap context() {
46-
return NoopContextMap.INSTANCE;
43+
return ContextMaps.emptyMap();
4744
}
4845

4946
@Override
@@ -154,121 +151,4 @@ public <T, U, V> BiFunction<T, U, V> wrapBiFunction(final BiFunction<T, U, V> fu
154151
final ContextMap context) {
155152
return func;
156153
}
157-
158-
private static final class NoopContextMap implements ContextMap {
159-
static final ContextMap INSTANCE = new NoopContextMap();
160-
161-
private NoopContextMap() {
162-
// Singleton
163-
}
164-
165-
@Override
166-
public int size() {
167-
return 0;
168-
}
169-
170-
@Override
171-
public boolean isEmpty() {
172-
return true;
173-
}
174-
175-
@Override
176-
public boolean containsKey(final Key<?> key) {
177-
return false;
178-
}
179-
180-
@Override
181-
public boolean containsValue(@Nullable final Object value) {
182-
return false;
183-
}
184-
185-
@Override
186-
public <T> boolean contains(final Key<T> key, @Nullable final T value) {
187-
return false;
188-
}
189-
190-
@Nullable
191-
@Override
192-
public <T> T get(final Key<T> key) {
193-
return null;
194-
}
195-
196-
@Override
197-
public <T> T getOrDefault(final Key<T> key, final T defaultValue) {
198-
return defaultValue;
199-
}
200-
201-
@Nullable
202-
@Override
203-
public <T> T put(final Key<T> key, @Nullable final T value) {
204-
return null;
205-
}
206-
207-
@Nullable
208-
@Override
209-
public <T> T putIfAbsent(final Key<T> key, @Nullable final T value) {
210-
return null;
211-
}
212-
213-
@Nullable
214-
@Override
215-
public <T> T computeIfAbsent(final Key<T> key, final Function<Key<T>, T> computeFunction) {
216-
return null;
217-
}
218-
219-
@Override
220-
public void putAll(final ContextMap map) {
221-
}
222-
223-
@Override
224-
public void putAll(final Map<Key<?>, Object> map) {
225-
}
226-
227-
@Nullable
228-
@Override
229-
public <T> T remove(final Key<T> key) {
230-
return null;
231-
}
232-
233-
@Override
234-
public boolean removeAll(final Iterable<Key<?>> keys) {
235-
return false;
236-
}
237-
238-
@Override
239-
public void clear() {
240-
}
241-
242-
@Nullable
243-
@Override
244-
public Key<?> forEach(final BiPredicate<Key<?>, Object> consumer) {
245-
return null;
246-
}
247-
248-
@Override
249-
public ContextMap copy() {
250-
return this;
251-
}
252-
253-
@Override
254-
public int hashCode() {
255-
return System.identityHashCode(this);
256-
}
257-
258-
@Override
259-
public boolean equals(final Object o) {
260-
if (this == o) {
261-
return true;
262-
}
263-
if (!(o instanceof ContextMap)) {
264-
return false;
265-
}
266-
return ((ContextMap) o).isEmpty();
267-
}
268-
269-
@Override
270-
public String toString() {
271-
return ContextMapUtils.toString(this);
272-
}
273-
}
274154
}

servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/ContextMapUtils.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626

2727
/**
2828
* Shared utilities for {@link ContextMap}.
29+
*
30+
* @deprecated This class will be removed in the future releases.
2931
*/
30-
public final class ContextMapUtils {
32+
@Deprecated
33+
public final class ContextMapUtils { // FIXME: 0.43 - remove deprecated class
3134
private ContextMapUtils() {
3235
// no instances
3336
}

servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/DefaultContextMap.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.servicetalk.concurrent.internal;
1717

1818
import io.servicetalk.context.api.ContextMap;
19+
import io.servicetalk.context.api.ContextMaps;
1920

2021
import java.util.HashMap;
2122
import java.util.Map;
@@ -29,8 +30,11 @@
2930
* Default implementation of {@link ContextMap}.
3031
* <p>
3132
* Note: it's not thread-safe!
33+
*
34+
* @deprecated Use {@link ContextMaps#newDefaultMap()}.
3235
*/
33-
public final class DefaultContextMap implements ContextMap {
36+
@Deprecated
37+
public final class DefaultContextMap implements ContextMap { // FIXME: 0.43 - remove deprecated class
3438

3539
private final Map<Key<?>, Object> theMap;
3640

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.servicetalk.concurrent.api;
17-
18-
import io.servicetalk.concurrent.internal.ContextMapUtils;
19-
import io.servicetalk.context.api.ContextMap;
16+
package io.servicetalk.context.api;
2017

2118
import java.util.Map;
2219
import java.util.Map.Entry;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright © 2021 Apple Inc. and the ServiceTalk project authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.servicetalk.context.api;
17+
18+
import io.servicetalk.context.api.ContextMap.Key;
19+
20+
import javax.annotation.Nullable;
21+
22+
import static java.lang.Integer.toHexString;
23+
import static java.lang.System.identityHashCode;
24+
import static java.util.Objects.requireNonNull;
25+
26+
/**
27+
* Shared utilities for {@link ContextMap}.
28+
*/
29+
final class ContextMapUtils {
30+
private ContextMapUtils() {
31+
// no instances
32+
}
33+
34+
/**
35+
* {@link Object#toString()} implementation for {@link ContextMap}.
36+
*
37+
* @param map {@link ContextMap} to convert
38+
* @return {@link String} representation of the context map
39+
*/
40+
static String toString(final ContextMap map) {
41+
final String simpleName = map.getClass().getSimpleName();
42+
final int size = map.size();
43+
if (size == 0) {
44+
return simpleName + '@' + toHexString(identityHashCode(map)) + ":{}";
45+
}
46+
// 12 is 1 character for '@' + 8 hash code integer in hex form + 1 character for ':' + 2 characters for "{}".
47+
// Assume size of 90 for each key/value pair: 42 overhead characters for formatting + 16 characters for key
48+
// name + 16 characters for key type + 16 characters for value.
49+
StringBuilder sb = new StringBuilder(simpleName.length() + 12 + size * 90);
50+
sb.append(simpleName)
51+
.append('@')
52+
// There are many copies of these maps around, the content maybe equal but a differentiating factor is
53+
// the object reference. this may help folks understand why state is not visible across AsyncContext
54+
// boundaries.
55+
.append(toHexString(identityHashCode(map)))
56+
.append(":{");
57+
58+
map.forEach((key, value) -> {
59+
sb.append(key).append('=').append(value == map ? "(this Map)" : value).append(", ");
60+
return true;
61+
});
62+
sb.setLength(sb.length() - 2);
63+
return sb.append('}').toString();
64+
}
65+
66+
/**
67+
* {@link java.util.Objects#equals(Object, Object)} alternative for {@link ContextMap}.
68+
*
69+
* @param first the first {@link ContextMap}
70+
* @param second the second {@link ContextMap} to compare equality with the first one
71+
* @return {@code true} if both {@link ContextMap}(s) are equal (contains the same elements), {@code false}
72+
* otherwise.
73+
*/
74+
static boolean equals(final ContextMap first, final ContextMap second) {
75+
if (first.size() != second.size()) {
76+
return false;
77+
}
78+
@SuppressWarnings("unchecked")
79+
final Key<?> stopped = first.forEach((key, value) -> second.contains((Key<? super Object>) key, value));
80+
return stopped == null;
81+
}
82+
83+
/**
84+
* Make sure that the {@code value} type matches with the {@link Key#type()}.
85+
*
86+
* @param key the {@link Key} to verify
87+
* @param value the value to verify
88+
* @throws NullPointerException if {@code key == null}
89+
* @throws IllegalArgumentException if type of the {@code value} does not match with {@link Key#type()}
90+
*/
91+
static void ensureType(final Key<?> key, @Nullable final Object value) {
92+
requireNonNull(key);
93+
if (value != null && !key.type().isInstance(value)) {
94+
throw new IllegalArgumentException("Type of the value " + value + '(' + value.getClass() + ')' +
95+
" does mot match with " + key);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)