Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport scala.jdk.OptionConverters to 2.11 / 2.12 #533

Merged
merged 4 commits into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,20 @@ lazy val compat = new MultiScalaCrossProject(
exclude[ReversedMissingMethodProblem]("scala.collection.compat.PackageShared.*"), // it's package-private
exclude[Problem]("scala.collection.compat.*PreservingBuilder*")
)
},
}
)
.jvmSettings(
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-jvm",
junit,
Compile / unmanagedSourceDirectories += {
val jvmParent = (ThisBuild / baseDirectory).value / "compat/jvm/src/main"
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _) | (2, 13)) =>
jvmParent / "scala-2.13"
case _ =>
jvmParent / "scala-2.11_2.12"
}
},
junit
)
.disablePlugins(ScalafixPlugin),
_.jsSettings(
Expand All @@ -111,6 +120,16 @@ lazy val compat = new MultiScalaCrossProject(
}
Seq(s"$opt:$x->$y/")
},
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-js",
Compile / unmanagedSourceDirectories += {
val jsAndNativeSourcesParent = (ThisBuild / baseDirectory).value / "compat/jsNative/src/main"
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _) | (2, 13)) =>
jsAndNativeSourcesParent / "scala-2.13"
case _ =>
jsAndNativeSourcesParent / "scala-2.11_2.12"
}
},
Test / fork := false // Scala.js cannot run forked tests
).jsEnablePlugins(ScalaJSJUnitPlugin),
_.nativeSettings(
Expand All @@ -122,6 +141,15 @@ lazy val compat = new MultiScalaCrossProject(
case Some((3, 1)) => mimaPreviousArtifacts.value.filter(_.revision != "2.6.0")
case _ => mimaPreviousArtifacts.value
}),
Compile / unmanagedSourceDirectories += {
val jsAndNativeSourcesParent = (ThisBuild / baseDirectory).value / "compat/jsNative/src/main"
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _) | (2, 13)) =>
jsAndNativeSourcesParent / "scala-2.13"
case _ =>
jsAndNativeSourcesParent / "scala-2.11_2.12"
}
},
libraryDependencies += "org.scala-native" %%% "junit-runtime" % nativeVersion,
Test / fork := false // Scala Native cannot run forked tests
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.jdk

import java.util.Optional

/** This object provides extension methods that convert between Scala `Option` and Java `Optional`
* types.
*
* It differs from the JVM version as in it does not provide any conversions for the Optional primitive type
* wrappers which are available in the JDK but not in Scala-JS or Scala-Native.
*
* Scala `Option` is extended with a `toJava` method that creates a corresponding `Optional`.
*
* Java `Optional` is extended with a `toScala` method.
*
*
* Example usage:
*
* {{{
* import scala.jdk.OptionConverters._
* val a = Option("example").toJava // Creates java.util.Optional[String] containing "example"
* val b = (None: Option[String]).toJava // Creates an empty java.util.Optional[String]
* val c = a.toScala // Back to Option("example")
* val d = b.toScala // Back to None typed as Option[String]
* }}}
*/
object OptionConverters {

/** Provides conversions from Java `Optional` to Scala `Option` and specialized `Optional` types */
implicit class RichOptional[A](private val o: java.util.Optional[A]) extends AnyVal {

/** Convert a Java `Optional` to a Scala `Option` */
def toScala: Option[A] = if (o.isPresent) Some(o.get) else None

/** Convert a Java `Optional` to a Scala `Option` */
@deprecated("Use `toScala` instead", "2.13.0")
def asScala: Option[A] = if (o.isPresent) Some(o.get) else None
}

/** Provides conversions from Scala `Option` to Java `Optional` types */
implicit class RichOption[A](private val o: Option[A]) extends AnyVal {

/** Convert a Scala `Option` to a generic Java `Optional` */
def toJava: Optional[A] = o match {
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
}

/** Convert a Scala `Option` to a generic Java `Optional` */
@deprecated("Use `toJava` instead", "2.13.0")
def asJava: Optional[A] = o match {
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
}
}
}
121 changes: 121 additions & 0 deletions compat/jvm/src/main/scala-2.11_2.12/scala/jdk/OptionConverters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.jdk

import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong}

