diff --git a/.gitignore b/.gitignore index 5ffeca9..86fb982 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ target -.cache -.classpath -.project -.settings/ -.target/ -bin/ +/.cache +/.classpath +/.project +/.settings +/.target +/bin diff --git a/.travis.yml b/.travis.yml index e605151..7859a69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ script: - sbt ++$TRAVIS_SCALA_VERSION clean test publishLocal scala: - 2.10.4 - - 2.11.0-RC3 + - 2.11.4 jdk: - oraclejdk8 notifications: diff --git a/build.sbt b/build.sbt index 6f0af73..84a4bd3 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,9 @@ import com.typesafe.tools.mima.plugin.{MimaPlugin, MimaKeys} scalaModuleSettings -scalaVersion := "2.10.4" +scalaVersion := "2.11.4" -snapshotScalaBinaryVersion := "2.10.4" +snapshotScalaBinaryVersion := "2.11.4" // TODO this project can be cross build against 2.11 and 2.10, express that here. @@ -37,16 +37,23 @@ testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a") sourceGenerators in Compile <+= sourceManaged in Compile map { dir => def write(name: String, content: String) = { - val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java" + val f = dir / "scala" / "compat" / "java8" / s"${name}.java" IO.write(f, content) f } - Seq(write("JFunction", CodeGen.factory)) ++ (0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++ (1 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) + ( + 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) + ) } sourceGenerators in Test <+= sourceManaged in Test map { dir => def write(name: String, content: String) = { - val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java" + val f = dir / "scala" / "compat" / "java8" / s"${name}.java" IO.write(f, content) f } @@ -62,6 +69,10 @@ initialize := { sys.error("Java 8 or higher is required for this project.") } +val disableDocs = sys.props("nodocs") == "true" + +publishArtifact in packageDoc := !disableDocs + lazy val JavaDoc = config("genjavadoc") extend Compile sources in (Compile, doc) := { @@ -69,7 +80,7 @@ sources in (Compile, doc) := { orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449 } -inConfig(JavaDoc)(Defaults.configSettings) ++ Seq( +inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq( packageDoc in Compile <<= packageDoc in JavaDoc, sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) => val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java")) @@ -77,9 +88,9 @@ inConfig(JavaDoc)(Defaults.configSettings) ++ Seq( }, javacOptions in JavaDoc := Seq(), artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"), - libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" %% "genjavadoc-plugin" % "0.5" cross CrossVersion.full), + libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full), scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java")) -) +)) initialCommands := """|import scala.concurrent._ diff --git a/project/CodeGen.scala b/project/CodeGen.scala index 662b9e6..ffd1e4c 100644 --- a/project/CodeGen.scala +++ b/project/CodeGen.scala @@ -2,7 +2,9 @@ * Copyright (C) 2012-2014 Typesafe Inc. */ -sealed abstract class Type(val code: Char, val prim: String, val ref: String) +sealed abstract class Type(val code: Char, val prim: String, val ref: String) { + def boxed: String = ref +} object Type { case object Boolean extends Type('Z', "boolean", "Boolean") case object Byte extends Type('B', "byte", "Byte") @@ -12,7 +14,7 @@ object Type { case object Float extends Type('F', "float", "Float") case object Double extends Type('D', "double", "Double") case object Long extends Type('J', "long", "Long") - case object Void extends Type('V', "void", "Void") + case object Void extends Type('V', "void", "BoxedUnit") case object Object extends Type('L', "Object", "Object") } @@ -95,33 +97,106 @@ object CodeGen { val vparams = csv(n => s"T$n t$n") val vparamRefs = csv(n => s"t$n") val parent = "JFunction" + n - s""" - |$copyright - | - |$packaging - | - |import scala.runtime.BoxedUnit; - | - |@FunctionalInterface - |public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> { - | default void $initName() { - | } - | - | void applyVoid($vparams); - | - | default BoxedUnit apply($vparams) { - | applyVoid($vparamRefs); - | return BoxedUnit.UNIT; - | } - |} - |""".stripMargin + if (n == 0) + s""" + |$copyright + | + |$packaging + | + |import scala.runtime.BoxedUnit; + | + |@FunctionalInterface + |public interface JProcedure0 extends ${parent} { + | default void $initName() { + | } + | + | void applyVoid(); + | + | default BoxedUnit apply() { + | applyVoid(); + | return BoxedUnit.UNIT; + | } + |} + |""".stripMargin + else + s""" + |$copyright + | + |$packaging + | + |import scala.runtime.BoxedUnit; + | + |@FunctionalInterface + |public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> { + | default void $initName() { + | } + | + | void applyVoid($vparams); + | + | default BoxedUnit apply($vparams) { + | applyVoid($vparamRefs); + | return BoxedUnit.UNIT; + | } + |} + |""".stripMargin } def factory: String = { - s""" - |public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; } - |public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; } - |""".stripMargin.trim + val specializedFactories = this.n match { + case 0 => + val tparamNames = function0Spec.map(_._1) + + def specFactory(tps: List[Type]) = { + val List(r) = tps + val suffix = specializedSuffix(tparamNames, tps) + val name = (if (r == Type.Void) "proc" else "func") + "Specialized" + s"public static scala.Function0<${r.ref}> $name(JFunction0$suffix f) { return f; }" + } + + for { + variantTypes <- crossProduct(function0Spec.map(_._2)) + } yield specFactory(variantTypes) + case 1 => + val tparamNames = function1Spec.map(_._1) + + def specFactory(tps: List[Type]) = { + val List(t, r) = tps + val suffix = specializedSuffix(tparamNames, tps) + val name = (if (r == Type.Void) "proc" else "func") + "Specialized" + s"public static scala.Function1<${t.ref}, ${r.ref}> $name(JFunction1$suffix f) { return f; }" + } + + for { + variantTypes <- crossProduct(function1Spec.map(_._2)) + } yield specFactory(variantTypes) + case 2 => + val tparamNames = function2Spec.map(_._1) + + def specFactory(tps: List[Type]) = { + val List(t1, t2, r) = tps + val suffix = specializedSuffix(tparamNames, tps) + val name = (if (r == Type.Void) "proc" else "func") + "Specialized" + s"public static scala.Function2<${t1.ref}, ${t2.ref}, ${r.ref}> $name(JFunction2$suffix f) { return f; }" + } + + for { + variantTypes <- crossProduct(function2Spec.map(_._2)) + } yield specFactory(variantTypes) + case _ => + Nil + } + if (n == 0) + s""" + |public static scala.Function$n func(JFunction$n f) { return f; } + |public static scala.Function$n proc(JProcedure$n p) { return p; } + |${specializedFactories.mkString("\n")} + |""".stripMargin.trim + else + s""" + |public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; } + |public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; } + |${specializedFactories.mkString("\n")} + |""".stripMargin.trim } def accept: String = { @@ -138,6 +213,69 @@ object CodeGen { } } + def f0Specialized(tps: List[Type]): (String, String) = { + val tparamNames = function0Spec.map(_._1) + val suffix = specializedSuffix(tparamNames, tps) + val List(r) = tps + val applyMethodBody = if (r == Type.Void) s"apply$suffix(); return scala.runtime.BoxedUnit.UNIT;" + else s"return (${r.ref}) apply$suffix();" + val code = s""" + |$copyright + | + |$packaging + | + |@FunctionalInterface + |public interface JFunction0$suffix extends JFunction0 { + | abstract ${r.prim} apply$suffix(); + | + | default Object apply() { $applyMethodBody } + |} + |""".stripMargin + (s"JFunction0$suffix", code) + } + + + def f1Specialized(tps: List[Type]): (String, String) = { + val tparamNames = function1Spec.map(_._1) + val suffix = specializedSuffix(tparamNames, tps) + val List(t, r) = tps + val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t.ref}) t); return scala.runtime.BoxedUnit.UNIT;" + else s"return (${r.ref}) apply$suffix((${t.ref}) t);" + val code = s""" + |$copyright + | + |$packaging + | + |@FunctionalInterface + |public interface JFunction1$suffix extends JFunction1 { + | abstract ${r.prim} apply$suffix(${t.prim} v1); + | + | default Object apply(Object t) { $applyMethodBody } + |} + |""".stripMargin + (s"JFunction1$suffix", code) + } + + def f2Specialized(tps: List[Type]): (String, String) = { + val tparamNames = function2Spec.map(_._1) + val suffix = specializedSuffix(tparamNames, tps) + val List(t1, t2, r) = tps + val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t1.ref}) v1, (${t2.ref}) v2); return scala.runtime.BoxedUnit.UNIT;" + else s"return (${r.ref}) apply$suffix((${t1.ref}) v1, (${t2.ref}) v2);" + val code = s""" + |$copyright + | + |$packaging + | + |@FunctionalInterface + |public interface JFunction2$suffix extends JFunction2 { + | abstract ${r.prim} apply$suffix(${t1.prim} v1, ${t2.prim} v2); + | + | default Object apply(Object v1, Object v2) { $applyMethodBody } + |} + |""".stripMargin + (s"JFunction2$suffix", code) + } private val initName = "$init$" private val function1ImplClass = "scala.Function1$class" @@ -236,15 +374,17 @@ object CodeGen { indent(List(apply, curried, tupled).mkString("\n\n")) } + def specializedSuffix(tparamNames: List[String], tps: List[Type]): String = { + val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code + val code = sorted.map(_.code).mkString + "$mc" + code + "$sp" + } + private def specialized(name: String, tps: List[(String, List[Type])])(f: (String, List[Type]) => String): String = { val tparamNames = tps.map(_._1) - def code(tps: List[Type]) = { - val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code - sorted.map(_.code).mkString - } val ms = for { variantTypes <- crossProduct(tps.map(_._2)) - specName = name + "$mc" + code(variantTypes) + "$sp" + specName = name + specializedSuffix(tparamNames, variantTypes) } yield f(specName, variantTypes) ms.mkString("\n") } @@ -267,10 +407,29 @@ object CodeGen { List(header, specializedVariants, trailer).mkString } + def specializedF0: List[(String, String)] = { + val tparamNames = function0Spec.map(_._1) + for { + variantTypes <- crossProduct(function0Spec.map(_._2)) + } yield f0Specialized(variantTypes) + } + def specializedF1: List[(String, String)] = { + val tparamNames = function1Spec.map(_._1) + for { + variantTypes <- crossProduct(function1Spec.map(_._2)) + } yield f1Specialized(variantTypes) + } + def specializedF2: List[(String, String)] = { + val tparamNames = function2Spec.map(_._1) + for { + variantTypes <- crossProduct(function2Spec.map(_._2)) + } yield f2Specialized(variantTypes) + } + def pN(n: Int) = arity(n).pN def factory: String = { - val ms = (1 to 22).map(n => arity(n).factory).mkString("\n") + val ms = (0 to 22).map(n => arity(n).factory).mkString("\n") s""" |$copyright | @@ -280,7 +439,6 @@ object CodeGen { | |public final class JFunction { | private JFunction() {} - | public static scala.Function0 func(JFunction0 f) { return f; } |${indent(ms)} |} | diff --git a/project/build.properties b/project/build.properties index 6b39007..df58110 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.2-M2 \ No newline at end of file +sbt.version=0.13.6 \ No newline at end of file diff --git a/src/test/java/scala/compat/java8/LambdaTest.java b/src/test/java/scala/compat/java8/LambdaTest.java index e727ed1..adf9971 100644 --- a/src/test/java/scala/compat/java8/LambdaTest.java +++ b/src/test/java/scala/compat/java8/LambdaTest.java @@ -11,7 +11,7 @@ public class LambdaTest { @Test - public static void lambdaDemo() { + public void lambdaDemo() { // Not allowed with Scala 2.10 nor 2.11 // "incompatible types: Function1 is not a functional interface" // scala.Function1 f = (String s) -> s; diff --git a/src/test/java/scala/compat/java8/SpecializedFactoryTest.java b/src/test/java/scala/compat/java8/SpecializedFactoryTest.java new file mode 100644 index 0000000..192ef82 --- /dev/null +++ b/src/test/java/scala/compat/java8/SpecializedFactoryTest.java @@ -0,0 +1,17 @@ +package scala.compat.java8; + +import org.junit.Test; +import scala.runtime.BoxedUnit; + +public class SpecializedFactoryTest { + @Test public void intIntFunction() { + scala.Function1 f1 = JFunction.funcSpecialized((int x) -> x); + assert(f1 instanceof JFunction1$mcII$sp); + + scala.Function1 f2 = JFunction.procSpecialized((int x) -> System.out.print("")); + assert(f2 instanceof JFunction1$mcVI$sp); + + scala.Function0 f3 = JFunction.procSpecialized(() -> System.out.print("")); + assert (f3 instanceof JFunction0$mcV$sp); + } +} diff --git a/src/test/java/scala/compat/java8/SpecializedTest.scala b/src/test/java/scala/compat/java8/SpecializedTest.scala new file mode 100644 index 0000000..38998d3 --- /dev/null +++ b/src/test/java/scala/compat/java8/SpecializedTest.scala @@ -0,0 +1,11 @@ +package scala.compat.java8 + +import org.junit.Test +import scala.compat.java8.SpecializedTestSupport.IntIdentity + +class SpecializedTest { + @Test def specializationWorks() { + val intIdentity: (Int => Int) = new IntIdentity().asInstanceOf[Int => Int] + intIdentity(24) // this function checks that it was called via the specialized apply variant. + } +} diff --git a/src/test/java/scala/compat/java8/SpecializedTestSupport.java b/src/test/java/scala/compat/java8/SpecializedTestSupport.java new file mode 100644 index 0000000..201e632 --- /dev/null +++ b/src/test/java/scala/compat/java8/SpecializedTestSupport.java @@ -0,0 +1,16 @@ +package scala.compat.java8; + +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; + +public class SpecializedTestSupport { + public static class IntIdentity implements JFunction1$mcII$sp { + public int apply$mcII$sp(int i) { + List stackTrace = Arrays.asList(Thread.currentThread().getStackTrace()); + long count = stackTrace.stream().filter(x -> x.getMethodName().equals("apply")).count(); + Assert.assertEquals("the unspecialized apply method should not have been called", 0, count); + return i; + } + } +}