Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ lazy val indexchecker = (project in file("indexchecker")).settings(defaultSettin
lazy val lift = (project in file("lift")).settings(defaultSettings).dependsOn(field, indexchecker, core % "compile;test->test;runtime->runtime")
//lazy val spindle = (project in file("spindle")).settings(defaultSettings).dependsOn(core)
lazy val cc = (project in file("cc")).dependsOn(field, core).settings(defaultSettings)
lazy val root = (project in file(".")).settings(defaultSettings).aggregate(field,core,index,indexchecker,lift,cc)
lazy val `cc-test` = (project in file("cc-test")).dependsOn(cc % "compile;test->test;runtime->runtime").settings(defaultSettings)

lazy val root = (project in file(".")).settings(defaultSettings).aggregate(field, core, index, indexchecker, lift, cc)


/*
Expand Down
3 changes: 3 additions & 0 deletions cc-test/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
libraryDependencies ++= RogueDependencies.ccDeps

organization := "me.sgrouples"
63 changes: 63 additions & 0 deletions cc-test/src/test/scala/me/sgrouples/rogue/cc/RCcAutoMetaSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package me.sgrouples.rogue.cc

import me.sgrouples.rogue._
import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach, FlatSpec, MustMatchers }
import org.bson.types.ObjectId
import me.sgrouples.rogue.cc.CcRogue._
import org.scalatest.concurrent.{ Futures, ScalaFutures }

import scala.concurrent.Future
import scala.util.{ Failure, Try }

/**
* Created by mwielocha on 03/08/16.
*/

case class ACaseClass(
anId: ObjectId,
aString: String,
anInt: Int,
aLong: Long
)

class RCcAutoMetaSpec extends FlatSpec with MustMatchers with BeforeAndAfterEach with ScalaFutures {

override protected def beforeEach(): Unit = {
super.beforeEach()
val m = MongoTestConn.connectToMongo
CcMongo.defineDb("default", m, "rogue-test-")
}

override protected def afterEach(): Unit = {
super.afterEach()
MongoTestConn.disconnectFromMongoSync
}

"CcMeta macro" should "create a proper cc meta from a cc" in {

import scala.concurrent.ExecutionContext.Implicits.global

import me.sgrouples.rogue.BsonFormats._

val CaseClasses = RCcAutoMeta[ACaseClass]("case_classes")

val id = new ObjectId

val foo = ACaseClass(id, "test", 1, 1L)

val insert = CaseClasses.insertOneAsync(foo)

val get = CaseClasses
.where(_.anId eqs id)
.getAsync()

val r = {
for {
_ <- insert
f <- get
} yield f
}

r.futureValue mustBe Some(foo)
}
}
3 changes: 2 additions & 1 deletion cc/src/main/scala/me/sgrouples/rogue/cc/CcMeta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ trait CcMeta[T] extends CcMetaLike[T] {
def reader(field: Field[_, _]): BsonFormat[_]
}

class RCcMeta[T](collName: String)(implicit f: BsonFormat[T]) extends CcMeta[T] {
class RCcMeta[T](collName: String)(implicit private val f: BsonFormat[T]) extends CcMeta[T] {

def connId = "default"

Expand Down Expand Up @@ -61,3 +61,4 @@ class RCcMeta[T](collName: String)(implicit f: BsonFormat[T]) extends CcMeta[T]
override def writeAnyRef(t: AnyRef): BsonDocument = f.write(t.asInstanceOf[T]).asDocument()

}

183 changes: 183 additions & 0 deletions cc/src/main/scala/me/sgrouples/rogue/cc/RCcAutoMeta.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package me.sgrouples.rogue.cc

import java.time.LocalDateTime
import java.util.UUID

import me.sgrouples.rogue._
import org.bson.types.ObjectId

import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros

object RCcAutoMeta {
def apply[T](collectionName: String)(implicit f: BsonFormat[T]): Any = macro RCcAutoMetaImpl.applyImpl[T]
}

object RCcAutoMetaImpl {

/*
class IntField[O](name: String, o: O) extends MCField[Int, O](name, o) {
override def defaultValue = 0
}
class LongField[O](name: String, o: O) extends MCField[Long, O](name, o) {
override def defaultValue = 0L
}
class DoubleField[O](name: String, o: O) extends MCField[Double, O](name, o) {
override def defaultValue = 0d
}
class StringField[O](name: String, o: O) extends MCField[String, O](name, o) {
override def defaultValue = ""
}
class ObjectIdField[O](name: String, o: O) extends MCField[ObjectId, O](name, o) {
override def defaultValue = ObjectId.get()
}

class UUIDIdField[O](name: String, o: O) extends MCField[UUID, O](name, o) {
override def defaultValue = UUID.randomUUID()
}

class LocalDateTimeField[O](name: String, o: O) extends MCField[LocalDateTime, O](name, o) {
override def defaultValue = LocalDateTime.now()
}

class BooleanField[O](name: String, o: O) extends MCField[Boolean, O](name, o) {
override def defaultValue = false
}
class EnumField[T <: Enumeration, O](name: String, o: O)(implicit e: T) extends MCField[T#Value, O](name, o) {
override def defaultValue: T#Value = e(0)
}

class ListField[V, O](name: String, o: O) extends MCField[List[V], O](name, o) {
override def defaultValue = Nil
}

class ArrayField[V: ClassTag, O](name: String, o: O) extends MCField[Array[V], O](name, o) {
override def defaultValue = Array.empty[V]
}

class CClassField[C, MC <: CcMeta[C], O](val name: String, val childMeta: MC, val owner: O) extends Field[C, O]

class CClassListField[C, MC <: CcMeta[C], O](name: String, val childMeta: MC, owner: O) extends MCField[Seq[C], O](name, owner) {
override def defaultValue: List[C] = Nil
}

class CClassArrayField[C: ClassTag, O](name: String, o: O) extends MCField[Array[C], O](name, o) {
override def defaultValue = Array.empty[C]
}

class MapField[V, O](name: String, o: O) extends MCField[Map[String, V], O](name, o) {
override def defaultValue = Map.empty
}

class OptIntField[O](name: String, o: O) extends OCField[Int, O](name, o)
class OptLongField[O](name: String, o: O) extends OCField[Long, O](name, o)
class OptDoubleField[O](name: String, o: O) extends OCField[Double, O](name, o)
class OptStringField[O](name: String, o: O) extends OCField[String, O](name, o)
class OptObjectIdField[O](name: String, o: O) extends OCField[ObjectId, O](name, o)
class OptUUIDIdField[O](name: String, o: O) extends OCField[UUID, O](name, o)
class OptLocalDateTimeField[O](name: String, o: O) extends OCField[LocalDateTime, O](name, o)
class OptBooleanField[O](name: String, o: O) extends OCField[Boolean, O](name, o)
class OptEnumField[T <: Enumeration, O](name: String, o: O)(implicit e: T) extends OCField[T#Value, O](name, o)
class OptListField[V, O](name: String, o: O) extends OCField[List[V], O](name, o)
class OptArrayField[V: ClassTag, O](name: String, o: O) extends OCField[Array[V], O](name, o)
class OptCClassField[C, MC <: CcMeta[C], O](val name: String, val childMeta: MC, val owner: O) extends Field[C, O]
class OptCClassListField[C, O](name: String, o: O) extends OCField[List[C], O](name, o)
class OptCClassArrayField[C: ClassTag, O](name: String, o: O) extends OCField[Array[C], O](name, o)
class OptMapField[V, O](name: String, o: O) extends OCField[Map[String, V], O](name, o)
*/

def applyImpl[A: c.WeakTypeTag](c: Context)(collectionName: c.Expr[String])(f: c.Expr[BsonFormat[A]]): c.Expr[Any] = {

import c.universe._

def log(msg: String) = Console println msg

val tpe = weakTypeOf[A]
val sym = tpe.typeSymbol.asClass

require(sym.isCaseClass)

val companionObject = sym.companion
val companionType = companionObject.typeSignature

val applyMethod = companionType.decl(TermName("apply")).asMethod

val params = applyMethod.paramLists.flatten

val paramsWithNames = params.map {
p => p.name.decodedName.toString -> p.typeSignature
}

val outClsName = sym.name.decodedName

val outCls = TypeName(outClsName + "_RCcAutoMeta")

def clsName[T: Manifest]: String = {
implicitly[Manifest[T]].runtimeClass.getSimpleName
}

def mkSimpleField[C: Manifest](name: String): c.Tree = {
val nameLiteral = Literal(Constant(name))
val nameTerm = TermName(name)
val fieldTypeNameTerm = TypeName(clsName[C])
q"val $nameTerm = new $fieldTypeNameTerm[$outCls]($nameLiteral, this)"
}

def mkTypeParamField[C: Manifest](name: String, t: Type): c.Tree = {
val nameLiteral = Literal(Constant(name))
val nameTerm = TermName(name)
val fieldTypeNameTerm = TypeName(clsName[C])
val p = t.typeArgs.head
q"val $nameTerm = new $fieldTypeNameTerm[$p, $outCls]($nameLiteral, this)"
}

// TODO: this currently doesn't work
def mkCaseClassField[C: Manifest](name: String, t: Type): c.Tree = {
val nameLiteral = Literal(Constant(name))
val nameTerm = TermName(name)
val fieldTypeNameTerm = TypeName(clsName[C])

val metaName = TermName(List(
t.typeSymbol.asClass.name.decodedName, "_",
outClsName, "_", "RCcAutoMeta"
).mkString)

val meta = q"val $metaName = RCcAutoMetaImpl.applyImpl[$t]"
val metaType = q"RCcMeta[$t]"

q"val $nameTerm = new $fieldTypeNameTerm[$t, $metaType, $outCls]($nameLiteral, $meta, this)"
}

// @formatter:off
// format: OFF

val fields = paramsWithNames.map {
case (n, t) if t <:< typeOf[String] => mkSimpleField[StringField[_]](n)
case (n, t) if t <:< typeOf[Long] => mkSimpleField[LongField[_]](n)
case (n, t) if t <:< typeOf[Int] => mkSimpleField[IntField[_]](n)
case (n, t) if t <:< typeOf[Double] => mkSimpleField[DoubleField[_]](n)
case (n, t) if t <:< typeOf[ObjectId] => mkSimpleField[ObjectIdField[_]](n)
case (n, t) if t <:< typeOf[Boolean] => mkSimpleField[BooleanField[_]](n)
case (n, t) if t <:< typeOf[LocalDateTime] => mkSimpleField[LocalDateTimeField[_]](n)
case (n, t) if t <:< typeOf[UUID] => mkSimpleField[OptUUIDIdField[_]](n)
case (n, t) if t <:< typeOf[List[_]] => mkTypeParamField[ListField[_, _]](n, t)
case (n, t) if t.typeSymbol.asClass.isCaseClass => mkCaseClassField[CClassField[_, _, _]](n , t)
}

val dummy = TermName(c.freshName)

log(s"Generated following fields for $tpe:\n" + q"..$fields")

val res = q"""
import me.sgrouples.rogue._
import me.sgrouples.rogue.cc._
class $outCls(c: String)(f: BsonFormat[$tpe])
extends RCcMeta[$tpe](c)(f) { ..$fields }
val $dummy = 1
new $outCls($collectionName)($f)
"""

c.Expr(res)
}
}

10 changes: 8 additions & 2 deletions project/RogueSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ object RogueSettings {
val nexus = "https://nexus.groupl.es/"
val nexusReleases = "releases" at nexus+"repository/maven-releases/"
val nexusSnapshots = "snapshots" at nexus+"repository/maven-snapshots/"

val projectScalaVersion = "2.11.8"

lazy val defaultSettings: Seq[Setting[_]] = Seq(
version := "2.5.1-MongoAsync-shapeless-12",
organization := "io.fsq",
scalaVersion := "2.11.8",
scalaVersion := projectScalaVersion,
publishMavenStyle := true,
publishArtifact in Test := false,
pomIncludeRepository := { _ => false },
Expand Down Expand Up @@ -65,6 +67,7 @@ object RogueDependencies {
"org.specs2" %% "specs2-core" % specsVer % "test",
"org.specs2" %% "specs2-matcher" % specsVer % "test",
"org.specs2" %% "specs2-junit" % specsVer % "test",
"org.scalatest" % "scalatest_2.11" % "3.0.0-RC4" % "test",
"com.novocode" % "junit-interface" % "0.11" % "test",
"org.slf4j" % "slf4j-simple" % "1.7.21" % "test"
)
Expand All @@ -75,6 +78,9 @@ object RogueDependencies {

val rogueLiftDeps = mongoDeps ++ joda ++ liftDeps ++ liftRecordDeps

val ccDeps = mongoDeps ++ Seq(shapeless) ++ testDeps
val ccDeps = mongoDeps ++ Seq(
shapeless,
"org.scala-lang" % "scala-reflect" % RogueSettings.projectScalaVersion
) ++ testDeps

}