Skip to content

Commit 7a1171d

Browse files
authored
Merge pull request #533 from haukeh/backport-optionconverters
Backport scala.jdk.OptionConverters to 2.11 / 2.12
2 parents f7718aa + cf398c8 commit 7a1171d

File tree

7 files changed

+443
-3
lines changed

7 files changed

+443
-3
lines changed

build.sbt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,20 @@ lazy val compat = new MultiScalaCrossProject(
9191
exclude[ReversedMissingMethodProblem]("scala.collection.compat.PackageShared.*"), // it's package-private
9292
exclude[Problem]("scala.collection.compat.*PreservingBuilder*")
9393
)
94-
},
94+
}
9595
)
9696
.jvmSettings(
9797
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-jvm",
98-
junit,
98+
Compile / unmanagedSourceDirectories += {
99+
val jvmParent = (ThisBuild / baseDirectory).value / "compat/jvm/src/main"
100+
CrossVersion.partialVersion(scalaVersion.value) match {
101+
case Some((3, _) | (2, 13)) =>
102+
jvmParent / "scala-2.13"
103+
case _ =>
104+
jvmParent / "scala-2.11_2.12"
105+
}
106+
},
107+
junit
99108
)
100109
.disablePlugins(ScalafixPlugin),
101110
_.jsSettings(
@@ -111,6 +120,16 @@ lazy val compat = new MultiScalaCrossProject(
111120
}
112121
Seq(s"$opt:$x->$y/")
113122
},
123+
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-js",
124+
Compile / unmanagedSourceDirectories += {
125+
val jsAndNativeSourcesParent = (ThisBuild / baseDirectory).value / "compat/jsNative/src/main"
126+
CrossVersion.partialVersion(scalaVersion.value) match {
127+
case Some((3, _) | (2, 13)) =>
128+
jsAndNativeSourcesParent / "scala-2.13"
129+
case _ =>
130+
jsAndNativeSourcesParent / "scala-2.11_2.12"
131+
}
132+
},
114133
Test / fork := false // Scala.js cannot run forked tests
115134
).jsEnablePlugins(ScalaJSJUnitPlugin),
116135
_.nativeSettings(
@@ -122,6 +141,15 @@ lazy val compat = new MultiScalaCrossProject(
122141
case Some((3, 1)) => mimaPreviousArtifacts.value.filter(_.revision != "2.6.0")
123142
case _ => mimaPreviousArtifacts.value
124143
}),
144+
Compile / unmanagedSourceDirectories += {
145+
val jsAndNativeSourcesParent = (ThisBuild / baseDirectory).value / "compat/jsNative/src/main"
146+
CrossVersion.partialVersion(scalaVersion.value) match {
147+
case Some((3, _) | (2, 13)) =>
148+
jsAndNativeSourcesParent / "scala-2.13"
149+
case _ =>
150+
jsAndNativeSourcesParent / "scala-2.11_2.12"
151+
}
152+
},
125153
libraryDependencies += "org.scala-native" %%% "junit-runtime" % nativeVersion,
126154
Test / fork := false // Scala Native cannot run forked tests
127155
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.jdk
14+
15+
import java.util.Optional
16+
17+
/** This object provides extension methods that convert between Scala `Option` and Java `Optional`
18+
* types.
19+
*
20+
* It differs from the JVM version as in it does not provide any conversions for the Optional primitive type
21+
* wrappers which are available in the JDK but not in Scala-JS or Scala-Native.
22+
*
23+
* Scala `Option` is extended with a `toJava` method that creates a corresponding `Optional`.
24+
*
25+
* Java `Optional` is extended with a `toScala` method.
26+
*
27+
*
28+
* Example usage:
29+
*
30+
* {{{
31+
* import scala.jdk.OptionConverters._
32+
* val a = Option("example").toJava // Creates java.util.Optional[String] containing "example"
33+
* val b = (None: Option[String]).toJava // Creates an empty java.util.Optional[String]
34+
* val c = a.toScala // Back to Option("example")
35+
* val d = b.toScala // Back to None typed as Option[String]
36+
* }}}
37+
*/
38+
object OptionConverters {
39+
40+
/** Provides conversions from Java `Optional` to Scala `Option` and specialized `Optional` types */
41+
implicit class RichOptional[A](private val o: java.util.Optional[A]) extends AnyVal {
42+
43+
/** Convert a Java `Optional` to a Scala `Option` */
44+
def toScala: Option[A] = if (o.isPresent) Some(o.get) else None
45+
46+
/** Convert a Java `Optional` to a Scala `Option` */
47+
@deprecated("Use `toScala` instead", "2.13.0")
48+
def asScala: Option[A] = if (o.isPresent) Some(o.get) else None
49+
}
50+
51+
/** Provides conversions from Scala `Option` to Java `Optional` types */
52+
implicit class RichOption[A](private val o: Option[A]) extends AnyVal {
53+
54+
/** Convert a Scala `Option` to a generic Java `Optional` */
55+
def toJava: Optional[A] = o match {
56+
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
57+
}
58+
59+
/** Convert a Scala `Option` to a generic Java `Optional` */
60+
@deprecated("Use `toJava` instead", "2.13.0")
61+
def asJava: Optional[A] = o match {
62+
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
63+
}
64+
}
65+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.jdk
14+
15+
import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong}
16+
17+
/** This object provides extension methods that convert between Scala `Option` and Java `Optional`
18+
* types.
19+
*
20+
* Scala `Option` is extended with a `toJava` method that creates a corresponding `Optional`, and
21+
* a `toJavaPrimitive` method that creates a specialized variant (e.g., `OptionalInt`) if
22+
* applicable.
23+
*
24+
* Java `Optional` is extended with a `toScala` method and a `toJavaPrimitive` method.
25+
*
26+
* Finally, specialized `Optional` types are extended with `toScala` and `toJavaGeneric` methods.
27+
*
28+
* Example usage:
29+
*
30+
* {{{
31+
* import scala.jdk.OptionConverters._
32+
* val a = Option("example").toJava // Creates java.util.Optional[String] containing "example"
33+
* val b = (None: Option[String]).toJava // Creates an empty java.util.Optional[String]
34+
* val c = a.toScala // Back to Option("example")
35+
* val d = b.toScala // Back to None typed as Option[String]
36+
* val e = Option(2.7).toJava // java.util.Optional[Double] containing boxed 2.7
37+
* val f = Option(2.7).toJavaPrimitive // java.util.OptionalDouble containing 2.7 (not boxed)
38+
* val g = f.toScala // Back to Option(2.7)
39+
* val h = f.toJavaGeneric // Same as e
40+
* val i = e.toJavaPrimitive // Same as f
41+
* }}}
42+
*/
43+
object OptionConverters {
44+
45+
/** Provides conversions from Java `Optional` to Scala `Option` and specialized `Optional` types */
46+
implicit class RichOptional[A](private val o: java.util.Optional[A]) extends AnyVal {
47+
48+
/** Convert a Java `Optional` to a Scala `Option` */
49+
def toScala: Option[A] = if (o.isPresent) Some(o.get) else None
50+
51+
/** Convert a Java `Optional` to a Scala `Option` */
52+
@deprecated("Use `toScala` instead", "2.13.0")
53+
def asScala: Option[A] = if (o.isPresent) Some(o.get) else None
54+
55+
/** Convert a generic Java `Optional` to a specialized variant */
56+
def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromJava(o)
57+
}
58+
59+
/** Provides conversions from Scala `Option` to Java `Optional` types */
60+
implicit class RichOption[A](private val o: Option[A]) extends AnyVal {
61+
62+
/** Convert a Scala `Option` to a generic Java `Optional` */
63+
def toJava: Optional[A] = o match {
64+
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
65+
}
66+
67+
/** Convert a Scala `Option` to a generic Java `Optional` */
68+
@deprecated("Use `toJava` instead", "2.13.0")
69+
def asJava: Optional[A] = o match {
70+
case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A]
71+
}
72+
73+
/** Convert a Scala `Option` to a specialized Java `Optional` */
74+
def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromScala(o)
75+
}
76+
77+
/** Provides conversions from `OptionalDouble` to Scala `Option` and the generic `Optional` */
78+
implicit class RichOptionalDouble(private val o: OptionalDouble) extends AnyVal {
79+
80+
/** Convert a Java `OptionalDouble` to a Scala `Option` */
81+
def toScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None
82+
83+
/** Convert a Java `OptionalDouble` to a Scala `Option` */
84+
@deprecated("Use `toScala` instead", "2.13.0")
85+
def asScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None
86+
87+
/** Convert a Java `OptionalDouble` to a generic Java `Optional` */
88+
def toJavaGeneric: Optional[Double] =
89+
if (o.isPresent) Optional.of(o.getAsDouble) else Optional.empty[Double]
90+
}
91+
92+
/** Provides conversions from `OptionalInt` to Scala `Option` and the generic `Optional` */
93+
implicit class RichOptionalInt(private val o: OptionalInt) extends AnyVal {
94+
95+
/** Convert a Java `OptionalInt` to a Scala `Option` */
96+
def toScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None
97+
98+
/** Convert a Java `OptionalInt` to a Scala `Option` */
99+
@deprecated("Use `toScala` instead", "2.13.0")
100+
def asScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None
101+
102+
/** Convert a Java `OptionalInt` to a generic Java `Optional` */
103+
def toJavaGeneric: Optional[Int] =
104+
if (o.isPresent) Optional.of(o.getAsInt) else Optional.empty[Int]
105+
}
106+
107+
/** Provides conversions from `OptionalLong` to Scala `Option` and the generic `Optional` */
108+
implicit class RichOptionalLong(private val o: OptionalLong) extends AnyVal {
109+
110+
/** Convert a Java `OptionalLong` to a Scala `Option` */
111+
def toScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None
112+
113+
/** Convert a Java `OptionalLong` to a Scala `Option` */
114+
@deprecated("Use `toScala` instead", "2.13.0")
115+
def asScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None
116+
117+
/** Convert a Java `OptionalLong` to a generic Java `Optional` */
118+
def toJavaGeneric: Optional[Long] =
119+
if (o.isPresent) Optional.of(o.getAsLong) else Optional.empty[Long]
120+
}
121+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.jdk
14+
15+
import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong}
16+
import java.{lang => jl}
17+
18+
import scala.annotation.implicitNotFound
19+
20+
/** A type class implementing conversions from a generic Scala `Option` or Java `Optional` to
21+
* a specialized Java variant (for `Double`, `Int` and `Long`).
22+
*
23+
* @tparam A the primitive type wrapped in an option
24+
* @tparam O the specialized Java `Optional` wrapping an element of type `A`
25+
*/
26+
@implicitNotFound("No specialized Optional type exists for elements of type ${A}")
27+
sealed abstract class OptionShape[A, O] {
28+
29+
/** Converts from `Optional` to the specialized variant `O` */
30+
def fromJava(o: Optional[A]): O
31+
32+
/** Converts from `Option` to the specialized variant `O` */
33+
def fromScala(o: Option[A]): O
34+
}
35+
36+
object OptionShape {
37+
implicit val doubleOptionShape: OptionShape[Double, OptionalDouble] =
38+
new OptionShape[Double, OptionalDouble] {
39+
def fromJava(o: Optional[Double]): OptionalDouble =
40+
if (o.isPresent) OptionalDouble.of(o.get) else OptionalDouble.empty
41+
42+
def fromScala(o: Option[Double]): OptionalDouble = o match {
43+
case Some(d) => OptionalDouble.of(d)
44+
case _ => OptionalDouble.empty
45+
}
46+
}
47+
implicit val jDoubleOptionShape: OptionShape[jl.Double, OptionalDouble] =
48+
doubleOptionShape.asInstanceOf[OptionShape[jl.Double, OptionalDouble]]
49+
50+
implicit val intOptionShape: OptionShape[Int, OptionalInt] = new OptionShape[Int, OptionalInt] {
51+
def fromJava(o: Optional[Int]): OptionalInt =
52+
if (o.isPresent) OptionalInt.of(o.get) else OptionalInt.empty
53+
54+
def fromScala(o: Option[Int]): OptionalInt = o match {
55+
case Some(d) => OptionalInt.of(d)
56+
case _ => OptionalInt.empty
57+
}
58+
}
59+
implicit val jIntegerOptionShape: OptionShape[jl.Integer, OptionalInt] =
60+
intOptionShape.asInstanceOf[OptionShape[jl.Integer, OptionalInt]]
61+
62+
implicit val longOptionShape: OptionShape[Long, OptionalLong] =
63+
new OptionShape[Long, OptionalLong] {
64+
def fromJava(o: Optional[Long]): OptionalLong =
65+
if (o.isPresent) OptionalLong.of(o.get) else OptionalLong.empty
66+
67+
def fromScala(o: Option[Long]): OptionalLong = o match {
68+
case Some(d) => OptionalLong.of(d)
69+
case _ => OptionalLong.empty
70+
}
71+
}
72+
implicit val jLongOptionShape: OptionShape[jl.Long, OptionalLong] =
73+
longOptionShape.asInstanceOf[OptionShape[jl.Long, OptionalLong]]
74+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package test.scala.jdk
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
import java.util.Optional
7+
import scala.jdk.OptionConverters._
8+
9+
/**
10+
* The tests were copied from the Scala 2.13 Standard Library. `scala.jdk.javaapi` stuff has been omitted and
11+
* everything concerning `OptionalInt`, `OptionalDouble` and `OptionalLong` is only available in the jvm tests.
12+
*
13+
* See https://github.com/scala/scala/blob/2.13.x/test/junit/scala/jdk/OptionConvertersTest.scala.
14+
*/
15+
class OptionConvertersTest {
16+
@Test
17+
def scalaToEverything(): Unit = {
18+
val o = Option("fish")
19+
val n = None: Option[String]
20+
val od = Option(2.7)
21+
val nd = None: Option[Double]
22+
val oi = Option(4)
23+
val ni = None: Option[Int]
24+
val ol = Option(-1L)
25+
val nl = None: Option[Long]
26+
assertEquals(o.toJava, Optional.of(o.get))
27+
assertEquals(n.toJava, Optional.empty[String])
28+
assertEquals(od.toJava.get: Double, Optional.of(od.get).get: Double, 0)
29+
assertEquals(nd.toJava, Optional.empty[Double])
30+
assertEquals(oi.toJava.get: Int, Optional.of(oi.get).get: Int)
31+
assertEquals(ni.toJava, Optional.empty[Int])
32+
assertEquals(ol.toJava.get: Long, Optional.of(ol.get).get: Long)
33+
assertEquals(nl.toJava, Optional.empty[Long])
34+
}
35+
36+
@Test
37+
def javaGenericToEverything(): Unit = {
38+
val o = Optional.of("fish")
39+
val n = Optional.empty[String]
40+
val od = Optional.of(2.7)
41+
val nd = Optional.empty[Double]
42+
val oi = Optional.of(4)
43+
val ni = Optional.empty[Int]
44+
val ol = Optional.of(-1L)
45+
val nl = Optional.empty[Long]
46+
assertEquals(o.toScala, Option(o.get))
47+
assertEquals(n.toScala, Option.empty[String])
48+
assertEquals(od.toScala, Option(od.get))
49+
assertEquals(nd.toScala, Option.empty[Double])
50+
assertEquals(oi.toScala, Option(oi.get))
51+
assertEquals(ni.toScala, Option.empty[Int])
52+
assertEquals(ol.toScala, Option(ol.get))
53+
assertEquals(nl.toScala, Option.empty[Long])
54+
}
55+
}

0 commit comments

Comments
 (0)