in scalding-core/src/main/scala/com/twitter/scalding/macros/impl/TupleConverterImpl.scala [28:135]
def caseClassTupleConverterImpl[T](c: Context)(implicit T: c.WeakTypeTag[T]): c.Expr[TupleConverter[T]] =
caseClassTupleConverterCommonImpl(c, false)
def caseClassTupleConverterWithUnknownImpl[T](c: Context)(implicit
T: c.WeakTypeTag[T]
): c.Expr[TupleConverter[T]] =
caseClassTupleConverterCommonImpl(c, true)
def caseClassTupleConverterCommonImpl[T](c: Context, allowUnknownTypes: Boolean)(implicit
T: c.WeakTypeTag[T]
): c.Expr[TupleConverter[T]] = {
import c.universe._
import TypeDescriptorProviderImpl.evidentColumn
def membersOf(outerTpe: Type): Vector[Type] =
outerTpe.declarations
.collect { case m: MethodSymbol if m.isCaseAccessor => m }
.map { accessorMethod =>
accessorMethod.returnType.asSeenFrom(outerTpe, outerTpe.typeSymbol.asClass)
}
.toVector
sealed trait ConverterBuilder {
def columns: Int
def applyTree(offset: Int): Tree
}
final case class PrimitiveBuilder(primitiveGetter: Int => Tree) extends ConverterBuilder {
def columns = 1
def applyTree(offset: Int) = primitiveGetter(offset)
}
final case class OptionBuilder(evidentCol: Int, of: ConverterBuilder) extends ConverterBuilder {
def columns = of.columns
def applyTree(offset: Int) = {
val testIdx = offset + evidentCol
q"""if (t.getObject($testIdx) == null) None
else Some(${of.applyTree(offset)})"""
}
}
final case class CaseClassBuilder(tpe: Type, members: Vector[ConverterBuilder]) extends ConverterBuilder {
val columns = members.map(_.columns).sum
def applyTree(offset: Int) = {
val trees = members
.scanLeft((offset, Option.empty[Tree])) { case ((o, _), cb) =>
val nextOffset = o + cb.columns
(nextOffset, Some(cb.applyTree(o)))
}
.collect { case (_, Some(tree)) => tree }
q"${tpe.typeSymbol.companionSymbol}(..$trees)"
}
}
def matchField(outerTpe: Type): ConverterBuilder =
outerTpe match {
/*
* First we handle primitives, which never recurse
*/
case tpe if tpe =:= typeOf[String] && allowUnknownTypes =>
PrimitiveBuilder(idx => q"""t.getString($idx)""")
case tpe if tpe =:= typeOf[String] =>
// In this case, null is identical to empty, and we always return non-null
PrimitiveBuilder(idx => q"""{val s = t.getString($idx); if (s == null) "" else s}""")
case tpe if tpe =:= typeOf[Boolean] =>
PrimitiveBuilder(idx => q"""t.getBoolean($idx)""")
case tpe if tpe =:= typeOf[Short] =>
PrimitiveBuilder(idx => q"""t.getShort($idx)""")
case tpe if tpe =:= typeOf[Int] =>
PrimitiveBuilder(idx => q"""t.getInteger($idx)""")
case tpe if tpe =:= typeOf[Long] =>
PrimitiveBuilder(idx => q"""t.getLong($idx)""")
case tpe if tpe =:= typeOf[Float] =>
PrimitiveBuilder(idx => q"""t.getFloat($idx)""")
case tpe if tpe =:= typeOf[Double] =>
PrimitiveBuilder(idx => q"""t.getDouble($idx)""")
case tpe if tpe.erasure =:= typeOf[Option[Any]] =>
val innerType = tpe.asInstanceOf[TypeRefApi].args.head
evidentColumn(c, allowUnknownTypes)(innerType) match {
case None => // there is no evident column, not supported.
c.abort(c.enclosingPosition, s"$tpe has unsupported nesting of Options at: $innerType")
case Some(ev) => // we can recurse here
OptionBuilder(ev, matchField(innerType))
}
case tpe if tpe.typeSymbol.isClass && tpe.typeSymbol.asClass.isCaseClass =>
CaseClassBuilder(tpe, membersOf(tpe).map(matchField))
case tpe if allowUnknownTypes =>
PrimitiveBuilder(idx => q"""t.getObject($idx).asInstanceOf[$tpe]""")
case tpe =>
c.abort(
c.enclosingPosition,
s"${T.tpe} is not pure primitives, Option of a primitive, nested case classes when looking at type $tpe"
)
}
val builder = matchField(T.tpe)
if (builder.columns == 0)
c.abort(c.enclosingPosition, "Didn't consume any elements in the tuple, possibly empty case class?")
val res = q"""
new _root_.com.twitter.scalding.TupleConverter[$T] with _root_.com.twitter.bijection.macros.MacroGenerated {
override def apply(t: _root_.cascading.tuple.TupleEntry): $T = {
${builder.applyTree(0)}
}
override val arity: _root_.scala.Int = ${builder.columns}
}
"""
c.Expr[TupleConverter[T]](res)
}