Skip to content
Permalink
Browse files

Macro generated case class Tuple Setter and Converter

  • Loading branch information...
Jonathan Coveney
Jonathan Coveney committed Sep 5, 2014
1 parent 15da307 commit aacb39246989dd1754834ab2b1dabd3d9dc5606e
@@ -44,6 +44,9 @@ object ScaldingBuild extends Build {
"Cloudera" at "https://repository.cloudera.com/artifactory/cloudera-repos/"
),

//TODO turn on JUST for scaldingMacro
//addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full),

printDependencyClasspath := {
val cp = (dependencyClasspath in Compile).value
cp.foreach(f => println(s"${f.metadata.get(moduleID.key)} => ${f.data}"))
@@ -161,6 +164,7 @@ object ScaldingBuild extends Build {
scaldingJson,
scaldingJdbc,
scaldingHadoopTest,
scaldingMacro,
maple
)

@@ -340,6 +344,15 @@ object ScaldingBuild extends Build {
}
).dependsOn(scaldingCore)

lazy val scaldingMacro = module("macro").settings(
libraryDependencies <++= (scalaVersion) { scalaVersion => Seq(
"org.scala-lang" % "scala-library" % scalaVersion,
"org.scala-lang" % "scala-reflect" % scalaVersion
) ++ (if (scalaVersion.startsWith("2.10")) Seq("org.scalamacros" %% "quasiquotes" % "2.0.1") else Seq())
},
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)
).dependsOn(scaldingCore)

// This one uses a different naming convention
lazy val maple = Project(
id = "maple",
@@ -0,0 +1,14 @@
package com.twitter.scalding.macroimpl

import scala.language.experimental.macros

import com.twitter.scalding._

/**
* This is an object which can be imported to make the macro functionality defined in this package
* available implicitly.
*/
object Implicits {
implicit def materializeCaseClassTupleSetter[T]: TupleSetter[T] = macro Macro.caseClassTupleSetterImpl[T]
implicit def materializeCaseClassTupleConverter[T]: TupleConverter[T] = macro Macro.caseClassTupleConverterImpl[T]
}
@@ -0,0 +1,91 @@
package com.twitter.scalding.macroimpl

import scala.language.experimental.macros
import scala.reflect.macros.Context
import scala.reflect.runtime.universe._
import scala.util.{ Try => BasicTry }

import cascading.tuple.{ Tuple => CTuple, TupleEntry }

import com.twitter.scalding._

object Macro {
def caseClassTupleSetter[T]: TupleSetter[T] = macro caseClassTupleSetterImpl[T]
def caseClassTupleSetterImpl[T](c: Context)(implicit tag: c.WeakTypeTag[T]): c.Expr[TupleSetter[T]] = {
import c.universe._
if (!isCaseClass[T](c)) {
throw new IllegalArgumentException("Type paramter of caseClassTupleSetter must be a case class")
}
val params = tag.tpe.declaration(nme.CONSTRUCTOR).asMethod.paramss.head
val set =
params
.zipWithIndex
.map {
case (sym, idx) =>
val name = newTermName(sym.name.toString)
sym.typeSignature match {
case tpe if tpe =:= typeOf[String] => q"""tup.setString(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Boolean] => q"""tup.setBoolean(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Short] => q"""tup.setShort(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Int] => q"""tup.setInteger(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Long] => q"""tup.setLong(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Float] => q"""tup.setFloat(${idx}, t.$name)"""
case tpe if tpe =:= typeOf[Double] => q"""tup.setDouble(${idx}, t.$name)"""
case _ => q"""tup.set(${idx}, t.$name)"""
}
}
.foldLeft(q"") { (cum, next) => q"""$cum;$next""" }

val res = q"""
new TupleSetter[${tag.tpe}] {
override def apply(t: ${tag.tpe}): CTuple = {
val tup = CTuple.size(${params.size})
$set
tup
}
override def arity = ${params.size}
}
"""
c.Expr[TupleSetter[T]](res)
}

def caseClassTupleConverter[T]: TupleConverter[T] = macro caseClassTupleConverterImpl[T]
def caseClassTupleConverterImpl[T](c: Context)(implicit tag: c.WeakTypeTag[T]): c.Expr[TupleConverter[T]] = {
import c.universe._
if (!isCaseClass[T](c)) {
throw new IllegalArgumentException("Type paramter of caseClassTupleConverter must be a case class")
}
val params = tag.tpe.declaration(nme.CONSTRUCTOR).asMethod.paramss.head
val gets =
params
.zipWithIndex
.map {
case (sym, idx) =>
val name = newTermName(sym.name.toString)
sym.typeSignature match {
case tpe if tpe =:= typeOf[String] => q"""tup.getString(${idx})"""
case tpe if tpe =:= typeOf[Boolean] => q"""tup.getBoolean(${idx})"""
case tpe if tpe =:= typeOf[Short] => q"""tup.getShort(${idx})"""
case tpe if tpe =:= typeOf[Int] => q"""tup.getInteger(${idx})"""
case tpe if tpe =:= typeOf[Long] => q"""tup.getLong(${idx})"""
case tpe if tpe =:= typeOf[Float] => q"""tup.getFloat(${idx})"""
case tpe if tpe =:= typeOf[Double] => q"""tup.getDouble(${idx})"""
case tpe => q"""tup.getObject(${idx}).asInstanceOf[${tpe}]"""
}
}

val res = q"""
new TupleConverter[${tag.tpe}] {
override def apply(t: TupleEntry): ${tag.tpe} = {
val tup = t.getTuple()
${tag.tpe.typeSymbol.companionSymbol}(..$gets)
}
override def arity = ${params.size}
}
"""
c.Expr[TupleConverter[T]](res)
}

def isCaseClass[T](c: Context)(implicit tag: c.WeakTypeTag[T]): Boolean =
BasicTry { tag.tpe.typeSymbol.asInstanceOf[ClassSymbol].isCaseClass }.toOption.getOrElse(false)
}

0 comments on commit aacb392

Please sign in to comment.
You can’t perform that action at this time.