diff --git a/build.sbt b/build.sbt index 9b06450..76eee92 100644 --- a/build.sbt +++ b/build.sbt @@ -2,15 +2,15 @@ val disableDocs = sys.props("nodocs") == "true" lazy val JavaDoc = config("genjavadoc") extend Compile -def jwrite(dir: java.io.File)(name: String, content: String) = { - val f = dir / "scala" / "runtime" / "java8" / s"${name}.java" +def jwrite(dir: java.io.File, pck: String = "scala/compat/java8")(name: String, content: String) = { + val f = dir / pck / s"${name}.java" IO.write(f, content) f } lazy val commonSettings = Seq( - scalaVersion := "2.11.8", - crossScalaVersions := List("2.11.8", "2.12.0-M5"), + crossScalaVersions := List("2.12.0-RC1", "2.11.8"), + scalaVersion := crossScalaVersions.value.head, organization := "org.scala-lang.modules", version := "0.8.0-SNAPSHOT" ) @@ -62,15 +62,16 @@ lazy val root = (project in file(".")). }.taskValue, sourceGenerators in Compile <+= (sourceManaged in Compile, scalaVersion) map { (dir, v) => + val write = jwrite(dir) _ if(v.startsWith("2.11.")) { - val write = jwrite(dir) _ Seq(write("JFunction", CodeGen.factory)) ++ - (0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++ - (0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++ - CodeGen.specializedF0.map(write.tupled) ++ - CodeGen.specializedF1.map(write.tupled) ++ - CodeGen.specializedF2.map(write.tupled) - } else Seq.empty + (0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++ + (0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++ + CodeGen.specializedF0.map(write.tupled) ++ + CodeGen.specializedF1.map(write.tupled) ++ + CodeGen.specializedF2.map(write.tupled) ++ + CodeGen.packageDummy.map((jwrite(dir, "java/runtime/java8") _).tupled) + } else CodeGen.create212.map(write.tupled) }, sourceGenerators in Test <+= sourceManaged in Test map { dir => @@ -79,7 +80,7 @@ lazy val root = (project in file(".")). initialize := { // Run previously configured inialization... - initialize.value + val _ = initialize.value // ... and then check the Java version. val specVersion = sys.props("java.specification.version") if (Set("1.5", "1.6", "1.7") contains specVersion) diff --git a/fnGen/WrapFnGen.scala b/fnGen/WrapFnGen.scala index 02938f5..46779bc 100644 --- a/fnGen/WrapFnGen.scala +++ b/fnGen/WrapFnGen.scala @@ -1,12 +1,12 @@ /* - * Copyright (C) 2015 Typesafe Inc. + * Copyright (C) 2015-2016 Lightbend Inc. */ object WrapFnGen { val copyright = s""" |/* - | * Copyright (C) 2015, Typesafe Inc. + | * Copyright (C) 2015-2016, Lightbend Inc. | * This file auto-generated by WrapFnGen.scala. Do not modify directly. | */ |""".stripMargin diff --git a/project/CodeGen.scala b/project/CodeGen.scala index 15c475f..250637a 100644 --- a/project/CodeGen.scala +++ b/project/CodeGen.scala @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Typesafe Inc. + * Copyright (C) 2012-2016 Lightbend Inc. */ sealed abstract class Type(val code: Char, val prim: String, val ref: String) { @@ -25,7 +25,7 @@ object Type { } object CodeGen { - def packaging = "package scala.runtime.java8;" + def packaging = "package scala.compat.java8;" case class arity(n: Int) { val ns = (1 to n).toList @@ -288,7 +288,7 @@ object CodeGen { private val copyright = """ |/* - | * Copyright (C) 2012-2015 Typesafe Inc. + | * Copyright (C) 2012-2016 Lightbend Inc. | */""".stripMargin.trim private def function0SpecMethods = { @@ -433,4 +433,101 @@ object CodeGen { } def indent(s: String) = s.linesIterator.map(" " + _).mkString("\n") + + /** Create a dummy class to put into scala.runtime.java8 for Scala 2.11 so that wildcard imports from the + * package won't fail. This allows importing both `scala.runtime.java8.*` and `scala.compat.java8.*` for + * source compatibility between 2.11 and 2.12. + */ + def packageDummy: Seq[(String, String)] = Seq( + ( "PackageDummy", + s"""$copyright + | + |package scala.runtime.java8; + | + |public final class PackageDummy { + | private PackageDummy() {} + |} + """.stripMargin) + ) + + /** Create the simpler JFunction and JProcedure sources for Scala 2.12+ */ + def create212: Seq[(String, String)] = { + val blocks = for(i <- 0 to 22) yield { + val ts = (1 to i).map(i => s"T$i").mkString(", ") + val tsComma = if(ts.isEmpty) "" else s"$ts," + val tsAngled = if(ts.isEmpty) "" else s"<$ts>" + val paramTs = (1 to i).map(i => s"T$i t$i").mkString(", ") + val argTs = (1 to i).map(i => s"t$i").mkString(", ") + + ( + ( s"JFunction$i", + s"""$copyright + |$packaging + | + |/** @deprecated Use scala.Function$i in Scala 2.12 */ + |@Deprecated + |@FunctionalInterface + |public interface JFunction$i<$tsComma R> extends scala.Function$i<$tsComma R>, java.io.Serializable {} + """.stripMargin), + ( s"JProcedure$i", + s"""$copyright + |$packaging + | + |import scala.runtime.BoxedUnit; + | + |@FunctionalInterface + |public interface JProcedure$i$tsAngled extends scala.Function$i<$tsComma BoxedUnit> { + | void applyVoid($paramTs); + | default BoxedUnit apply($paramTs) { applyVoid($argTs); return BoxedUnit.UNIT; } + |} + """.stripMargin), + s""" /** @deprecated Not needed anymore in Scala 2.12 */ + | @Deprecated + | public static <$tsComma R> scala.Function$i<$tsComma R> func(scala.Function$i<$tsComma R> f) { return f; } + | public static $tsAngled scala.Function$i<$tsComma BoxedUnit> proc(JProcedure$i$tsAngled p) { return p; } + """.stripMargin + ) + } + + def specialize(args: String): List[(Int, String, String)] = { + def combinations(l: List[String]): List[List[Char]] = + l.foldRight(List(Nil: List[Char])) { (s, z) => s.toList.flatMap(c => z.map(c :: _)) } + val split = args.split(",") + combinations(split.toList).map { s => + val types = s.map { + case 'B' => "Byte" + case 'S' => "Short" + case 'V' => "BoxedUnit" + case 'I' => "Integer" + case 'J' => "Long" + case 'C' => "Character" + case 'F' => "Float" + case 'D' => "Double" + case 'Z' => "Boolean" + } + (split.length-1, (types.tail :+ types.head).mkString(", "), "$mc" + s.mkString + "$sp") + } + } + + val specialized = + List("V", "V,IJFD", "V,IJD,IJD").flatMap(specialize).map { case (i, a, sp) => + s" public static scala.Function$i<$a> procSpecialized(JFunction$i$sp f) { return f; }" } ++ + List("BSIJCFDZ", "ZIFJD,IJFD", "ZIFJD,IJD,IJD").flatMap(specialize).map { case (i, a, sp) => + s" public static scala.Function$i<$a> funcSpecialized(JFunction$i$sp f) { return f; }" } + + (blocks.map(_._1) ++ blocks.map(_._2)) :+ + ( "JFunction", + s"""$copyright + |$packaging + | + |import scala.runtime.BoxedUnit; + |import scala.runtime.java8.*; + | + |public final class JFunction { + | private JFunction() {} + |${specialized.mkString("\n")} + |${blocks.map(_._3).mkString("\n")} + |} + """.stripMargin) + } } diff --git a/src/test/java/scala/runtime/java8/BoxingTest.java b/src/test/java/scala/compat/java8/BoxingTest.java similarity index 75% rename from src/test/java/scala/runtime/java8/BoxingTest.java rename to src/test/java/scala/compat/java8/BoxingTest.java index 083b5de..94b05ff 100644 --- a/src/test/java/scala/runtime/java8/BoxingTest.java +++ b/src/test/java/scala/compat/java8/BoxingTest.java @@ -1,14 +1,15 @@ /* * Copyright (C) 2012-2015 Typesafe Inc. */ -package scala.runtime.java8; +package scala.compat.java8; import org.junit.Test; +import scala.runtime.java8.*; public class BoxingTest { @Test public void nullBoxesInterpretedAsZeroF1() { - JFunction1$mcII$sp jFunction1 = new JFunction1$mcII$sp() { + scala.Function1 jFunction1 = new JFunction1$mcII$sp() { @Override public int apply$mcII$sp(int v1) { return v1 + 1; @@ -20,7 +21,7 @@ public void nullBoxesInterpretedAsZeroF1() { @Test public void nullBoxesInterpretedAsZeroF2() { - JFunction2$mcIII$sp jFunction2 = new JFunction2$mcIII$sp() { + scala.Function2 jFunction2 = new JFunction2$mcIII$sp() { @Override public int apply$mcIII$sp(int v1, int v2) { return v1 + v2 + 1; diff --git a/src/test/java/scala/compat/java8/LambdaTest.java b/src/test/java/scala/compat/java8/LambdaTest.java new file mode 100644 index 0000000..36634c4 --- /dev/null +++ b/src/test/java/scala/compat/java8/LambdaTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2012-2015 Typesafe Inc. + */ +package scala.compat.java8; + +import org.apache.commons.lang3.SerializationUtils; +import scala.runtime.*; + +import static junit.framework.Assert.assertEquals; +import static scala.compat.java8.JFunction.*; +import static scala.compat.java8.TestAPI.*; + +import org.junit.Test; + + +public class LambdaTest { + @Test + public void lambdaDemo() { + // Scala 2.12+ only: + //scala.Function1 f1 = (String s) -> s; + + // Not allowed with Scala 2.10 nor 2.11 + // "incompatible types: Function1 is not a functional interface" + // scala.Function1 f = (String s) -> s; + + // Function1 is not a functional interface because it has abstract + // methods in addition to apply, namely `compose` and `andThen` + // (which are implemented in Scala-derived subclasses with mixin + // inheritance), and the specialized variants of apply (also provided + // by scalac.) + + // That's a pity, but we can get pretty close with this library! + + // We have to tell javac to use `JFunction1` as the functional interface. + // Scala 2.12 does not have or need JFunction anymore. We provide it as a + // deprecated stub for backwards compatibility. Use `scala.Function1` for + // code that targets Scala 2.12+ exclusively. + JFunction1 f1 = (String s) -> s; + + // That's more or less equivalent to the old, anonymous class syntax: + new JFunction1() { + public String apply(String s) { return s; } + }; + + // You might have seen this form before: + new AbstractFunction1() { + public String apply(String s) { return s; } + }; + + // However, we can't use `AbstractFunction1` as a functional interface + // as it is a class. Further + + // F1 is a subclass of Function1: + scala.Function1 f2 = f1; + + // Factory methods in `JFunction` can reduce the verbosity a little: + // `func` is actually just an identity method; it only exists to + // trigger lambda creation using the `JFunction1` functional interface. + scala.Function1 f3 = func((String s) -> s); + + // Note that javac's type inference can infer the parameter type here, + // based on the acribed type of `f4`. + scala.Function1 f4 = func(s -> s); + + // f1.apply(""); + + // Specialized variants of the `apply` method are provided but implementing a specialized + // Scala function in this straight-forward way results in boxing and unboxing because the + // Java lambda operates on boxed types: + JFunction1 f5 = (i) -> -i; + assert(f5.apply(1) == -1); + assert(f5.apply$mcII$sp(1) == -1); + + // We provide `JFunction.funcSpecialized` and `JFunction.procSpecialized` methods to avoid + // boxing: + scala.Function1 f5b = funcSpecialized((int i) -> -i); + assert(f5b.apply(1) == -1); + assert(f5b.apply$mcII$sp(1) == -1); + + // as are `curried`, `tupled`, `compose`, `andThen`. + f3.compose(f3).andThen(f3).apply(""); + scala.Function2 f6 = func((s1, s2) -> join(s1, s2)); + assert(f6.curried().apply("1").apply("2").equals("12")); + + // Functions returning unit can use the `JProcedure1`, ... functional interfaces + // in order to convert a void lamdba return to Scala's Unit: + JProcedure1 f7b = s -> sideEffect(); + scala.Function1 f7c = f7b; + + // The easiest way to do this is via `JFunction.proc`, .... + // + // Note that the lambda has a return type of `void` if the last + // statement is a call to a `void` returning method, or if it is + // a `return`. + scala.Function1 f7 = proc(s -> sideEffect()); + scala.Function1 f8 = proc(s -> {s.toUpperCase(); return;}); + + // Function0 is also available + scala.Function0 f9 = func(() -> "42"); + assert(f9.apply().equals("42")); + + // You can go up to 22 (the highest arity function defined in the Scala standard library.) + assert(acceptFunction1(func(v1 -> v1.toUpperCase())).equals("1")); + acceptFunction1Unit(proc(v1 -> sideEffect())); + acceptFunction1Unit(proc(v1 -> {v1.toUpperCase(); return;})); + + assert(acceptFunction2(func((v1, v2) -> join(v1, v2))).equals("12")); + acceptFunction2Unit(proc((v1, v2) -> {v1.toUpperCase(); return;})); + + assert(acceptFunction3(func((v1, v2, v3) -> join(v1, v2, v3))).equals("123")); + acceptFunction3Unit(proc((v1, v2, v3) -> {v1.toUpperCase(); return;})); + + assert(acceptFunction22(func((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> join(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22))).equals("12345678910111213141516171819202122")); + acceptFunction22Unit( proc((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> {v1.toUpperCase(); return;})); + } + + @Test + public void isSerializable() { + JFunction0 f0 = () -> "foo"; + assertEquals("foo", SerializationUtils.clone(f0).apply()); + + JFunction1 f1 = (a) -> a.toUpperCase(); + assertEquals("FOO", SerializationUtils.clone(f1).apply("foo")); + + JFunction2 f2 = (a, b) -> a + b; + assertEquals("foobar", SerializationUtils.clone(f2).apply("foo", "bar")); + + JFunction3 f3 = (a, b, c) -> a + b + c; + assertEquals("foobarbaz", SerializationUtils.clone(f3).apply("foo", "bar", "baz")); + } + + private static scala.concurrent.Future futureExample( + scala.concurrent.Future future, scala.concurrent.ExecutionContext ec) { + return future.map(func(s -> s.toUpperCase()), ec).map(func(s -> s.length()), ec); + } + + private static void sideEffect() { + } + + private static String join(String... ss) { + String result = ""; + for (String s : ss) { + result = result + s; + } + return result; + } +} diff --git a/src/test/java/scala/compat/java8/SpecializedTest.scala b/src/test/java/scala/compat/java8/SpecializedTest.scala index 707ad82..e6b3239 100644 --- a/src/test/java/scala/compat/java8/SpecializedTest.scala +++ b/src/test/java/scala/compat/java8/SpecializedTest.scala @@ -4,7 +4,7 @@ package scala.compat.java8 import org.junit.Test -import scala.runtime.java8.SpecializedTestSupport.IntIdentity +import SpecializedTestSupport.IntIdentity class SpecializedTest { @Test def specializationWorks() { diff --git a/src/test/java/scala/runtime/java8/SpecializedTestSupport.java b/src/test/java/scala/compat/java8/SpecializedTestSupport.java similarity index 91% rename from src/test/java/scala/runtime/java8/SpecializedTestSupport.java rename to src/test/java/scala/compat/java8/SpecializedTestSupport.java index 2ba2833..c7e4386 100644 --- a/src/test/java/scala/runtime/java8/SpecializedTestSupport.java +++ b/src/test/java/scala/compat/java8/SpecializedTestSupport.java @@ -1,11 +1,12 @@ /* * Copyright (C) 2012-2015 Typesafe Inc. */ -package scala.runtime.java8; +package scala.compat.java8; import java.util.Arrays; import java.util.List; import org.junit.Assert; +import scala.runtime.java8.*; public class SpecializedTestSupport { public static class IntIdentity implements JFunction1$mcII$sp { diff --git a/src/test/java/scala/runtime/java8/LambdaTest.java b/src/test/java/scala/runtime/java8/LambdaTest.java deleted file mode 100644 index 9d67926..0000000 --- a/src/test/java/scala/runtime/java8/LambdaTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2012-2015 Typesafe Inc. - */ -package scala.runtime.java8; - -import org.apache.commons.lang3.SerializationUtils; -import scala.runtime.*; - -import static junit.framework.Assert.assertEquals; -import static scala.runtime.java8.TestAPI.*; - -import org.junit.Test; - - -public class LambdaTest { - /* - // This version is for Scala 2.12.0-RC1 and is not compatible with 2.11. It's commented out to allow cross-building. - @Test - public void lambdaDemo() { - scala.Function1 f1 = (String s) -> s; - - // That's more or less equivalent to the old, anonymous class syntax: - new scala.Function1() { - public String apply(String s) { return s; } - }; - - // You might have seen this form before: - new AbstractFunction1() { - public String apply(String s) { return s; } - }; - - // However, we can't use `AbstractFunction1` as a functional interface - // as it is a class. Further - - // F1 is a subclass of Function1: - scala.Function1 f2 = f1; - - scala.Function1 f3 = (String s) -> s; - scala.Function1 f4 = s -> s; - - // Specialized variants of the `apply` method are implenented in the - // functional interface - scala.Function1 f5 = (i -> -i); - assert(f5.apply(1) == -1); - assert(f5.apply$mcII$sp(1) == -1); - - // as are `curried`, `tupled`, `compose`, `andThen`. - f3.compose(f3).andThen(f3).apply(""); - scala.Function2 f6 = ((s1, s2) -> join(s1, s2)); - assert(f6.curried().apply("1").apply("2").equals("12")); - - // Functions returning unit must return BoxedUnit.UNIT explicitly. - // - // Note that the lambda has a return type of `void` if the last - // statement is a call to a `void` returning method, or if it is - // a `return`. - scala.Function1 f7 = (s -> { sideEffect(); return scala.runtime.BoxedUnit.UNIT; }); - scala.Function1 f8 = (s -> { s.toUpperCase(); return scala.runtime.BoxedUnit.UNIT; }); - - // Function0 is also available - scala.Function0 f9 = (() -> "42"); - assert(f9.apply().equals("42")); - - // You can go up to 22 (the highest arity function defined in the Scala standard library.) - assert(acceptFunction1((v1 -> v1.toUpperCase())).equals("1")); - acceptFunction1Unit((v1 -> {sideEffect(); return scala.runtime.BoxedUnit.UNIT;})); - acceptFunction1Unit((v1 -> {v1.toUpperCase(); return scala.runtime.BoxedUnit.UNIT;})); - - assert(acceptFunction2(((v1, v2) -> join(v1, v2))).equals("12")); - acceptFunction2Unit(((v1, v2) -> {v1.toUpperCase(); return scala.runtime.BoxedUnit.UNIT;})); - - assert(acceptFunction3(((v1, v2, v3) -> join(v1, v2, v3))).equals("123")); - acceptFunction3Unit(((v1, v2, v3) -> {v1.toUpperCase(); return scala.runtime.BoxedUnit.UNIT;})); - - assert(acceptFunction22(((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> join(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22))).equals("12345678910111213141516171819202122")); - acceptFunction22Unit( ((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> {v1.toUpperCase(); return scala.runtime.BoxedUnit.UNIT;})); - } - */ - - /* - // The JFunctions in 2.12.0-M4 are not Serializable anymore - @Test - public void isSerializable() { - scala.Function0 f0 = () -> "foo"; - assertEquals("foo", SerializationUtils.clone(f0).apply()); - - scala.Function1 f1 = (a) -> a.toUpperCase(); - assertEquals("FOO", SerializationUtils.clone(f1).apply("foo")); - - scala.Function2 f2 = (a, b) -> a + b; - assertEquals("foobar", SerializationUtils.clone(f2).apply("foo", "bar")); - - scala.Function3 f3 = (a, b, c) -> a + b + c; - assertEquals("foobarbaz", SerializationUtils.clone(f3).apply("foo", "bar", "baz")); - } - */ - - /* - // This version is for Scala 2.12.0-RC1 and is not compatible with 2.11. It's commented out to allow cross-building. - private static scala.concurrent.Future futureExample( - scala.concurrent.Future future, scala.concurrent.ExecutionContext ec) { - return future.map(s -> s.toUpperCase(), ec).map(s -> s.length(), ec); - } - */ - - private static void sideEffect() { - } - - private static String join(String... ss) { - String result = ""; - for (String s : ss) { - result = result + s; - } - return result; - } -}