From 79dd5a7abcd48800fd44dd587a96973bcbc7142f Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Sat, 23 Mar 2024 18:03:13 +0800 Subject: [PATCH] refactor: Eliminate Optional from fields and parameters for async method invocation (#2830) --- async-method-invocation/README.md | 3 +- .../etc/async-method-invocation.urm.puml | 14 ++++--- .../iluwatar/async/method/invocation/App.java | 12 ++++-- .../method/invocation/AsyncCallback.java | 16 +++++--- .../invocation/ThreadAsyncExecutor.java | 16 ++++++-- .../invocation/ThreadAsyncExecutorTest.java | 39 ++++++------------- 6 files changed, 53 insertions(+), 47 deletions(-) diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md index a032a0e6f..030f120bf 100644 --- a/async-method-invocation/README.md +++ b/async-method-invocation/README.md @@ -57,7 +57,8 @@ public interface AsyncResult { ```java public interface AsyncCallback { - void onComplete(T value, Optional ex); + void onComplete(T value); + void onError(Exception ex); } ``` diff --git a/async-method-invocation/etc/async-method-invocation.urm.puml b/async-method-invocation/etc/async-method-invocation.urm.puml index 6f5d0b27f..d44665081 100644 --- a/async-method-invocation/etc/async-method-invocation.urm.puml +++ b/async-method-invocation/etc/async-method-invocation.urm.puml @@ -9,7 +9,8 @@ package com.iluwatar.async.method.invocation { + main(args : String[]) {static} } interface AsyncCallback { - + onComplete(T, Optional) {abstract} + + onComplete(T) {abstract} + + onError(Exception) {abstract} } interface AsyncExecutor { + endProcess(AsyncResult) : T {abstract} @@ -32,7 +33,7 @@ package com.iluwatar.async.method.invocation { ~ COMPLETED : int {static} ~ FAILED : int {static} ~ RUNNING : int {static} - ~ callback : Optional> + ~ callback : AsyncCallback ~ exception : Exception ~ lock : Object ~ state : int @@ -40,12 +41,15 @@ package com.iluwatar.async.method.invocation { ~ CompletableResult(callback : AsyncCallback) + await() + getValue() : T + ~ hasCallback() : boolean + isCompleted() : boolean ~ setException(exception : Exception) ~ setValue(value : T) } } +ThreadAsyncExecutor ..|> AsyncCallback +ThreadAsyncExecutor ..|> AsyncResult +ThreadAsyncExecutor ..|> AsyncExecutor CompletableResult ..+ ThreadAsyncExecutor -ThreadAsyncExecutor ..|> AsyncExecutor -CompletableResult ..|> AsyncResult -@enduml \ No newline at end of file +CompletableResult ..|> AsyncResult +@enduml diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index f1a03edf8..01fd8f1c6 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -118,12 +118,16 @@ public class App { * @return new async callback */ private static AsyncCallback callback(String name) { - return (value, ex) -> { - if (ex.isPresent()) { - log(name + " failed: " + ex.map(Exception::getMessage).orElse("")); - } else { + return new AsyncCallback<>() { + @Override + public void onComplete(T value) { log(name + " <" + value + ">"); } + + @Override + public void onError(Exception ex) { + log(name + " failed: " + ex.getMessage()); + } }; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java index d5cf8f821..2dfbaf9a1 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -24,8 +24,6 @@ */ package com.iluwatar.async.method.invocation; -import java.util.Optional; - /** * AsyncCallback interface. * @@ -34,10 +32,16 @@ import java.util.Optional; public interface AsyncCallback { /** - * 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 ex empty value if execution succeeds, some exception if executions fails + * @param value the evaluated value from async task */ - void onComplete(T value, Optional 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); } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index d518ea3fb..f4a50e0d6 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -81,7 +81,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor { static final int COMPLETED = 3; final Object lock; - final Optional> callback; + final AsyncCallback callback; volatile int state = RUNNING; T value; @@ -89,7 +89,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor { CompletableResult(AsyncCallback callback) { 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) { this.value = value; this.state = COMPLETED; - this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); + if (hasCallback()) { + callback.onComplete(value); + } synchronized (lock) { lock.notifyAll(); } @@ -116,7 +122,9 @@ public class ThreadAsyncExecutor implements AsyncExecutor { void setException(Exception exception) { this.exception = exception; this.state = FAILED; - this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception))); + if (hasCallback()) { + callback.onError(exception); + } synchronized (lock) { lock.notifyAll(); } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java index 7af9074a4..32d591b2b 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -25,15 +25,8 @@ package com.iluwatar.async.method.invocation; import static java.time.Duration.ofMillis; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -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.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -58,7 +51,7 @@ import org.mockito.MockitoAnnotations; class ThreadAsyncExecutorTest { @Captor - private ArgumentCaptor> optionalCaptor; + private ArgumentCaptor exceptionCaptor; @Mock private Callable task; @@ -118,11 +111,8 @@ class ThreadAsyncExecutorTest { verify(task, times(1)).call(); // ... same for the callback, we expect our object - verify(callback, times(1)).onComplete(eq(result), optionalCaptor.capture()); - - final var optionalException = optionalCaptor.getValue(); - assertNotNull(optionalException); - assertFalse(optionalException.isPresent()); + verify(callback, times(1)).onComplete(eq(result)); + verify(callback, times(0)).onError(exceptionCaptor.capture()); // ... and the result should be exactly the same object assertSame(result, asyncResult.getValue()); @@ -200,11 +190,8 @@ class ThreadAsyncExecutorTest { // Our task should only execute once, but it can take a while ... verify(task, timeout(3000).times(1)).call(); - verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture()); - - final var optionalException = optionalCaptor.getValue(); - assertNotNull(optionalException); - assertFalse(optionalException.isPresent()); + verify(callback, timeout(3000).times(1)).onComplete(eq(result)); + verify(callback, times(0)).onError(isA(Exception.class)); // Prevent timing issues, and wait until the result is available asyncResult.await(); @@ -295,14 +282,12 @@ class ThreadAsyncExecutorTest { 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 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(); - assertNotNull(optionalException); - assertTrue(optionalException.isPresent()); - - final var exception = optionalException.get(); + final var exception = exceptionCaptor.getValue(); assertNotNull(exception); + assertEquals(NullPointerException.class, exception.getClass()); try { @@ -347,4 +332,4 @@ class ThreadAsyncExecutorTest { } -} \ No newline at end of file +}