Skip to content

Commit 2533dc1

Browse files
committed
Merge pull request #49 from retronym/ticket/43
Support blocking with: `scalaFuture.toJava.toCompletableFuture`
2 parents 416fe31 + 1d60cd0 commit 2533dc1

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

src/main/scala/scala/concurrent/java8/FutureConvertersImpl.scala

+16-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package scala.concurrent.java8
66
// Located in this package to access private[concurrent] members
77

88
import scala.concurrent.{ Future, Promise, ExecutionContext, ExecutionContextExecutorService, ExecutionContextExecutor, impl }
9-
import java.util.concurrent.{ CompletionStage, Executor, ExecutorService, CompletableFuture }
9+
import java.util.concurrent._
1010
import scala.util.{ Try, Success, Failure }
1111
import java.util.function.{ BiConsumer, Function JF, Consumer, BiFunction }
1212

@@ -66,8 +66,21 @@ object FuturesConvertersImpl {
6666
cf
6767
}
6868

69-
override def toCompletableFuture(): CompletableFuture[T] =
70-
throw new UnsupportedOperationException("this CompletionStage represents a read-only Scala Future")
69+
/**
70+
* @inheritdoc
71+
*
72+
* WARNING: completing the result of this method will not complete the underlying
73+
* Scala Future or Promise (ie, the one that that was passed to `toJava`.)
74+
*/
75+
override def toCompletableFuture(): CompletableFuture[T] = this
76+
77+
override def obtrudeValue(value: T): Unit = throw new UnsupportedOperationException("obtrudeValue may not be used on the result of toJava(scalaFuture)")
78+
79+
override def obtrudeException(ex: Throwable): Unit = throw new UnsupportedOperationException("obtrudeException may not be used on the result of toJava(scalaFuture)")
80+
81+
override def get(): T = scala.concurrent.blocking(super.get())
82+
83+
override def get(timeout: Long, unit: TimeUnit): T = scala.concurrent.blocking(super.get(timeout, unit))
7184

7285
override def toString: String = super[CompletableFuture].toString
7386
}

src/test/java/scala/compat/java8/FutureConvertersTest.java

+84-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
import scala.concurrent.Future;
88
import scala.concurrent.Promise;
99

10-
import java.util.concurrent.CompletableFuture;
11-
import java.util.concurrent.CompletionStage;
12-
import java.util.concurrent.CountDownLatch;
13-
import java.util.concurrent.ExecutionException;
10+
import java.util.concurrent.*;
1411

12+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
1513
import static java.util.concurrent.TimeUnit.SECONDS;
1614
import static org.junit.Assert.*;
1715
import static scala.compat.java8.FutureConverters.*;
@@ -318,4 +316,86 @@ public void testToJavaExceptionally() throws InterruptedException,
318316
latch.countDown();
319317
assertEquals("Hello", second.toCompletableFuture().get());
320318
}
319+
320+
@Test
321+
public void testToJavaThenComposeWithToJavaThenAccept() throws InterruptedException,
322+
ExecutionException, TimeoutException {
323+
// Test case from https://github.com/scala/scala-java8-compat/issues/29
324+
final Promise<String> p1 = promise();
325+
final CompletableFuture<String> future = new CompletableFuture<>();
326+
327+
CompletableFuture.supplyAsync(() -> "Hello").
328+
thenCompose(x -> toJava(p1.future())).handle((x, t) -> future.complete(x));
329+
p1.success("Hello");
330+
assertEquals("Hello", future.get(1000, MILLISECONDS));
331+
}
332+
333+
@Test
334+
public void testToJavaToCompletableFuture() throws ExecutionException, InterruptedException {
335+
final Promise<String> p = promise();
336+
final CompletionStage<String> cs = toJava(p.future());
337+
CompletableFuture<String> cf = cs.toCompletableFuture();
338+
assertEquals("notyet", cf.getNow("notyet"));
339+
p.success("done");
340+
assertEquals("done", cf.get());
341+
}
342+
343+
@Test
344+
public void testToJavaToCompletableFutureDoesNotMutateUnderlyingPromise() throws ExecutionException, InterruptedException {
345+
final Promise<String> p = promise();
346+
Future<String> sf = p.future();
347+
final CompletionStage<String> cs = toJava(sf);
348+
CompletableFuture<String> cf = cs.toCompletableFuture();
349+
assertEquals("notyet", cf.getNow("notyet"));
350+
cf.complete("done");
351+
assertEquals("done", cf.get());
352+
assertFalse(sf.isCompleted());
353+
assertFalse(p.isCompleted());
354+
}
355+
356+
@Test
357+
public void testToJavaToCompletableFutureJavaCompleteCalledAfterScalaComplete() throws ExecutionException, InterruptedException {
358+
final Promise<String> p = promise();
359+
Future<String> sf = p.future();
360+
final CompletionStage<String> cs = toJava(sf);
361+
CompletableFuture<String> cf = cs.toCompletableFuture();
362+
assertEquals("notyet", cf.getNow("notyet"));
363+
p.success("scaladone");
364+
assertEquals("scaladone", cf.get());
365+
cf.complete("javadone");
366+
assertEquals("scaladone", cf.get());
367+
}
368+
369+
@Test
370+
public void testToJavaToCompletableFutureJavaCompleteCalledBeforeScalaComplete() throws ExecutionException, InterruptedException {
371+
final Promise<String> p = promise();
372+
Future<String> sf = p.future();
373+
final CompletionStage<String> cs = toJava(sf);
374+
CompletableFuture<String> cf = cs.toCompletableFuture();
375+
assertEquals("notyet", cf.getNow("notyet"));
376+
cf.complete("javadone");
377+
assertEquals("javadone", cf.get());
378+
p.success("scaladone");
379+
assertEquals("javadone", cf.get());
380+
}
381+
382+
@Test
383+
public void testToJavaToCompletableFutureJavaObtrudeCalledBeforeScalaComplete() throws ExecutionException, InterruptedException {
384+
final Promise<String> p = promise();
385+
Future<String> sf = p.future();
386+
final CompletionStage<String> cs = toJava(sf);
387+
CompletableFuture<String> cf = cs.toCompletableFuture();
388+
try {
389+
cf.obtrudeValue("");
390+
fail();
391+
} catch (UnsupportedOperationException iae) {
392+
// okay
393+
}
394+
try {
395+
cf.obtrudeException(new Exception());
396+
fail();
397+
} catch (UnsupportedOperationException iae) {
398+
// okay
399+
}
400+
}
321401
}

0 commit comments

Comments
 (0)