Skip to content

Add computeIfAbsent variants for non-nullable types to stores #4760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ repository on GitHub.
* `ConversionSupport` now converts `String` to `Locale` using the IETF BCP 47 language tag
format supported by the `Locale.forLanguageTag(String)` factory method instead of the
format used by the deprecated `Locale(String)` constructor.
* Deprecate `getOrComputeIfAbsent(...)` methods in `NamespacedHierarchicalStore` in favor
of the new `computeIfAbsent(...)` methods.

[[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
Expand All @@ -51,6 +53,8 @@ repository on GitHub.
* Provide cancellation support for the Suite and Vintage test engines
* Introduce `TestTask.getTestDescriptor()` method for use in
`HierarchicalTestExecutorService` implementations.
* Introduce `computeIfAbsent(...)` methods in `NamespacedHierarchicalStore` to simplify
working with non-nullable types.


[[release-notes-6.0.0-M2-junit-jupiter]]
Expand All @@ -72,12 +76,16 @@ repository on GitHub.
configuration parameter. `Locale` conversions are now always performed using the IETF
BCP 47 language tag format supported by the `Locale.forLanguageTag(String)` factory
method.
* Deprecate `getOrComputeIfAbsent(...)` methods in `ExtensionContext.Store` in favor of
the new `computeIfAbsent(...)` methods.

[[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements

* Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared
as `@Nullable`.
* Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to ease simplify
with non-nullable types.


[[release-notes-6.0.0-M2-junit-vintage]]
Expand Down
5 changes: 1 addition & 4 deletions documentation/src/test/java/example/FirstCustomEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,14 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId
return new EngineDescriptor(uniqueId, "First Custom Test Engine");
}

//end::user_guide[]
@SuppressWarnings("NullAway")
//tag::user_guide[]
@Override
public void execute(ExecutionRequest request) {
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionStarted(request.getRootTestDescriptor());

NamespacedHierarchicalStore<Namespace> store = request.getStore();
socket = store.getOrComputeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
try {
return new ServerSocket(0, 50, getLoopbackAddress());
}
Expand Down
5 changes: 1 addition & 4 deletions documentation/src/test/java/example/SecondCustomEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,14 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId
return new EngineDescriptor(uniqueId, "Second Custom Test Engine");
}

//end::user_guide[]
@SuppressWarnings("NullAway")
//tag::user_guide[]
@Override
public void execute(ExecutionRequest request) {
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionStarted(request.getRootTestDescriptor());

NamespacedHierarchicalStore<Namespace> store = request.getStore();
socket = store.getOrComputeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
try {
return new ServerSocket(0, 50, getLoopbackAddress());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,13 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon
return HttpServer.class.equals(parameterContext.getParameter().getType());
}

//end::user_guide[]
@SuppressWarnings({ "DataFlowIssue", "NullAway" })
//tag::user_guide[]
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

ExtensionContext rootContext = extensionContext.getRoot();
ExtensionContext.Store store = rootContext.getStore(Namespace.GLOBAL);
Class<HttpServerResource> key = HttpServerResource.class;
HttpServerResource resource = store.getOrComputeIfAbsent(key, __ -> {
HttpServerResource resource = store.computeIfAbsent(key, __ -> {
try {
HttpServerResource serverResource = new HttpServerResource(0);
serverResource.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void testPlanExecutionStarted(TestPlan testPlan) {
}
//tag::user_guide[]
NamespacedHierarchicalStore<Namespace> store = session.getStore(); // <1>
store.getOrComputeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2>
store.computeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2>
InetSocketAddress address = new InetSocketAddress(getLoopbackAddress(), 0);
HttpServer server;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,9 @@ interface CloseableResource {
*
* @param key the key; never {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param defaultValue the default value
* @param defaultValue the default value; never {@code null}
* @param <V> the value type
* @return the value; potentially {@code null} if {@code defaultValue}
* is {@code null}
* @return the value; never {@code null}
* @since 5.5
* @see #get(Object, Class)
*/
Expand All @@ -592,13 +591,13 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* the type of object we wish to retrieve from the store.
*
* <pre style="code">
* X x = store.getOrComputeIfAbsent(X.class, key -&gt; new X(), X.class);
* X x = store.computeIfAbsent(X.class, key -&gt; new X(), X.class);
* // Equivalent to:
* // X x = store.getOrComputeIfAbsent(X.class);
* // X x = store.computeIfAbsent(X.class);
* </pre>
*
* <p>See {@link #getOrComputeIfAbsent(Object, Function, Class)} for
* further details.
* <p>See {@link #computeIfAbsent(Object, Function, Class)} for further
* details.
*
* <p>If {@code type} implements {@link CloseableResource} or
* {@link AutoCloseable} (unless the
Expand All @@ -610,14 +609,56 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* @param <V> the key and value type
* @return the object; never {@code null}
* @since 5.1
* @see #getOrComputeIfAbsent(Object, Function)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see #computeIfAbsent(Class)
* @see #computeIfAbsent(Object, Function)
* @see #computeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*
* @deprecated Please use {@link #computeIfAbsent(Class)} instead.
*/
@API(status = STABLE, since = "5.1")
default <V> @Nullable V getOrComputeIfAbsent(Class<V> type) {
return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type);
@Deprecated
@API(status = DEPRECATED, since = "6.0")
default <V> V getOrComputeIfAbsent(Class<V> type) {
return computeIfAbsent(type);
}

/**
* Return the object of type {@code type} if it is present and not
* {@code null} in this {@code Store} (<em>keyed</em> by {@code type});
* otherwise, invoke the default constructor for {@code type} to
* generate the object, store it, and return it.
*
* <p>This method is a shortcut for the following, where {@code X} is
* the type of object we wish to retrieve from the store.
*
* <pre style="code">
* X x = store.computeIfAbsent(X.class, key -&gt; new X(), X.class);
* // Equivalent to:
* // X x = store.computeIfAbsent(X.class);
* </pre>
*
* <p>See {@link #computeIfAbsent(Object, Function, Class)} for further
* details.
*
* <p>If {@code type} implements {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param type the type of object to retrieve; never {@code null}
* @param <V> the key and value type
* @return the object; never {@code null}
* @since 6.0
* @see #computeIfAbsent(Object, Function)
* @see #computeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*/
@API(status = MAINTAINED, since = "6.0")
default <V> V computeIfAbsent(Class<V> type) {
return computeIfAbsent(type, ReflectionSupport::newInstance, type);
}

/**
Expand All @@ -631,7 +672,7 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* the {@code key} as input), stored, and returned.
*
* <p>For greater type safety, consider using
* {@link #getOrComputeIfAbsent(Object, Function, Class)} instead.
* {@link #computeIfAbsent(Object, Function, Class)} instead.
*
* <p>If the created value is an instance of {@link CloseableResource} or
* {@link AutoCloseable} (unless the
Expand All @@ -645,14 +686,56 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* @param <K> the key type
* @param <V> the value type
* @return the value; potentially {@code null}
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see #computeIfAbsent(Class)
* @see #computeIfAbsent(Object, Function)
* @see #computeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*
* @deprecated Please use {@link #computeIfAbsent(Object, Function)}
* instead.
*/
@Deprecated
@API(status = DEPRECATED, since = "6.0")
<K, V extends @Nullable Object> @Nullable Object getOrComputeIfAbsent(K key,
Function<? super K, ? extends V> defaultCreator);

/**
* Return the value of the specified required type that is stored under
* the supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store. If no value is found for the supplied {@code key}
* or the value is {@code null}, a new value will be computed by the
* {@code defaultCreator} (given the {@code key} as input), stored, and
* returned.
*
* <p>For greater type safety, consider using
* {@link #computeIfAbsent(Object, Function, Class)} instead.
*
* <p>If the created value is an instance of {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
* to create a new value; never {@code null} and must not return
* {@code null}
* @param <K> the key type
* @param <V> the value type
* @return the value; never {@code null}
* @since 6.0
* @see #computeIfAbsent(Class)
* @see #computeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*/
<K, V> Object computeIfAbsent(K key, Function<? super K, ? extends V> defaultCreator);

/**
* Get the value of the specified required type that is stored under the
* supplied {@code key}.
Expand All @@ -664,7 +747,7 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* a new value will be computed by the {@code defaultCreator} (given
* the {@code key} as input), stored, and returned.
*
* <p>If {@code requiredType} implements {@link CloseableResource} or
* <p>If the created value implements {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
Expand All @@ -677,14 +760,50 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* @param <K> the key type
* @param <V> the value type
* @return the value; potentially {@code null}
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function)
* @see #computeIfAbsent(Class)
* @see #computeIfAbsent(Object, Function)
* @see #computeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*/
@Deprecated
@API(status = DEPRECATED, since = "6.0")
<K, V extends @Nullable Object> @Nullable V getOrComputeIfAbsent(K key,
Function<? super K, ? extends V> defaultCreator, Class<V> requiredType);

/**
* Get the value of the specified required type that is stored under the
* supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store. If no value is found for the supplied {@code key}
* or the value is {@code null}, a new value will be computed by the
* {@code defaultCreator} (given the {@code key} as input), stored, and
* returned.
*
* <p>If the created value is an instance of {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
* to create a new value; never {@code null} and must not return
* {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param <K> the key type
* @param <V> the value type
* @return the value; never {@code null}
* @see #computeIfAbsent(Class)
* @see #computeIfAbsent(Object, Function)
* @see CloseableResource
* @see AutoCloseable
*/
<K, V> V computeIfAbsent(K key, Function<? super K, ? extends V> defaultCreator, Class<V> requiredType);

/**
* Store a {@code value} for later retrieval under the supplied {@code key}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -41,6 +42,7 @@
* @see LoggerFactory
* @see LogRecordListener
*/
@NullMarked
@Target({ ElementType.TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(TrackLogRecords.Extension.class)
Expand Down Expand Up @@ -71,7 +73,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
}

private LogRecordListener getListener(ExtensionContext context) {
return getStore(context).getOrComputeIfAbsent(LogRecordListener.class);
return getStore(context).computeIfAbsent(LogRecordListener.class);
}

private Store getStore(ExtensionContext context) {
Expand Down
Loading
Loading