/** This object provides extension methods that convert between Scala `Option` and Java `Optional`
* types.
*
* Scala `Option` is extended with a `toJava` method that creates a corresponding `Optional`, and
* a `toJavaPrimitive` method that creates a specialized variant (e.g., `OptionalInt`) if
* applicable.
*
* Java `Optional` is extended with a `toScala` method and a `toJavaPrimitive` method.
*
* Finally, specialized `Optional` types are extended with `toScala` and `toJavaGeneric` methods.
*
* Example usage:
*
* {{{
* import scala.jdk.OptionConverters._
* val a = Option("example").toJava // Creates java.util.Optional[String] containing "example"
* val b = (None: Option[String]).toJava // Creates an empty java.util.Optional[String]
* val c = a.toScala // Back to Option("example")
* val d = b.toScala // Back to None typed as Option[String]
* val e = Option(2.7).toJava // java.util.Optional[Double] containing boxed 2.7
* val f = Option(2.7).toJavaPrimitive // java.util.OptionalDouble containing 2.7 (not boxed)
* val g = f.toScala // Back to Option(2.7)
* val h = f.toJavaGeneric // Same as e
* val i = e.toJavaPrimitive // Same as f
* }}}
*/
object OptionConverters {

/** Provides conversions from Java `Optional` to Scala `Option` and specialized `Optional` types */
implicit class RichOptional[A](private val o: java.util.Optional[A]) extends AnyVal {

/** Convert a Java `Optional` to a Scala `Option` */
def toScala: Option[A] = if (o.isPresent) Some(o.get) else None

/** Convert a Java `Optional` to a Scala `Option` */
@deprecated("Use `toScala` instead", "2.13.0")
def asScala: Option[A] = if (o.isPresent) Some(o.get) else None

/** Convert a generic Java `Optional` to a specialized variant */
def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromJava(o)
}

/** Provides conversions from Scala `Option` to Java `Optional` types */
implicit class RichOption[A](private val o: Option[A]) extends AnyVal {

/** Convert a Scala `Option` to a generic Java `Optional` */
def toJava: Optional[A] = o match {
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
}

/** Convert a Scala `Option` to a generic Java `Optional` */
@deprecated("Use `toJava` instead", "2.13.0")
def asJava: Optional[A] = o match {
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
}

/** Convert a Scala `Option` to a specialized Java `Optional` */
def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromScala(o)
}

/** Provides conversions from `OptionalDouble` to Scala `Option` and the generic `Optional` */
implicit class RichOptionalDouble(private val o: OptionalDouble) extends AnyVal {

/** Convert a Java `OptionalDouble` to a Scala `Option` */
def toScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None

/** Convert a Java `OptionalDouble` to a Scala `Option` */
@deprecated("Use `toScala` instead", "2.13.0")
def asScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None

/** Convert a Java `OptionalDouble` to a generic Java `Optional` */
def toJavaGeneric: Optional[Double] =
if (o.isPresent) Optional.of(o.getAsDouble) else Optional.empty[Double]
}

/** Provides conversions from `OptionalInt` to Scala `Option` and the generic `Optional` */
implicit class RichOptionalInt(private val o: OptionalInt) extends AnyVal {

/** Convert a Java `OptionalInt` to a Scala `Option` */
def toScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None

/** Convert a Java `OptionalInt` to a Scala `Option` */
@deprecated("Use `toScala` instead", "2.13.0")
def asScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None

/** Convert a Java `OptionalInt` to a generic Java `Optional` */
def toJavaGeneric: Optional[Int] =
if (o.isPresent) Optional.of(o.getAsInt) else Optional.empty[Int]
}

/** Provides conversions from `OptionalLong` to Scala `Option` and the generic `Optional` */
implicit class RichOptionalLong(private val o: OptionalLong) extends AnyVal {

/** Convert a Java `OptionalLong` to a Scala `Option` */
def toScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None

/** Convert a Java `OptionalLong` to a Scala `Option` */
@deprecated("Use `toScala` instead", "2.13.0")
def asScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None

/** Convert a Java `OptionalLong` to a generic Java `Optional` */
def toJavaGeneric: Optional[Long] =
if (o.isPresent) Optional.of(o.getAsLong) else Optional.empty[Long]
}
}
74 changes: 74 additions & 0 deletions compat/jvm/src/main/scala-2.11_2.12/scala/jdk/OptionShape.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.jdk

import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong}
import java.{lang => jl}

import scala.annotation.implicitNotFound

