refactor: Eliminate Optional from fields and parameters for async method invocation (#2830)

This commit is contained in:
guqing
2024-03-23 12:03:13 +02:00
committed by GitHub
parent 846dea2dca
commit 79dd5a7abc
6 changed files with 53 additions and 47 deletions
+2 -1
View File
@@ -57,7 +57,8 @@ public interface AsyncResult<T> {
```java ```java
public interface AsyncCallback<T> { public interface AsyncCallback<T> {
void onComplete(T value, Optional<Exception> ex); void onComplete(T value);
void onError(Exception ex);
} }
``` ```
@@ -9,7 +9,8 @@ package com.iluwatar.async.method.invocation {
+ main(args : String[]) {static} + main(args : String[]) {static}
} }
interface AsyncCallback<T> { interface AsyncCallback<T> {
+ onComplete(T, Optional<Exception>) {abstract} + onComplete(T) {abstract}
+ onError(Exception) {abstract}
} }
interface AsyncExecutor { interface AsyncExecutor {
+ endProcess(AsyncResult<T>) : T {abstract} + endProcess(AsyncResult<T>) : T {abstract}
@@ -32,7 +33,7 @@ package com.iluwatar.async.method.invocation {
~ COMPLETED : int {static} ~ COMPLETED : int {static}
~ FAILED : int {static} ~ FAILED : int {static}
~ RUNNING : int {static} ~ RUNNING : int {static}
~ callback : Optional<AsyncCallback<T>> ~ callback : AsyncCallback<T>
~ exception : Exception ~ exception : Exception
~ lock : Object ~ lock : Object
~ state : int ~ state : int
@@ -40,12 +41,15 @@ package com.iluwatar.async.method.invocation {
~ CompletableResult<T>(callback : AsyncCallback<T>) ~ CompletableResult<T>(callback : AsyncCallback<T>)
+ await() + await()
+ getValue() : T + getValue() : T
~ hasCallback() : boolean
+ isCompleted() : boolean + isCompleted() : boolean
~ setException(exception : Exception) ~ setException(exception : Exception)
~ setValue(value : T) ~ setValue(value : T)
} }
} }
ThreadAsyncExecutor ..|> AsyncCallback
ThreadAsyncExecutor ..|> AsyncResult
ThreadAsyncExecutor ..|> AsyncExecutor
CompletableResult ..+ ThreadAsyncExecutor CompletableResult ..+ ThreadAsyncExecutor
ThreadAsyncExecutor ..|> AsyncExecutor CompletableResult ..|> AsyncResult
CompletableResult ..|> AsyncResult @enduml
@enduml
@@ -118,12 +118,16 @@ public class App {
* @return new async callback * @return new async callback
*/ */
private static <T> AsyncCallback<T> callback(String name) { private static <T> AsyncCallback<T> callback(String name) {
return (value, ex) -> { return new AsyncCallback<>() {
if (ex.isPresent()) { @Override
log(name + " failed: " + ex.map(Exception::getMessage).orElse("")); public void onComplete(T value) {
} else {
log(name + " <" + value + ">"); log(name + " <" + value + ">");
} }
@Override
public void onError(Exception ex) {
log(name + " failed: " + ex.getMessage());
}
}; };
} }
@@ -24,8 +24,6 @@
*/ */
package com.iluwatar.async.method.invocation; package com.iluwatar.async.method.invocation;
import java.util.Optional;
/** /**
* AsyncCallback interface. * AsyncCallback interface.
* *
@@ -34,10 +32,16 @@ import java.util.Optional;
public interface AsyncCallback<T> { public interface AsyncCallback<T> {
/** /**
* Complete handler which is executed when async task is completed or fails execution. * Complete handler which is executed when async task is completed.
* *
* @param value the evaluated value from async task, undefined when execution fails * @param value the evaluated value from async task
* @param ex empty value if execution succeeds, some exception if executions fails
*/ */
void onComplete(T value, Optional<Exception> ex); void onComplete(T value);
/**
* Error handler which is executed when async task fails execution.
*
* @param ex exception which was thrown during async task execution(non-null)
*/
void onError(Exception ex);
} }
@@ -81,7 +81,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
static final int COMPLETED = 3; static final int COMPLETED = 3;
final Object lock; final Object lock;
final Optional<AsyncCallback<T>> callback; final AsyncCallback<T> callback;
volatile int state = RUNNING; volatile int state = RUNNING;
T value; T value;
@@ -89,7 +89,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
CompletableResult(AsyncCallback<T> callback) { CompletableResult(AsyncCallback<T> callback) {
this.lock = new Object(); this.lock = new Object();
this.callback = Optional.ofNullable(callback); this.callback = callback;
}
boolean hasCallback() {
return callback != null;
} }
/** /**
@@ -101,7 +105,9 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
void setValue(T value) { void setValue(T value) {
this.value = value; this.value = value;
this.state = COMPLETED; this.state = COMPLETED;
this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); if (hasCallback()) {
callback.onComplete(value);
}
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
} }
@@ -116,7 +122,9 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
void setException(Exception exception) { void setException(Exception exception) {
this.exception = exception; this.exception = exception;
this.state = FAILED; this.state = FAILED;
this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception))); if (hasCallback()) {
callback.onError(exception);
}
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
} }
@@ -25,15 +25,8 @@
package com.iluwatar.async.method.invocation; package com.iluwatar.async.method.invocation;
import static java.time.Duration.ofMillis; import static java.time.Duration.ofMillis;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -58,7 +51,7 @@ import org.mockito.MockitoAnnotations;
class ThreadAsyncExecutorTest { class ThreadAsyncExecutorTest {
@Captor @Captor
private ArgumentCaptor<Optional<Exception>> optionalCaptor; private ArgumentCaptor<Exception> exceptionCaptor;
@Mock @Mock
private Callable<Object> task; private Callable<Object> task;
@@ -118,11 +111,8 @@ class ThreadAsyncExecutorTest {
verify(task, times(1)).call(); verify(task, times(1)).call();
// ... same for the callback, we expect our object // ... same for the callback, we expect our object
verify(callback, times(1)).onComplete(eq(result), optionalCaptor.capture()); verify(callback, times(1)).onComplete(eq(result));
verify(callback, times(0)).onError(exceptionCaptor.capture());
final var optionalException = optionalCaptor.getValue();
assertNotNull(optionalException);
assertFalse(optionalException.isPresent());
// ... and the result should be exactly the same object // ... and the result should be exactly the same object
assertSame(result, asyncResult.getValue()); assertSame(result, asyncResult.getValue());
@@ -200,11 +190,8 @@ class ThreadAsyncExecutorTest {
// Our task should only execute once, but it can take a while ... // Our task should only execute once, but it can take a while ...
verify(task, timeout(3000).times(1)).call(); verify(task, timeout(3000).times(1)).call();
verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture()); verify(callback, timeout(3000).times(1)).onComplete(eq(result));
verify(callback, times(0)).onError(isA(Exception.class));
final var optionalException = optionalCaptor.getValue();
assertNotNull(optionalException);
assertFalse(optionalException.isPresent());
// Prevent timing issues, and wait until the result is available // Prevent timing issues, and wait until the result is available
asyncResult.await(); asyncResult.await();
@@ -295,14 +282,12 @@ class ThreadAsyncExecutorTest {
assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'."); assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'.");
asyncResult.await(); // Prevent timing issues, and wait until the result is available asyncResult.await(); // Prevent timing issues, and wait until the result is available
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
verify(callback, times(1)).onComplete(isNull(), optionalCaptor.capture()); verify(callback, times(0)).onComplete(any());
verify(callback, times(1)).onError(exceptionCaptor.capture());
final var optionalException = optionalCaptor.getValue(); final var exception = exceptionCaptor.getValue();
assertNotNull(optionalException);
assertTrue(optionalException.isPresent());
final var exception = optionalException.get();
assertNotNull(exception); assertNotNull(exception);
assertEquals(NullPointerException.class, exception.getClass()); assertEquals(NullPointerException.class, exception.getClass());
try { try {
@@ -347,4 +332,4 @@ class ThreadAsyncExecutorTest {
} }
} }