From 3c9ffd18e73de355ffd8f9f9d950b7434e28b9ab Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Sat, 18 May 2024 20:15:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20use=20`toNonMinCf`=20instead=20of=20`toC?= =?UTF-8?q?f`=20in=20method=20`mostResultsOfSuccess`=20=F0=9F=94=84?= =?UTF-8?q?=F0=9F=92=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avoid `UnsupportedOperationException` for minimal stage input --- .../cffu/CompletableFutureUtils.java | 41 ++++++++++-- .../cffu/CompletableFutureUtilsTest.java | 65 +++++++++++-------- 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java index 79ca16ae..fea4462a 100644 --- a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java +++ b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java @@ -208,11 +208,12 @@ public static CompletableFuture> mostResultsOfSuccess( if (cfs.length == 0) return CompletableFuture.completedFuture(arrayList()); if (cfs.length == 1) { - final CompletableFuture f = copy(toCf(cfs[0])); + final CompletableFuture f = copy(toNonMinCf(cfs[0])); return orTimeout(f, timeout, unit).handle((unused, ex) -> arrayList(getSuccessNow(f, valueIfNotSuccess))); } - final CompletableFuture[] cfArray = f_toCfArray(cfs); + // MUST be Non-Minimal CF instances in order to read results, otherwise UnsupportedOperationException + final CompletableFuture[] cfArray = f_toNonMinCfArray(cfs); return orTimeout(CompletableFuture.allOf(cfArray), timeout, unit) .handle((unused, ex) -> MGetSuccessNow(valueIfNotSuccess, cfArray)); } @@ -279,17 +280,31 @@ private static CompletableFuture f_cast(CompletableFuture f) { * IGNORE the compile-time type check. */ private static CompletableFuture[] f_toCfArray(CompletionStage[] stages) { + return toCfArray0(stages, CompletableFutureUtils::toCf); + } + + /** + * Force converts {@link CompletionStage} array to {@link CompletableFuture} array, + * IGNORE the compile-time type check. + */ + private static CompletableFuture[] f_toNonMinCfArray(CompletionStage[] stages) { + return toCfArray0(stages, CompletableFutureUtils::toNonMinCf); + } + + private static CompletableFuture[] toCfArray0( + CompletionStage[] stages, + Function, CompletableFuture> converter) { requireNonNull(stages, "cfs is null"); @SuppressWarnings("unchecked") CompletableFuture[] ret = new CompletableFuture[stages.length]; for (int i = 0; i < stages.length; i++) { - ret[i] = toCf(requireNonNull(stages[i], "cf" + (i + 1) + " is null")); + ret[i] = converter.apply(requireNonNull(stages[i], "cf" + (i + 1) + " is null")); } return ret; } /** - * Converts CompletionStage to CompletableFuture, reuse cf instance as much as possible. + * Converts CompletionStage to CompletableFuture, reuse cf instance as many as possible. *

* CAUTION: because reused the CF instances, * so the returned CF instances MUST NOT be written(e.g. {@link CompletableFuture#complete(Object)}). @@ -302,6 +317,24 @@ private static CompletableFuture toCf(CompletionStage s) { else return (CompletableFuture) s.toCompletableFuture(); } + /** + * Converts CompletionStage to Non-MinimalStage CompletableFuture, reuse cf instance as many as possible. + *

+ * CAUTION: because reused the CF instances, + * so the returned CF instances MUST NOT be written(e.g. {@link CompletableFuture#complete(Object)}). + * Otherwise, the caller should defensive copy instead of writing it directly. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static CompletableFuture toNonMinCf(CompletionStage s) { + final CompletableFuture f; + if (s instanceof CompletableFuture) f = (CompletableFuture) s; + else if (s instanceof Cffu) f = ((Cffu) s).cffuUnwrap(); + else return (CompletableFuture) s.toCompletableFuture(); + + return "java.util.concurrent.CompletableFuture$MinimalStage".equals(s.getClass().getName()) + ? f.toCompletableFuture() : f; + } + //////////////////////////////////////////////////////////////////////////////// //# anyOf* methods //////////////////////////////////////////////////////////////////////////////// diff --git a/cffu-core/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java b/cffu-core/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java index 5f884be3..ec22a29c 100644 --- a/cffu-core/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java +++ b/cffu-core/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java @@ -33,14 +33,18 @@ class CompletableFutureUtilsTest { void test_allOf__success__trivial_case() throws Exception { assertEquals(Arrays.asList(n, n + 1, n + 2), allResultsOf( completedFuture(n), - completedFuture(n + 1), + completedStage(n + 1), completedFuture(n + 2) ).get()); - assertEquals(Arrays.asList(n, n + 1), allResultsOf(completedFuture(n), completedFuture(n + 1) + assertEquals(Arrays.asList(n, n + 1), allResultsOf( + completedFuture(n), + completedStage(n + 1) ).get()); assertEquals(Collections.singletonList(n), allResultsOf(completedFuture(n)).get()); + assertEquals(Collections.singletonList(n), allResultsOf(completedStage(n)).toCompletableFuture().get()); + assertThrows(UnsupportedOperationException.class, () -> allResultsOf(completedStage(n)).get()); assertEquals(Collections.emptyList(), allResultsOf().get()); @@ -48,28 +52,29 @@ void test_allOf__success__trivial_case() throws Exception { assertEquals(Arrays.asList(n, n + 1, n + 2), allResultsOfFastFail( completedFuture(n), - completedFuture(n + 1), + completedStage(n + 1), completedFuture(n + 2) ).get()); assertEquals(Arrays.asList(n, n + 1), allResultsOfFastFail( completedFuture(n), - completedFuture(n + 1) + completedStage(n + 1) ).get()); assertEquals(Collections.singletonList(n), allResultsOfFastFail(completedFuture(n)).get()); + assertEquals(Collections.singletonList(n), allResultsOfFastFail(completedStage(n)).toCompletableFuture().get()); + assertThrows(UnsupportedOperationException.class, () -> allResultsOfFastFail(completedStage(n)).get()); assertEquals(Collections.emptyList(), allResultsOfFastFail().get()); //////////////////////////////////////////////////////////////////////////////// - assertNull(allOfFastFail(completedFuture(n), completedFuture(n + 1), completedFuture(n + 2)).get()); - - assertNull(allOfFastFail(completedFuture(n), completedFuture(n + 1)).get()); - - assertNull(allOfFastFail(completedFuture(n)).get()); - - assertNull(allOfFastFail().get()); + Arrays.asList( + allOfFastFail(completedFuture(n), completedFuture(n + 1), completedFuture(n + 2)), + allOfFastFail(completedFuture(n), completedFuture(n + 1)), + allOfFastFail(completedFuture(n)), + allOfFastFail() + ).forEach(f -> assertNull(f.join())); } @Test @@ -319,6 +324,7 @@ void test_allOf__exceptionally() throws Exception { @Test void test_mostOf() throws Exception { final CompletableFuture completed = completedFuture(n); + final CompletableFuture completedStage = completedFuture(n); final CompletableFuture failed = failedFuture(rte); final CompletableFuture cancelled = createCancelledFuture(); final CompletableFuture incomplete = createIncompleteFuture(); @@ -327,12 +333,14 @@ void test_mostOf() throws Exception { assertEquals(Collections.singletonList(n), mostResultsOfSuccess( 10, TimeUnit.MILLISECONDS, null, completed).get()); + assertEquals(Collections.singletonList(n), mostResultsOfSuccess( + 10, TimeUnit.MILLISECONDS, anotherN, completedStage).get()); assertEquals(Arrays.asList(n, null, null, null), mostResultsOfSuccess( 10, TimeUnit.MILLISECONDS, null, completed, failed, cancelled, incomplete ).get()); assertEquals(Arrays.asList(n, anotherN, anotherN, anotherN), mostResultsOfSuccess( - 10, TimeUnit.MILLISECONDS, anotherN, completed, failed, cancelled, incomplete + 10, TimeUnit.MILLISECONDS, anotherN, completedStage, failed, cancelled, incomplete ).get()); assertEquals(Arrays.asList(anotherN, anotherN, anotherN), mostResultsOfSuccess( @@ -367,10 +375,12 @@ void test_mostOf_wontModifyInputCf() throws Exception { @Test void test_anyOf__success__trivial_case() throws Exception { - assertEquals(n, anyOf(completedFuture(n), completedFuture(n + 1), completedFuture(n + 2)).get()); - assertEquals(n, anyOf(completedFuture(n), completedFuture(n + 1)).get()); + assertEquals(n, anyOf(completedFuture(n), completedStage(n + 1), completedFuture(n + 2)).get()); + assertEquals(n, anyOf(completedFuture(n), completedStage(n + 1)).get()); assertEquals(n, anyOf(completedFuture(n)).get()); + assertEquals(n, anyOf(completedStage(n)).toCompletableFuture().get()); + assertThrows(UnsupportedOperationException.class, () -> anyOf(completedStage(n)).get()); assertFalse(anyOf().isDone()); // success with incomplete CF @@ -378,20 +388,21 @@ void test_anyOf__success__trivial_case() throws Exception { //////////////////////////////////////// - assertEquals(n, anyOfSuccess(completedFuture(n), completedFuture(n + 1), completedFuture(n + 2)).get()); - assertEquals(n, anyOfSuccess(completedFuture(n), completedFuture(n + 1)).get()); + assertEquals(n, anyOfSuccess(completedFuture(n), completedStage(n + 1), completedFuture(n + 2)).get()); + assertEquals(n, anyOfSuccess(completedFuture(n), completedStage(n + 1)).get()); assertEquals(n, anyOfSuccess(completedFuture(n)).get()); - try { - anyOfSuccess().get(); + assertEquals(n, anyOfSuccess(completedStage(n)).toCompletableFuture().get()); + assertThrows(UnsupportedOperationException.class, () -> anyOfSuccess(completedStage(n)).get()); - fail(); - } catch (ExecutionException expected) { - assertSame(NoCfsProvidedException.class, expected.getCause().getClass()); - } + assertInstanceOf(NoCfsProvidedException.class, + assertThrows(ExecutionException.class, + () -> anyOfSuccess().get() + ).getCause()); // success with incomplete CF assertEquals(n, anyOfSuccess(createIncompleteFuture(), createIncompleteFuture(), completedFuture(n)).get()); + assertEquals(n, anyOfSuccess(createIncompleteFuture(), createIncompleteFuture(), completedStage(n)).get()); } @Test @@ -569,9 +580,9 @@ void test_anyOf__concurrent() throws Exception { @Test void test_allTupleOf() throws Exception { final CompletableFuture cf_n = completedFuture(n); - final CompletableFuture cf_s = completedFuture(s); + final CompletionStage cf_s = completedStage(s); final CompletableFuture cf_d = completedFuture(d); - final CompletableFuture cf_an = completedFuture(anotherN); + final CompletionStage cf_an = completedStage(anotherN); final CompletableFuture cf_nn = completedFuture(n + n); assertEquals(Tuple2.of(n, s), allTupleOf(cf_n, cf_s).get()); @@ -593,9 +604,9 @@ void test_allTupleOf_exceptionally() throws Exception { final CompletableFuture fail = failedFuture(rte); final CompletableFuture cf_n = completedFuture(n); - final CompletableFuture cf_s = completedFuture(s); + final CompletionStage cf_s = completedStage(s); final CompletableFuture cf_d = completedFuture(d); - final CompletableFuture cf_an = completedFuture(anotherN); + final CompletionStage cf_an = completedStage(anotherN); try { allTupleOf(cf_n, fail).get(); @@ -665,7 +676,7 @@ void test_allTupleOf_NotFastFail() throws Exception { final CompletableFuture cf_s = completedFuture(s); final CompletableFuture cf_d = completedFuture(d); - final CompletableFuture cf_an = completedFuture(anotherN); + final CompletionStage cf_an = completedStage(anotherN); try { allTupleOf(incomplete, fail).get(100, TimeUnit.MILLISECONDS);