diff --git a/build.sbt b/build.sbt index 8cd46aa..8a58f0f 100644 --- a/build.sbt +++ b/build.sbt @@ -22,13 +22,39 @@ inThisBuild( ) ) -lazy val root = (project in file(".")) +lazy val core = (project in file("core")) + .enablePlugins(KotlinPlugin) + .settings( + organization := "io.github.leviysoft", + name := "scala-kotlin-compat", + kotlinVersion := "1.9.25", + kotlincJvmTarget := "11", + kotlincOptions += "-Xjvm-default=all", + kotlinLib("stdlib") + ) + +lazy val coroutines = (project in file("coroutines")) .enablePlugins(KotlinPlugin) .settings( organization := "io.github.leviysoft", - name := "scala-kotlin-compat", + name := "scala-kotlin-coroutines-compat", kotlinVersion := "1.9.25", kotlincJvmTarget := "11", kotlincOptions += "-Xjvm-default=all", - kotlinLib("stdlib") + kotlinLib("stdlib"), + libraryDependencies ++= Seq( + "org.jetbrains.kotlinx" % "kotlinx-coroutines-core" % "1.8.1", + "com.github.sbt" % "junit-interface" % "0.13.3" % Test, + "org.jetbrains.kotlin" % "kotlin-test-junit" % kotlinVersion.value % Test + ) + ) + +lazy val root = (project in file(".")) + .dependsOn(core, coroutines) + .aggregate(core, coroutines) + .settings( + crossScalaVersions := Nil, + publish := {}, + publishArtifact := false, + publish / skip := true ) diff --git a/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToJava.kt b/core/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToJava.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToJava.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToJava.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToScala.kt b/core/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToScala.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToScala.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/CollectionConvertersToScala.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/DurationConverters.kt b/core/src/main/kotlin/com/github/leviysoft/sk/DurationConverters.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/DurationConverters.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/DurationConverters.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToKotlin.kt b/core/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToKotlin.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToKotlin.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToKotlin.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToScala.kt b/core/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToScala.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToScala.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/FunctionConvertersToScala.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/FutureConverters.kt b/core/src/main/kotlin/com/github/leviysoft/sk/FutureConverters.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/FutureConverters.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/FutureConverters.kt diff --git a/src/main/kotlin/com/github/leviysoft/sk/OptionConverters.kt b/core/src/main/kotlin/com/github/leviysoft/sk/OptionConverters.kt similarity index 100% rename from src/main/kotlin/com/github/leviysoft/sk/OptionConverters.kt rename to core/src/main/kotlin/com/github/leviysoft/sk/OptionConverters.kt diff --git a/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/CoroutineScalaConversions.kt b/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/CoroutineScalaConversions.kt new file mode 100644 index 0000000..7dacb2c --- /dev/null +++ b/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/CoroutineScalaConversions.kt @@ -0,0 +1,76 @@ +@file:OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) + +package com.github.leviysoft.sk.coroutines + +import kotlinx.coroutines.* + +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.util.Failure +import scala.util.Success +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +fun CoroutineScope.scalaFuture( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +) : Future { + require(!start.isLazy) { "$start start is not supported" } + val newContext = this.newCoroutineContext(context) + val promise = Promise.apply() + val coroutine = PromiseCoroutine(newContext, promise) + coroutine.start(start, coroutine, block) + + return promise.future() +} + +fun Deferred.asScalaFuture(): Future { + val promise: Promise = Promise.apply() + + invokeOnCompletion { + try { + promise.success(getCompleted()) + } catch (t: Throwable) { + promise.failure(t) + } + } + + return promise.future() +} + +fun Job.asScalaFuture(): Future { + val promise: Promise = Promise.apply() + invokeOnCompletion { cause -> + if (cause === null) promise.success(scala.runtime.BoxedUnit.UNIT) + else promise.failure(cause) + } + return promise.future() +} + +fun Future.asDeferred(executor: ExecutionContext): Deferred { + val result = CompletableDeferred() + this.onComplete({ res -> + when(res) { + is Success -> result.complete(res.value()) + is Failure -> result.completeExceptionally(res.exception()) + else -> throw IllegalStateException("Unreachable") + } + }, executor) + return result +} + +suspend fun Future.await(executor: ExecutionContext): T { + return suspendCancellableCoroutine { cont: CancellableContinuation -> + this.onComplete({ res -> + when(res) { + is Success -> cont.resume(res.value()) + is Failure -> cont.resumeWithException(res.exception()) + else -> throw IllegalStateException("Unreachable") + } + }, executor) + } +} \ No newline at end of file diff --git a/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/PromiseCoroutine.kt b/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/PromiseCoroutine.kt new file mode 100644 index 0000000..6e7c9aa --- /dev/null +++ b/coroutines/src/main/kotlin/com/github/leviysoft/sk/coroutines/PromiseCoroutine.kt @@ -0,0 +1,17 @@ +package com.github.leviysoft.sk.coroutines + +import kotlinx.coroutines.AbstractCoroutine +import kotlinx.coroutines.InternalCoroutinesApi +import scala.concurrent.Promise +import kotlin.coroutines.CoroutineContext + +@OptIn(InternalCoroutinesApi::class) +internal class PromiseCoroutine(context: CoroutineContext, private val promise: Promise): AbstractCoroutine(context, initParentJob = true, active = true) { + override fun onCompleted(value: T) { + this.promise.success(value) + } + + override fun onCancelled(cause: Throwable, handled: Boolean) { + this.promise.failure(cause) + } +} \ No newline at end of file diff --git a/coroutines/src/test/kotlin/com/github/leviysoft/sk/coroutines/FutureConversionTests.kt b/coroutines/src/test/kotlin/com/github/leviysoft/sk/coroutines/FutureConversionTests.kt new file mode 100644 index 0000000..b59e113 --- /dev/null +++ b/coroutines/src/test/kotlin/com/github/leviysoft/sk/coroutines/FutureConversionTests.kt @@ -0,0 +1,20 @@ +package com.github.leviysoft.sk.coroutines + +import kotlinx.coroutines.runBlocking +import scala.concurrent.ExecutionContext +import scala.concurrent.`Future$` +import kotlin.test.Test +import kotlin.test.assertEquals + +class FutureConversionTests { + @Test + fun awaitFutureTest() { + val future = `Future$`.`MODULE$`.successful(42) + + val result = runBlocking { + future.await(ExecutionContext.global()) + } + + assertEquals(42, result) + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 26541ec..880b3d9 100644 --- a/readme.md +++ b/readme.md @@ -16,4 +16,5 @@ The following components of `scala.jdk` are implemented: - [x] FutureConverters - [x] OptionConverters - [ ] StreamConverters - \ No newline at end of file + +In addition, `scala-kotlin-coroutines-compat` provides utilities for calling `suspend fun`s as `Future`s \ No newline at end of file