Permalink
Browse files

[scala] Refactor Scala RowList with self-types, for better inference …

…and implicits (operation on ScalaRowList returning ScalaRowList, not core RowList which would be converted again on next operation).

```scala
// Before
val l1a: RowList1[String] = rowList1(classOf[String])
val l1b: RowList1[String] = l1a :+ "B" // convert on :+ ...
// but core type RowList1 is returned

// Now
val l1a = rowList1(classOf[String])
val l1b: ScalaRowList1[A] = l1a :+ "B" // convert on :+ ...
// and keep it as pimped Scala type
val l1c: ScalaRowList1[A] = l1b :+ "C" // no conversion required
```

Typesafe append operations are generated along with generated row lists, so append-row operation and related Row implicits are no longer required.

```scala
// Before
rowList3 :+ row3(a, b, c) // no supported due to inference issues

// Now
rowList3 :+ (a, b, c)
```
  • Loading branch information...
cchantep
cchantep committed Jan 8, 2014
1 parent b4d944a commit dd64b526794625a65c1ca06e12492e2a4f692bd9
View
@@ -22,74 +22,101 @@ trait Scala {
generateRowClasses(base, managed)
}),
pomExtra := (
<url>https://github.com/cchantep/acolyte/</url>
<licenses>
<license>
<name>GNU Lesser General Public License, Version 2.1</name>
<url>
https://raw.github.com/cchantep/acolyte/master/LICENSE.txt
</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:cchantep/acolyte.git</connection>
<developerConnection>
scm:git:git@github.com:cchantep/acolyte.git
</developerConnection>
<url>git@github.com:cchantep/acolyte.git</url>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/cchantep/acolyte/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/cchantep/acolyte</url>
</ciManagement>
<developers>
<developer>
<id>cchantep</id>
<name>Cedric Chantepie</name>
</developer>
</developers>)).dependsOn(core)
<url>https://github.com/cchantep/acolyte/</url>
<licenses>
<license>
<name>GNU Lesser General Public License, Version 2.1</name>
<url>
https://raw.github.com/cchantep/acolyte/master/LICENSE.txt
</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:cchantep/acolyte.git</connection>
<developerConnection>
scm:git:git@github.com:cchantep/acolyte.git
</developerConnection>
<url>git@github.com:cchantep/acolyte.git</url>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/cchantep/acolyte/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/cchantep/acolyte</url>
</ciManagement>
<developers>
<developer>
<id>cchantep</id>
<name>Cedric Chantepie</name>
</developer>
</developers>)).dependsOn(core)
// Source generator
private def generateRowClasses(base: File, managed: File): Seq[File] = {
val letterAZ = 'A' to 'Z'
val letter = letterAZ.map(_.toString) ++: letterAZ.map(l "A" + l)
val lim = letter.size
val listTmpl = base / "src" / "main" / "templates" / "RowList.tmpl"
val rowLists: Seq[java.io.File] = for (n 1 to lim) yield {
val f = managed / "acolyte" / "ScalaRowList%d.scala".format(n)
val tc = for (i 0 until n) yield letter(i)
val cv = for (i 0 until n) yield {
"val c%d: Class[%s]".format(i, letter(i))
}
val ca = for (i 0 until n) yield "c%d: Class[%s]".format(i, letter(i))
val cc = for (i 0 until n) yield "c%d".format(i)
val ac = for (i 0 until n) yield "list.add(c%d)".format(i)
val vs = for (i 0 until n) yield letter(i).toLowerCase
val va = for (i 0 until n) yield {
"%s: %s".format(letter(i).toLowerCase, letter(i))
}
IO.writer[java.io.File](f, "", IO.defaultCharset, false) { w
// Generate by substitution on each line of template
IO.reader[Unit](listTmpl) { r
IO.foreachLine(r) { l
w.append(l.replaceAll("#N#", n.toString).
replaceAll("#TC#", tc.mkString(", ")).
replaceAll("#CV#", cv.mkString(", ")).
replaceAll("#CC#", cc.mkString(", ")).
replaceAll("#AC#", ac.mkString("\r\n ")).
replaceAll("#VS#", vs.mkString(", ")).
replaceAll("#VA#", va.mkString(", ")).
replaceAll("#CA#", ca.mkString(", "))).
append("\n")
}
}
f
}
}
val rlf = managed / "acolyte" / "RowLists.scala"
IO.writer[java.io.File](rlf, "", IO.defaultCharset, false) { w
val letter = ('A' to 'Z').map(_.toString) ++: ('A' to 'Z').map(l "A" + l)
val lim = letter.size
val conv = Nil ++: Seq("implicit def RowList1AsScala[A](l: RowList1[A]): ScalaRowList1[A] = new ScalaRowList1[A](l)") ++: (for (n 2 to lim) yield {
val conv = Nil ++: (for (n 1 to lim) yield {
val gp = (for (i 0 until n) yield letter(i)).mkString(", ")
"""implicit def RowList%dAsScala[%s](l: RowList%d[%s]): ScalaRowList[RowList%d[%s], Row%d[%s]] = new ScalaRowList[RowList%d[%s], Row%d[%s]](l)""".format(n, gp, n, gp, n, gp, n, gp, n, gp, n, gp)
val ca = (for (i 0 until n) yield "l.c%d".format(i)).mkString(", ")
"implicit def RowList%dAsScala[%s](l: RowList%d.Impl[%s]): ScalaRowList%d[%s] = new ScalaRowList%d[%s](%s, l.rows, l.colNames)".format(n, gp, n, gp, n, gp, n, gp, ca)
})
val tmpl = base / "src" / "main" / "templates" / "RowLists.tmpl"
IO.reader[Unit](tmpl) { r
IO.foreachLine(r) { l
w.append(l.replace("#SRL#", conv.mkString("\r\n "))).
append("\r\n")
}
}
rlf
}
val rf = managed / "acolyte" / "Rows.scala"
IO.writer[java.io.File](rf, "", IO.defaultCharset, false) { w
val letter = 'A' to 'Z' dropRight 4
val conv = for (n 1 to 22) yield {
val gp = (for (i 0 until n) yield letter(i)).mkString(", ")
val ps = for (i 1 to n) yield "p._%d".format(i)
"""implicit def Product%dAsRow[%s](p: Product%d[%s]): Row%d[%s] = Rows.row%d(%s)""".format(n, gp, n, gp, n, gp, n, ps.mkString(", "))
}
val tmpl = base / "src" / "main" / "templates" / "Rows.tmpl"
IO.reader[Unit](tmpl) { r
IO.foreachLine(r) { l
w.append(l.replace("#SR#", conv.mkString("\r\n "))).
append("\r\n")
}
}
rf
}
Seq(rlf, rf)
rowLists :+ rlf
}
}
@@ -101,18 +101,7 @@ object Acolyte {
* import acolyte.Implicits._
* }}}
*/
object Implicits
extends ScalaRowLists with ScalaRows with CompositeHandlerImplicits {
/**
* Pimps result row.
*
* {{{
* row.list // Scala list equivalent to .cells
* }}}
*/
implicit def ResultRowAsScala[R <: Row](r: R): ScalaResultRow =
new ScalaResultRow(r)
object Implicits extends ScalaRowLists with CompositeHandlerImplicits {
/**
* Converts tuple to column definition.
@@ -258,6 +247,13 @@ trait CompositeHandlerImplicits { srl: ScalaRowLists ⇒
*/
implicit def RowListAsResult[R <: RowList[_]](r: R): QueryResult = r.asResult
/**
* Allows to directly use string as query result.
*
* {{{
* val qr: QueryResult = "str"
* }}}
*/
implicit def StringAsResult(v: String): QueryResult =
(RowLists.stringList :+ v).asResult
@@ -294,95 +290,33 @@ trait CompositeHandlerImplicits { srl: ScalaRowLists ⇒
implicit def SqlDateAsTimestampResult(v: java.sql.Timestamp): QueryResult =
(RowLists.timestampList :+ v).asResult
/**
* Converts a single value as row, so that it can be added to row list.
*
* {{{
* stringList :+ "singleVal" // SingleValueRow("singleVal")
* }}}
*/
implicit def SingleValueRow[A, B](value: A)(implicit f: A B): Row.Row1[B] =
Rows.row1[B](f(value))
}
private object ScalaCompositeHandler {
def empty = new ScalaCompositeHandler(null, null, null)
}
final class ScalaResultRow(r: Row) extends Row {
lazy val cells = r.cells
lazy val list: List[Any] =
JavaConversions.iterableAsScalaIterable(cells).foldLeft(List[Any]()) {
(l, v) l :+ v
}
}
// TODO: Move ScalaRowListX to separate file
// TODO: Gather common :+(RowN[_] and withLabels
import acolyte.Row.Row1
/**
* Pimped row list.
* Convertions from Java datatypes to Scala.
*/
final class ScalaRowList1[A](l: RowList1[A]) {
/**
* Symbolic alias for `append` operation with null as single value
* (inferred as expected row type).
* !! If value is not null, usual `:+` is called.
*
*/
def :+(n: Null)(implicit f: A Row.Row1[A]): RowList1[A] =
l.append(f(null.asInstanceOf[A])).asInstanceOf[RowList1[A]]
@deprecated("Direct manipulation of row is no longer required", "1.0.12")
object JavaConverters {
/**
* Symbolic alias for `append` operation.
*
* {{{
* rowList :+ row
* }}}
*/
def :+(row: Row1[A]): RowList1[A] = l.append(row).asInstanceOf[RowList1[A]]
/**
* Defines column label(s) per position(s) (> 0).
*
* {{{
* rowList.withLabels(1 -> "label1", 2 -> "label2")
* }}}
*/
def withLabels(labels: (Int, String)*): RowList1[A] =
labels.foldLeft(l) { (l, t)
l.withLabel(t._1, t._2).asInstanceOf[RowList1[A]]
}
}
/**
* Pimped row list.
*/
final class ScalaRowList[L <: RowList[R], R <: Row](l: L) {
/**
* Symbolic alias for `append` operation.
* Pimps result row.
*
* {{{
* rowList :+ row
* row.list // Scala list equivalent to .cells
* }}}
*/
def :+(row: R): L = l.append(row).asInstanceOf[L]
// TODO: Remove asInstanceOf (implicit?)
implicit def rowAsScala[R <: Row](r: R): ScalaRow = new ScalaRow(r)
/**
* Defines column label(s) per position(s) (> 0).
*
* {{{
* rowList.withLabels(1 -> "label1", 2 -> "label2")
* }}}
*/
def withLabels(labels: (Int, String)*): L =
labels.foldLeft(l) { (l, t) l.withLabel(t._1, t._2).asInstanceOf[L] }
final class ScalaRow(r: Row) extends Row {
lazy val cells = r.cells
lazy val list: List[Any] =
JavaConversions.iterableAsScalaIterable(cells).foldLeft(List[Any]()) {
(l, v) l :+ v
}
}
}
@@ -0,0 +1,45 @@
// -*- mode: scala -*-
package acolyte
import java.util.{ ArrayList, List JList, Map JMap }
/** Scala implementation of RowList#N#. */
private[acolyte] final class ScalaRowList#N#[#TC#](#CV#, rows: JList[Row#N#[#TC#]], colNames: JMap[String, Integer]) extends RowList#N#[#TC#, ScalaRowList#N#[#TC#]] {
lazy val factory: RowList#N#.Factory[#TC#, ScalaRowList#N#[#TC#]] =
new RowList#N#.Factory[#TC#, ScalaRowList#N#[#TC#]]() {
def rowList(#CA#, rows: JList[Row#N#[#TC#]], colNames: JMap[String, Integer]): ScalaRowList#N#[#TC#] = new ScalaRowList#N#[#TC#](#CC#, rows, colNames)
}
/** Rows */
lazy val getRows: JList[Row#N#[#TC#]] = rows
/** Column labels */
lazy val getColumnLabels: JMap[String, Integer] = colNames
/** Column classes */
lazy val getColumnClasses: JList[Class[_]] = {
val list: JList[Class[_]] = new ArrayList[Class[_]]()
#AC#
list
}
/**
* Symbolic alias for `append` operation.
*
* {{{
* rowList :+ (#VS#)
* }}}
*/
def :+(#VA#): ScalaRowList#N#[#TC#] = append(#VS#)
/**
* Defines column label(s) per position(s) (> 0).
*
* {{{
* rowList.withLabels(1 -> "label1", 2 -> "label2")
* }}}
*/
def withLabels(labels: (Int, String)*): ScalaRowList#N#[#TC#] =
labels.foldLeft(this) { (l, t) l.withLabel(t._1, t._2) }
}
@@ -1,10 +0,0 @@
// -*- mode: scala -*-
package acolyte
import scala.language.implicitConversions
import Row.Row1
private[acolyte] trait ScalaRows {
#SR#
}
@@ -1,6 +1,7 @@
package acolyte
import acolyte.RowLists.stringList
import acolyte.JavaConverters.rowAsScala
import acolyte.Implicits._
object RowSpec extends org.specs2.mutable.Specification {
@@ -18,14 +19,13 @@ object RowSpec extends org.specs2.mutable.Specification {
"Null single value" should {
lazy val list = (stringList :+ null)
lazy val expectedRow = new Row.Row1[String](null)
lazy val expectedRow = Rows.row1[String](null)
"be inferred on :+" in {
val rows = list.getRows
val rs = list.resultSet
rs.next()
println(s"#rows=${list.getRows}")
(rows.size aka "row count" mustEqual 1).
and(rows.get(0) aka "row" mustEqual expectedRow).
and(rs.getObject(1) aka "single column" must beNull)
Oops, something went wrong.

0 comments on commit dd64b52

Please sign in to comment.