From 630113dcce18e4be54550669ccfc2c7bc6c3dff9 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 24 Sep 2009 16:54:49 -0700 Subject: Define implicit conversions from FieldSpec to functions that extract values. Defined FieldSpecOne2T and FieldSpecOption implicit conversions where T is a type of a value that given UserObject stores in it's class field. Thanks to that it's possible to use FieldSpec as functions: Message => T which is very convenient for defining UserType and implementing it's method toUserObject. Another improvement is defining implicit conversion FieldSpec => List[FieldSpec] that allows one to define list of FieldSpecs in an elegant way. FieldSpecMany and it's implicit conversions has not been implemented yet as it requires much more work. The problem is with sorting of fields that can be either interpreted as string and int fields. This ambiguity makes it much more difficult to define what is a valid (sorted) representation of given message that has fields named the same but storing both ints and non-int values. Change-Id: Iadeec3a97c7dac60508245e8f773e408ea9e5541 Signed-off-by: Grzegorz Kossakowski --- src/main/scala/com/google/gimd/UserType.scala | 87 +++++++++++++++++++++- .../scala/com/google/gimd/UserTypeTestCase.scala | 41 ++++++++-- .../google/gimd/jgit/JGitDatabaseTestCase.scala | 13 ++-- .../com/google/gimd/jgit/JGitFileTestCase.scala | 13 ++-- .../gimd/modification/ModificationTestCase.scala | 13 ++-- .../google/gimd/query/MessageQueryTestCase.scala | 11 ++- 6 files changed, 140 insertions(+), 38 deletions(-) diff --git a/src/main/scala/com/google/gimd/UserType.scala b/src/main/scala/com/google/gimd/UserType.scala index a5fb242..9bf08b1 100644 --- a/src/main/scala/com/google/gimd/UserType.scala +++ b/src/main/scala/com/google/gimd/UserType.scala @@ -24,11 +24,90 @@ abstract class UserType[T] { def toUserObject(itr: Message): T def toMessage(obj: T) = toMessageBuffer(obj).readOnly - def toMessageBuffer(obj: T): MessageBuffer = new MessageBuffer ++ fields.map { - case FieldSpec(name, f1, f2) => f1(name, f2(obj)) - } + def toMessageBuffer(obj: T): MessageBuffer = + new MessageBuffer ++ fields.flatMap(fieldSpec2Field(obj, _).toList) def fields: List[FieldSpec[T, _]] def children: Seq[NestedMember[_]] = Seq() + + protected final def FieldSpecOne[F](name: String, f1: (String, F) => Field, f2: T => F) = + new FieldSpecOne(name, f1, f2) + + protected final def FieldSpecOption[F](name: String, f1: (String, F) => Field, f2: T => F, + default: F) = new FieldSpecOption(name, f1, f2, default) + + private def fieldSpec2Field[F](obj: T, x: FieldSpec[T, F]) = + x.fieldFactoryOption(x.name, x.f2(obj)) } -case class FieldSpec[T, F](name: String, f1: (String, F) => Field, f2: T => F) +object UserType { + implicit def fieldSpec2SingletonList[T,F](x: FieldSpec[T, F]) = List(x) + + implicit def FieldSpecOne2Int[T](spec: FieldSpecOne[T, Int]) = + (m: Message) => m.one(spec.name).intField.value + + implicit def FieldSpecOne2String[T](spec: FieldSpecOne[T, String]) = + (m: Message) => m.one(spec.name).stringField.value + + implicit def FieldSpecOne2Timestamp[T](spec: FieldSpecOne[T, Timestamp]) = + (m: Message) => m.one(spec.name).timestampField.value + + implicit def FieldSpecOne2Long[T](spec: FieldSpecOne[T, Long]) = + (m: Message) => m.one(spec.name).longField.value + + implicit def FieldSpecOne2BigInt[T](spec: FieldSpecOne[T, BigInt]) = + (m: Message) => m.one(spec.name).bigIntField.value + + implicit def FieldSpecOne2BigDecimalField[T](spec: FieldSpecOne[T, BigDecimal]) = + (m: Message) => m.one(spec.name).bigDecimalField.value + + implicit def FieldSpecOption2Int[T](spec: FieldSpecOption[T, Int]) = + (m: Message) => m.oneOption(spec.name).map(_.intField.value).getOrElse(spec.defaultValue) + + implicit def FieldSpecOption2String[T](spec: FieldSpecOption[T, String]) = + (m: Message) => m.oneOption(spec.name).map(_.stringField.value).getOrElse(spec.defaultValue) + + implicit def FieldSpecOption2Timestamp[T](spec: FieldSpecOption[T, Timestamp]) = + (m: Message) => m.oneOption(spec.name).map(_.timestampField.value).getOrElse(spec.defaultValue) + + implicit def FieldSpecOption2Long[T](spec: FieldSpecOption[T, Long]) = + (m: Message) => m.oneOption(spec.name).map(_.longField.value).getOrElse(spec.defaultValue) + + implicit def FieldSpecOption2BigInt[T](spec: FieldSpecOption[T, BigInt]) = + (m: Message) => m.oneOption(spec.name).map(_.bigIntField.value).getOrElse(spec.defaultValue) + + implicit def FieldSpecOption2BigDecimalField[T](spec: FieldSpecOption[T, BigDecimal]) = + (m: Message) => m.oneOption(spec.name).map(_.bigDecimalField.value).getOrElse(spec.defaultValue) +} + +abstract sealed class FieldSpec[T, F](val name: String, + val fieldFactoryOption: (String, F) => Option[Field], + val f2: T => F) + +final case class FieldSpecOne[T, F](override val name: String, + val fieldFactory: (String, F) => Field, + override val f2: T => F) + extends FieldSpec(name, + (name: String, value: F) => Some(fieldFactory(name, value)), + { userObject: T => + val result = f2(userObject) + if (result == null) + error("UserObject '1%s' returned null for field '%2s'.".format(userObject, name)) + else + result + }) + +final case class FieldSpecOption[T, F](override val name: String, + val fieldFactory: (String, F) => Field, + override val f2: T => F, + val defaultValue: F) + extends FieldSpec(name, + (name: String, value: F) => if (value != defaultValue) + Some(fieldFactory(name, value)) + else + None, + f2) + + +//TODO FieldSpecMany has to be implemented in a future + + diff --git a/src/test/scala/com/google/gimd/UserTypeTestCase.scala b/src/test/scala/com/google/gimd/UserTypeTestCase.scala index 9d239c0..7310156 100644 --- a/src/test/scala/com/google/gimd/UserTypeTestCase.scala +++ b/src/test/scala/com/google/gimd/UserTypeTestCase.scala @@ -17,15 +17,20 @@ package com.google.gimd import org.junit.Test import org.junit.Assert._ +import UserType._ class UserTypeTestCase { - class MyUserType extends UserType[String] { - def toUserObject(itr: Message): String = "" - def fields = Nil + case class Person(id: Int, name: String) + + class PersonUserType extends UserType[Person] { + val id = FieldSpecOne("id", IntField, _.id) + val name = FieldSpecOption("name", StringField, _.name, null) + def toUserObject(m: Message) = Person(id(m), name(m)) + def fields = id :: name } - class MyChildUserType extends MyUserType + class MyChildUserType extends PersonUserType abstract class MyWrongUserTypeBase[T] extends UserType[T] class MyWrongUserType extends MyWrongUserTypeBase[String] { @@ -35,14 +40,14 @@ class UserTypeTestCase { @Test def userTypeClass { - val ut = new MyUserType - assertEquals(classOf[String], ut.userTypeClass) + val ut = new PersonUserType + assertEquals(classOf[Person], ut.userTypeClass) } @Test def userTypeClassInSubtype { val ut = new MyChildUserType - assertEquals(classOf[String], ut.userTypeClass) + assertEquals(classOf[Person], ut.userTypeClass) } @Test{val expected = classOf[IllegalStateException]} @@ -51,4 +56,26 @@ class UserTypeTestCase { ut.userTypeClass } + @Test + def optionalValueNotPresentInMessage { + val msg = Message(Field("id", 2)) + val ut = new PersonUserType + val person = ut.toUserObject(msg) + assertEquals(Person(2, null), person) + } + + @Test + def defaultForOptionalValueInUserObject { + val ut = new PersonUserType + val msg = ut.toMessage(Person(3, null)) + assertEquals(Message(Field("id", 3)), msg) + } + + @Test + def nonDefaultForOptionalValueInUserObject { + val ut = new PersonUserType + val msg = ut.toMessage(Person(3, "John")) + assertEquals(Message(Field("id", 3), Field("name", "John")), msg) + } + } diff --git a/src/test/scala/com/google/gimd/jgit/JGitDatabaseTestCase.scala b/src/test/scala/com/google/gimd/jgit/JGitDatabaseTestCase.scala index 5cad05c..d82e378 100644 --- a/src/test/scala/com/google/gimd/jgit/JGitDatabaseTestCase.scala +++ b/src/test/scala/com/google/gimd/jgit/JGitDatabaseTestCase.scala @@ -20,25 +20,24 @@ import org.junit.Test import org.junit.Assert._ import org.spearce.jgit.lib.{Constants, ObjectId} import query.Predicate +import UserType._ final class JGitDatabaseTestCase extends AbstractJGitTestCase { case class SimpleMessage(name: String, value: Int) object SimpleMessageType extends UserType[SimpleMessage] { - def toUserObject(m: Message): SimpleMessage = { - val name = m.one("name").stringField.value - val value = m.one("value").intField.value - SimpleMessage(name, value) - } - def fields = List(FieldSpec("name", StringField, _.name), FieldSpec("value", IntField, _.value)) + val name = FieldSpecOne("name", StringField, _.name) + val value = FieldSpecOne("value", IntField, _.value) + def fields = name :: value + def toUserObject(m: Message): SimpleMessage = SimpleMessage(name(m), value(m)) } object SimpleMessageFileType extends FileType[SimpleMessage] { val pathPrefix = Some("sm/") val pathSuffix = Some(".sm") val userType = SimpleMessageType - def name(m: Message) = m.one("name").stringField.value + def name(m: Message) = userType.name(m) } @Test diff --git a/src/test/scala/com/google/gimd/jgit/JGitFileTestCase.scala b/src/test/scala/com/google/gimd/jgit/JGitFileTestCase.scala index f11da02..9797b00 100644 --- a/src/test/scala/com/google/gimd/jgit/JGitFileTestCase.scala +++ b/src/test/scala/com/google/gimd/jgit/JGitFileTestCase.scala @@ -18,25 +18,24 @@ import file.FileType import junit.framework.Assert._ import org.junit.Test import org.spearce.jgit.lib.ObjectId +import UserType._ class JGitFileTestCase extends AbstractJGitTestCase { case class SimpleMessage(name: String, value: Int) object SimpleMessageType extends UserType[SimpleMessage] { - def toUserObject(m: Message): SimpleMessage = { - val name = m.one("name").stringField.value - val value = m.one("value").intField.value - SimpleMessage(name, value) - } - def fields = List(FieldSpec("name", StringField, _.name), FieldSpec("value", IntField, _.value)) + val name = FieldSpecOne("name", StringField, _.name) + val value = FieldSpecOne("value", IntField, _.value) + def fields = name :: value + def toUserObject(m: Message) = SimpleMessage(name(m), value(m)) } object SimpleMessageFileType extends FileType[SimpleMessage] { val pathPrefix = None val pathSuffix = None val userType = SimpleMessageType - def name(m: Message) = m.one("name").stringField.value + def name(m: Message) = userType.name(m).toString } @Test diff --git a/src/test/scala/com/google/gimd/modification/ModificationTestCase.scala b/src/test/scala/com/google/gimd/modification/ModificationTestCase.scala index d30f1cf..2f9e09c 100644 --- a/src/test/scala/com/google/gimd/modification/ModificationTestCase.scala +++ b/src/test/scala/com/google/gimd/modification/ModificationTestCase.scala @@ -19,6 +19,7 @@ import file.{FileType, File} import org.junit.Test import org.junit.Assert._ import query._ +import UserType._ final class ModificationTestCase { @@ -26,19 +27,17 @@ final class ModificationTestCase { case class TreeNode(id: Int, name: String) object TreeNodeType extends UserType[TreeNode] { - def toUserObject(m: Message): TreeNode = - new TreeNode( - m.one("id").intField.value, - m.one("name").stringField.value - ) + val id = FieldSpecOne("id", IntField, _.id) + val name = FieldSpecOne("name", StringField, _.name) + def fields = id :: name override def children = Seq(nestedMember) - def fields = List(FieldSpec("id", IntField, _.id), FieldSpec("name", StringField, _.name)) + def toUserObject(m: Message) = new TreeNode(id(m), name(m)) } object MockFileType extends FileType[TreeNode] { val pathPrefix = None val pathSuffix = None val userType = TreeNodeType - def name(m: Message) = m.one("id").stringField.value + def name(m: Message) = userType.id(m).toString } case class MockFile(message: Message) extends File[TreeNode] { val path = "" diff --git a/src/test/scala/com/google/gimd/query/MessageQueryTestCase.scala b/src/test/scala/com/google/gimd/query/MessageQueryTestCase.scala index 21b461b..c8c58eb 100644 --- a/src/test/scala/com/google/gimd/query/MessageQueryTestCase.scala +++ b/src/test/scala/com/google/gimd/query/MessageQueryTestCase.scala @@ -17,17 +17,16 @@ package com.google.gimd.query import file.{File, FileType} import org.junit.Test import org.junit.Assert._ +import UserType._ class MessageQueryTestCase { case class TreeNode(id: Int, name: String) object TreeNodeType extends UserType[TreeNode] { - def toUserObject(m: Message): TreeNode = - new TreeNode( - m.one("id").intField.value, - m.one("name").stringField.value - ) + val id = FieldSpecOne("id", IntField, _.id) + val name = FieldSpecOne("name", StringField, _.name) + def fields = id :: name override def children = Seq(new NestedMember("node", TreeNodeType)) - def fields = List(FieldSpec("id", IntField, _.id), FieldSpec("name", StringField, _.name)) + def toUserObject(m: Message) = new TreeNode(id(m), name(m)) } val root = new TreeNode(-1, "a") -- cgit v1.2.3