/** A type class implementing conversions from a generic Scala `Option` or Java `Optional` to
* a specialized Java variant (for `Double`, `Int` and `Long`).
*
* @tparam A the primitive type wrapped in an option
* @tparam O the specialized Java `Optional` wrapping an element of type `A`
*/
@implicitNotFound("No specialized Optional type exists for elements of type ${A}")
sealed abstract class OptionShape[A, O] {

/** Converts from `Optional` to the specialized variant `O` */
def fromJava(o: Optional[A]): O

/** Converts from `Option` to the specialized variant `O` */
def fromScala(o: Option[A]): O
}

object OptionShape {
implicit val doubleOptionShape: OptionShape[Double, OptionalDouble] =
new OptionShape[Double, OptionalDouble] {
def fromJava(o: Optional[Double]): OptionalDouble =
if (o.isPresent) OptionalDouble.of(o.get) else OptionalDouble.empty

def fromScala(o: Option[Double]): OptionalDouble = o match {
case Some(d) => OptionalDouble.of(d)
case _ => OptionalDouble.empty
}
}
implicit val jDoubleOptionShape: OptionShape[jl.Double, OptionalDouble] =
doubleOptionShape.asInstanceOf[OptionShape[jl.Double, OptionalDouble]]

implicit val intOptionShape: OptionShape[Int, OptionalInt] = new OptionShape[Int, OptionalInt] {
def fromJava(o: Optional[Int]): OptionalInt =
if (o.isPresent) OptionalInt.of(o.get) else OptionalInt.empty

def fromScala(o: Option[Int]): OptionalInt = o match {
case Some(d) => OptionalInt.of(d)
case _ => OptionalInt.empty
}
}
implicit val jIntegerOptionShape: OptionShape[jl.Integer, OptionalInt] =
intOptionShape.asInstanceOf[OptionShape[jl.Integer, OptionalInt]]

implicit val longOptionShape: OptionShape[Long, OptionalLong] =
new OptionShape[Long, OptionalLong] {
def fromJava(o: Optional[Long]): OptionalLong =
if (o.isPresent) OptionalLong.of(o.get) else OptionalLong.empty

def fromScala(o: Option[Long]): OptionalLong = o match {
case Some(d) => OptionalLong.of(d)
case _ => OptionalLong.empty
}
}
implicit val jLongOptionShape: OptionShape[jl.Long, OptionalLong] =
longOptionShape.asInstanceOf[OptionShape[jl.Long, OptionalLong]]
}
55 changes: 55 additions & 0 deletions compat/src/test/scala-js/test/scala/jdk/OptionConvertersTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package test.scala.jdk

import org.junit.Assert.assertEquals
import org.junit.Test

import java.util.Optional
import scala.jdk.OptionConverters._

/**
* The tests were copied from the Scala 2.13 Standard Library. `scala.jdk.javaapi` stuff has been omitted and
* everything concerning `OptionalInt`, `OptionalDouble` and `OptionalLong` is only available in the jvm tests.
*
* See https://github.com/scala/scala/blob/2.13.x/test/junit/scala/jdk/OptionConvertersTest.scala.
*/
class OptionConvertersTest {
@Test
def scalaToEverything(): Unit = {
val o = Option("fish")
val n = None: Option[String]
val od = Option(2.7)
val nd = None: Option[Double]
val oi = Option(4)
val ni = None: Option[Int]
val ol = Option(-1L)
val nl = None: Option[Long]
assertEquals(o.toJava, Optional.of(o.get))
assertEquals(n.toJava, Optional.empty[String])
assertEquals(od.toJava.get: Double, Optional.of(od.get).get: Double, 0)
assertEquals(nd.toJava, Optional.empty[Double])
assertEquals(oi.toJava.get: Int, Optional.of(oi.get).get: Int)
assertEquals(ni.toJava, Optional.empty[Int])
assertEquals(ol.toJava.get: Long, Optional.of(ol.get).get: Long)
assertEquals(nl.toJava, Optional.empty[Long])
}

@Test
def javaGenericToEverything(): Unit = {
val o = Optional.of("fish")
val n = Optional.empty[String]
val od = Optional.of(2.7)
val nd = Optional.empty[Double]
val oi = Optional.of(4)
val ni = Optional.empty[Int]
val ol = Optional.of(-1L)
val nl = Optional.empty[Long]
assertEquals(o.toScala, Option(o.get))
assertEquals(n.toScala, Option.empty[String])
assertEquals(od.toScala, Option(od.get))
assertEquals(nd.toScala, Option.empty[Double])
assertEquals(oi.toScala, Option(oi.get))
assertEquals(ni.toScala, Option.empty[Int])
assertEquals(ol.toScala, Option(ol.get))
assertEquals(nl.toScala, Option.empty[Long])
}
}
Loading