From 9eb940da8484181be3cb1a737eb746b0a9444628 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 28 Jul 2015 21:59:44 +1000 Subject: [PATCH] More efficient round trip conversions for Futures Makes `toJava.toScala` and `toScala.toJava` a wrap-and-unwrap operation, rather than a wrap-and-rewrap. Removes the cross build on 2.12, we should wait until 2.12.0-M3 in which DefaultPromise is once again extendable. See: https://github.com/retronym/scala-java8-compat/commit/4faeac5 https://github.com/scala/scala/pull/4690 --- .travis.yml | 2 +- build.sbt | 2 +- .../scala/compat/java8/FutureConverters.scala | 26 ++++++++++--------- .../java8/FutureConvertersImpl.scala | 14 ++++++++-- .../compat/java8/FutureConvertersTest.java | 19 ++++++++++++++ 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index c926215..dc8415a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ script: - admin/build.sh scala: - 2.11.7 - - 2.12.0-M2 + # - 2.12.0-M3 jdk: - oraclejdk8 notifications: diff --git a/build.sbt b/build.sbt index d959560..1b105c7 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ def jwrite(dir: java.io.File)(name: String, content: String) = { lazy val commonSettings = Seq( scalaVersion := "2.11.7", - crossScalaVersions := List("2.11.7", "2.12.0-M2"), + crossScalaVersions := List("2.11.7" /* TODO, "2.12.0-M3"*/), organization := "org.scala-lang.modules", version := "0.6.0-SNAPSHOT", libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, diff --git a/src/main/scala/scala/compat/java8/FutureConverters.scala b/src/main/scala/scala/compat/java8/FutureConverters.scala index a2c0977..c065e49 100644 --- a/src/main/scala/scala/compat/java8/FutureConverters.scala +++ b/src/main/scala/scala/compat/java8/FutureConverters.scala @@ -54,10 +54,14 @@ object FutureConverters { * not support the CompletableFuture interface */ def toJava[T](f: Future[T]): CompletionStage[T] = { - val cf = new CF[T] - implicit val ec = InternalCallbackExecutor - f onComplete cf - cf + f match { + case p: P[T] => p.wrapped + case _ => + val cf = new CF[T](f) + implicit val ec = InternalCallbackExecutor + f onComplete cf + cf + } } /** @@ -71,15 +75,13 @@ object FutureConverters { * @return a Scala Future that represents the CompletionStage's completion */ def toScala[T](cs: CompletionStage[T]): Future[T] = { - val p = Promise[T]() - val bc = new BiConsumer[T, Throwable] { - override def accept(v: T, e: Throwable): Unit = { - if (e == null) p.complete(Success(v)) - else p.complete(Failure(e)) - } + cs match { + case cf: CF[T] => cf.wrapped + case _ => + val p = new P[T](cs) + cs whenComplete p + p.future } - cs whenComplete bc - p.future } /** diff --git a/src/main/scala/scala/concurrent/java8/FutureConvertersImpl.scala b/src/main/scala/scala/concurrent/java8/FutureConvertersImpl.scala index 1ee934e..edc8723 100644 --- a/src/main/scala/scala/concurrent/java8/FutureConvertersImpl.scala +++ b/src/main/scala/scala/concurrent/java8/FutureConvertersImpl.scala @@ -5,8 +5,9 @@ package scala.concurrent.java8 // Located in this package to access private[concurrent] members -import scala.concurrent.{ Future, Promise, ExecutionContext, ExecutionContextExecutorService, ExecutionContextExecutor, impl } +import scala.concurrent.{ Future, ExecutionContext } import java.util.concurrent._ +import scala.concurrent.impl.Promise.DefaultPromise import scala.util.{ Try, Success, Failure } import java.util.function.{ BiConsumer, Function ⇒ JF, Consumer, BiFunction } @@ -14,7 +15,7 @@ import java.util.function.{ BiConsumer, Function ⇒ JF, Consumer, BiFunction } object FuturesConvertersImpl { def InternalCallbackExecutor = Future.InternalCallbackExecutor - class CF[T] extends CompletableFuture[T] with (Try[T] => Unit) { + class CF[T](val wrapped: Future[T]) extends CompletableFuture[T] with (Try[T] => Unit) { override def apply(t: Try[T]): Unit = t match { case Success(v) ⇒ complete(v) case Failure(e) ⇒ completeExceptionally(e) @@ -84,4 +85,13 @@ object FuturesConvertersImpl { override def toString: String = super[CompletableFuture].toString } + + class P[T](val wrapped: CompletionStage[T]) extends DefaultPromise[T] with BiConsumer[T, Throwable] { + override def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit = super.onSuccess(pf) + + override def accept(v: T, e: Throwable): Unit = { + if (e == null) complete(Success(v)) + else complete(Failure(e)) + } + } } diff --git a/src/test/java/scala/compat/java8/FutureConvertersTest.java b/src/test/java/scala/compat/java8/FutureConvertersTest.java index 7735aba..a87a6b8 100644 --- a/src/test/java/scala/compat/java8/FutureConvertersTest.java +++ b/src/test/java/scala/compat/java8/FutureConvertersTest.java @@ -12,6 +12,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.*; +import static org.junit.Assert.assertSame; import static scala.compat.java8.FutureConverters.*; public class FutureConvertersTest { @@ -398,4 +399,22 @@ public void testToJavaToCompletableFutureJavaObtrudeCalledBeforeScalaComplete() // okay } } + + @Test + public void testToJavaAndBackAvoidsWrappers() { + final Promise p = promise(); + final Future sf = p.future(); + final CompletionStage cs = toJava(sf); + Future sf1 = toScala(cs); + assertSame(sf, sf1); + } + + @Test + public void testToScalaAndBackAvoidsWrappers() { + final CompletableFuture cf = new CompletableFuture<>(); + final Future f = toScala(cf); + CompletionStage cs1 = toJava(f); + assertSame(cf, cs1); + + } }