Skip to content

Commit 31244ba

Browse files
committed
Merge pull request #48 from retronym/review/39
Function Converters
2 parents 9ef565f + 44643f4 commit 31244ba

File tree

5 files changed

+1327
-85
lines changed

5 files changed

+1327
-85
lines changed

README.md

+65-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,69 @@ class Test {
2525

2626
[More Examples / Documentation](src/test/java/scala/compat/java8/LambdaTest.java)
2727

28+
## Converters between `scala.FunctionN` and `java.util.function`
29+
30+
A set of converters that enable interconversion between Java's standard
31+
Functional Interfaces defined in `java.util.function` and Scala's `Function0`,
32+
`Function1`, and `Function2` traits. These are intended for use when you
33+
already have an instance of a `java.util.function` and need a Scala function,
34+
or have a Scala function and need an instance of a `java.util.function`.
35+
36+
The `.asScala` extension method will convert a `java.util.function` to the corresponding
37+
Scala function. The `.asJava` extension method will convert a Scala function to
38+
the most specific corresponding Java functional interface. If you wish to obtain
39+
a less specific functional interface, there are named methods that start with `asJava`
40+
and continue with the name of the Java functional interface. For instance, the
41+
most specific interface corresponding to the Scala function `val rev = (s: String) => s.reverse`
42+
is `UnaryOperator[String]`, and that is what `rev.asJava` will produce. However,
43+
`asJavaFunction(rev)` will return a `java.util.function.Function[String, String]` instead.
44+
45+
The `asJava` methods can also be called conveniently from Java. There are additional
46+
`asScalaFrom` methods (e.g. `asScalaFromUnaryOperator`) that will perform the
47+
functional-interface-to-Scala-function conversion; this is primarily of use when calling
48+
from Java since the `.asScala` extension method is more convenient in Scala.
49+
50+
#### Usage examples
51+
52+
In Scala:
53+
54+
```scala
55+
import java.util.function._
56+
import scala.compat.java8.FunctionConverters._
57+
58+
val foo: Int => Boolean = i => i > 7
59+
def testBig(ip: IntPredicate) = ip.test(9)
60+
println(testBig(foo.asJava)) // Prints true
61+
62+
val bar = new UnaryOperator[String]{ def apply(s: String) = s.reverse }
63+
List("cod", "herring").map(bar.asScala) // List("doc", "gnirrih")
64+
65+
def testA[A](p: Predicate[A])(a: A) = p.test(a)
66+
println(testA(asJavaPredicate(foo))(4)) // Prints false
67+
68+
// println(testA(foo.asJava)(4)) <-- doesn't work
69+
// IntPredicate does not extend Predicate!
70+
```
71+
72+
In Java:
73+
74+
```java
75+
import java.util.function.*;
76+
import scala.compat.java8.FunctionConverters;
77+
78+
class Example {
79+
String foo(UnaryOperator<String> f) {
80+
return f.apply("halibut");
81+
}
82+
String bar(scala.Function1<String, String> f) {
83+
return foo(functionConverters.asJavaUnaryOperator(f));
84+
}
85+
String baz(Function<String, String> f) {
86+
return bar(functionConverters.asScalaFromFunction(f));
87+
}
88+
}
89+
```
90+
2891
## Converters between `scala.concurrent` and `java.util.concurrent`
2992

3093
- [API](src/main/scala/scala/compat/java8/FutureConverters.scala)
@@ -53,6 +116,7 @@ class Test {
53116
}
54117
```
55118

119+
56120
## Future work
57-
- Converters for `java.util.function`, `java.util.stream`
121+
- Converters for `java.util.stream`
58122
- [`Spliterator`](https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html)s for Scala collections

build.sbt

+107-84
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,114 @@
1-
scalaModuleSettings
2-
3-
scalaVersion := "2.11.6"
4-
5-
organization := "org.scala-lang.modules"
6-
7-
name := "scala-java8-compat"
8-
9-
version := "0.6.0-SNAPSHOT"
10-
11-
// important!! must come here (why?)
12-
scalaModuleOsgiSettings
13-
14-
OsgiKeys.exportPackage := Seq(s"scala.compat.java8.*;version=${version.value}")
15-
16-
OsgiKeys.privatePackage := List("scala.concurrent.java8.*")
17-
18-
libraryDependencies += "junit" % "junit" % "4.11" % "test"
19-
20-
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.4" % "test"
21-
22-
libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test"
23-
24-
mimaPreviousVersion := None
25-
26-
testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a")
27-
28-
sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
29-
def write(name: String, content: String) = {
30-
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
31-
IO.write(f, content)
32-
f
33-
}
34-
(
35-
Seq(write("JFunction", CodeGen.factory)) ++
36-
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
37-
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
38-
CodeGen.specializedF0.map((write _).tupled) ++
39-
CodeGen.specializedF1.map((write _).tupled) ++
40-
CodeGen.specializedF2.map((write _).tupled)
41-
)
42-
}
43-
44-
sourceGenerators in Test <+= sourceManaged in Test map { dir =>
45-
def write(name: String, content: String) = {
46-
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
47-
IO.write(f, content)
48-
f
49-
}
50-
Seq(write("TestApi", CodeGen.testApi))
51-
}
52-
53-
initialize := {
54-
// Run previously configured inialization...
55-
initialize.value
56-
// ... and then check the Java version.
57-
val specVersion = sys.props("java.specification.version")
58-
if (Set("1.5", "1.6", "1.7") contains specVersion)
59-
sys.error("Java 8 or higher is required for this project.")
60-
}
61-
621
val disableDocs = sys.props("nodocs") == "true"
632

64-
publishArtifact in packageDoc := !disableDocs
65-
663
lazy val JavaDoc = config("genjavadoc") extend Compile
674

68-
sources in (Compile, doc) := {
69-
val orig = (sources in (Compile, doc)).value
70-
orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449
5+
def jwrite(dir: java.io.File)(name: String, content: String) = {
6+
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
7+
IO.write(f, content)
8+
f
719
}
7210

73-
inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq(
74-
packageDoc in Compile <<= packageDoc in JavaDoc,
75-
sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) =>
76-
val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java"))
77-
allJavaSources.filterNot(_.getName.contains("FuturesConvertersImpl.java")) // this file triggers bugs in genjavadoc
78-
},
79-
javacOptions in JavaDoc := Seq(),
80-
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
81-
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
82-
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
83-
))
84-
85-
initialCommands :=
86-
"""|import scala.concurrent._
87-
|import ExecutionContext.Implicits.global
88-
|import java.util.concurrent.{CompletionStage,CompletableFuture}
89-
|import scala.compat.java8.FutureConverter._
90-
|""".stripMargin
11+
lazy val commonSettings = Seq(
12+
scalaVersion := "2.11.6",
13+
organization := "org.scala-lang.modules",
14+
version := "0.6.0-SNAPSHOT",
15+
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
16+
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
17+
)
18+
19+
lazy val fnGen = (project in file("fnGen")).
20+
settings(commonSettings: _*).
21+
settings(
22+
fork in run := true // Needed if you run this project directly
23+
)
9124

25+
lazy val root = (project in file(".")).
26+
dependsOn(fnGen).
27+
settings(scalaModuleSettings: _*).
28+
settings(commonSettings: _*).
29+
settings(
30+
name := "scala-java8-compat"
31+
).
32+
settings(
33+
// important!! must come here (why?)
34+
scalaModuleOsgiSettings: _*
35+
).
36+
settings(
37+
fork := true, // This must be set so that runner task is forked when it runs fnGen and the compiler gets a proper classpath
38+
39+
OsgiKeys.exportPackage := Seq(s"scala.compat.java8.*;version=${version.value}"),
40+
41+
OsgiKeys.privatePackage := List("scala.concurrent.java8.*"),
42+
43+
libraryDependencies += "junit" % "junit" % "4.11" % "test",
44+
45+
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.4" % "test",
46+
47+
libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test",
48+
49+
mimaPreviousVersion := None,
50+
51+
testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a"),
52+
53+
(sourceGenerators in Compile) += Def.task {
54+
val out = (sourceManaged in Compile).value
55+
if (!out.exists) IO.createDirectory(out)
56+
val canon = out.getCanonicalPath
57+
val args = (new File(canon, "FunctionConverters.scala")).toString :: Nil
58+
val runTarget = (mainClass in Compile in fnGen).value getOrElse "No main class defined for function conversion generator"
59+
val classPath = (fullClasspath in Compile in fnGen).value
60+
toError(runner.value.run(runTarget, classPath.files, args, streams.value.log))
61+
(out ** "*.scala").get
62+
}.taskValue,
63+
64+
sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
65+
val write = jwrite(dir) _
66+
Seq(write("JFunction", CodeGen.factory)) ++
67+
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
68+
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
69+
CodeGen.specializedF0.map(write.tupled) ++
70+
CodeGen.specializedF1.map(write.tupled) ++
71+
CodeGen.specializedF2.map(write.tupled)
72+
},
73+
74+
sourceGenerators in Test <+= sourceManaged in Test map { dir =>
75+
Seq(jwrite(dir)("TestApi", CodeGen.testApi))
76+
},
77+
78+
initialize := {
79+
// Run previously configured inialization...
80+
initialize.value
81+
// ... and then check the Java version.
82+
val specVersion = sys.props("java.specification.version")
83+
if (Set("1.5", "1.6", "1.7") contains specVersion)
84+
sys.error("Java 8 or higher is required for this project.")
85+
},
86+
87+
publishArtifact in packageDoc := !disableDocs,
88+
89+
sources in (Compile, doc) := {
90+
val orig = (sources in (Compile, doc)).value
91+
orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449
92+
}
93+
).
94+
settings(
95+
(inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq(
96+
packageDoc in Compile <<= packageDoc in JavaDoc,
97+
sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) =>
98+
val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java"))
99+
allJavaSources.filterNot(_.getName.contains("FuturesConvertersImpl.java")) // this file triggers bugs in genjavadoc
100+
},
101+
javacOptions in JavaDoc := Seq(),
102+
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
103+
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
104+
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
105+
))): _*
106+
).
107+
settings(
108+
initialCommands :=
109+
"""|import scala.concurrent._
110+
|import ExecutionContext.Implicits.global
111+
|import java.util.concurrent.{CompletionStage,CompletableFuture}
112+
|import scala.compat.java8.FutureConverter._
113+
|""".stripMargin
114+
)

0 commit comments

Comments
 (0)