2013-07-10 16 views
0

我想構建一個Scala DSL以將Java POJO的現有結構轉換爲等效於Map的結構。用於Scala中安全導航運算符的DSL

但是,傳入對象結構很可能包含大量空引用,這將導致輸出映射中沒有值。

在這方面的表現非常重要,所以我需要避免反射和拋/ NPE。

我已經認爲已經不符合我的要求this topic

我認爲答案可能在於使用宏來生成一些特殊的類型,但我沒有使用scala宏的經驗。

更正式地說:由項目提供

POJO類:(會有像50 POJO,嵌套的,所以我想這並不需要手工編寫和維護他們每一類或性狀的解決方案)

case class Level1(
    @BeanProperty var a: String, 
    @BeanProperty var b: Int) 

    case class Level2(
    @BeanProperty var p: Level1, 
    @BeanProperty var b: Int) 

預期的行爲:

println(convert(null)) // == Map() 
    println(convert(Level2(null, 3))) // == Map("l2.b" -> 3) 
    println(convert(Level2(Level1("a", 2), 3))) // == Map(l2.p.a -> a, l2.p.b -> 2, l2.b -> 3) 

正確執行,但我想一個更簡單的DSL寫的映射

implicit def toOptionBuilder[T](f: => T) = new { 
    def ? : Option[T] = Option(f) 
    } 

def convert(l2: Level2): Map[String, _] = l2? match { 
    case None => Map() 
    case Some(o2) => convert(o2.p, "l2.p.") + ("l2.b" -> o2.b) 
    } 

    def convert(l1: Level1, prefix: String = ""): Map[String, _] = l1? match { 
    case None => Map() 
    case Some(o1) => Map(
     prefix + "a" -> o1.a, 
     prefix + "b" -> o1.b) 
    } 

這裏是我想用一個DSL寫:「?」

def convertDsl(l2:Level2)={ 
     Map(
      "l2.b" -> l2?.b, 
      "l2.p.a" -> l2?.l1?.a, 
      "l2.p.b" -> l2?.l1?.b 
     ) 
    } 

請注意,這是完全正常,我指定的屬性是可選的。 我想要的是使用宏來靜態生成一個方法l2.?l1或l2?.l1,它返回Option [Level1](因此類型檢查在我的DSL中正確完成)。

+0

我被暗示在郵件列表中使用宏quasiquotes,但目前在scala 2.10中實現似乎有點棘手。 http://docs.scala-lang.org/overviews/macros/quasiquotes.html; https://gist.github.com/anonymous/7ab617d054f28d68901b; http://docs.scala-lang.org/overviews/macros/paradise。html#macro_paradise_for_210x –

回答

0

我不能完善它下來正是你上面給的,但通常,這樣的事情可能工作的語法:

sealed trait FieldSpec 

sealed trait ValueFieldSpec[+T] extends FieldSpec 
{ 
    def value: Option[T] 
} 

case class IntFieldSpec(value: Option[Int]) extends ValueFieldSpec[Int] 
case class StringFieldSpec(value: Option[String]) extends ValueFieldSpec[String] 

case class Level1FieldSpec(input: Option[Level1]) extends FieldSpec 
{ 
    def a: ValueFieldSpec[_] = StringFieldSpec(input.map(_.a)) 
    def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b)) 
} 

case class Level2FieldSpec(input: Option[Level2]) extends FieldSpec 
{ 
    def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b)) 
    def l1 = Level1FieldSpec(input.map(_.p)) 
} 

case class SpecArrowAssoc(str: String) 
{ 
    def ->(value: ValueFieldSpec[_]) = (str, value) 
} 

implicit def str2SpecArrowAssoc(str: String) = SpecArrowAssoc(str) 

implicit def Level2ToFieldSpec(input: Option[Level2]) = Level2FieldSpec(input) 

def map(fields: (String, ValueFieldSpec[_])*): Map[String, _] = 
    Map[String, Any]((for { 
    field <- fields 
    value <- field._2.value 
    } yield (field._1, value)):_*) 

def convertDsl(implicit l2: Level2): Map[String, _] = 
{ 
    map(
    "l2.b" -> l2.?.b, 
    "l2.p.a" -> l2.?.l1.a, 
    "l2.p.b" -> l2.?.l1.b 
) 
} 

然後我們得到:

scala> val myL2 = Level2(Level1("a", 2), 3) 
myL2: Level2 = Level2(Level1(a,2),3) 

scala> convertDsl(myL2) 
res0: scala.collection.immutable.Map[String,Any] = Map(l2.b -> 3, l2.p.a -> a, l2.p.b -> 2) 

注意,DSL使用'?'而不僅僅是'?'作爲我可以看到Scala在分號推理和後綴運算符方面遇到困難的唯一方式(例如,請參閱@ 0__對scala syntactic suger question的回答)。

此外,您可以提供的字符串是任意的(不對其進行檢查或解析),而這個簡單化的'FieldSpec'層次結構將假定所有POJO對字符串字段使用'a',對Int使用'b'領域等

有一點時間和精力,我相信這可以改善。

+0

感謝您的回答,但這不是我想要去的方向:它需要大量的手工編寫和維護所有FooSpec類(我將擁有類似50個POJO的類)。 –

+0

很可能@MichelDaviot,我自己也沒有玩過宏,但是我想知道是不是可以通過宏來獲得相同結果的代碼少於通過上述代碼。您也可以考慮將Level1和Level2(等)FieldSpec類中的單獨方法拆分爲自己的特徵,因此您可以從處理特定「值」字段的特徵中爲特定POJO編寫處理程序。然後引入一個新的POJO應該不是太大的負擔。 – Shadowlands