3

In Java, I do a lot of data integration work. One thing that comes up all the time is mapping data between multiple systems. So i'm constantly doing things like this

public enum DataField{
  Field1("xmlField", "dbField", "system1Field";
  private String xml;
  private String db;
  private String sys;

  private DataField(String xml, String db, String sys){
    this.xml = xml;
    this.db = db;
    this.sys = sys;
  }

  public getXml(){
    return this.xml;
  }

  public static DataField valueOfXml(String xml){
    for (DataField d : this.values()){
      if (d.xml.equals(xml)){ return d;}
    }
  }
  bla, bla bla
}

What this allows me to do is put the field name DataField in all my messaging and be able to map what that field is called in multiple systems. So in my XML, it may be firstname, in my database, it may be called first_name but in my external interface system, it may be called first. This pattern pulls all of that together very nicely and makes messaging in these types of systems very easy in a tight, type safe way.

Now I don't remember why Scala changed the enumeration implementation but I remember it made sense when I read it. But the question is, what would I use in Scala to replace this design pattern? I hate to lose it because it is very useful and fundamental to a lot of systems I write on a given day.

thanks

ghik
  • 10,706
  • 1
  • 37
  • 50
scphantm
  • 4,293
  • 8
  • 43
  • 77
  • Didn't you mean to declare `valueOfXml` static? – ghik Jan 11 '14 at 23:24
  • See Glen Best's [outstanding answer](http://stackoverflow.com/questions/20412287/creating-a-java-enum-in-scala) for your options. – Vidya Jan 11 '14 at 23:51

2 Answers2

5

I managed to make up this kind-of replacement for your case:

sealed class DataField(val xml: String, val db: String, val sys: String)

object DataField {
  case object Field1 extends DataField("xmlField1", "dbField1", "system1Field")
  case object Field2 extends DataField("xmlField2", "dbField2", "system2Field")
  case object Field3 extends DataField("xmlField3", "dbField3", "system3Field")

  val values = List(Field1, Field2, Field3)

  def valueOfXml(xml: String) =
    values.find(_.xml == xml).get
}

The annoying thing is that we have to manually create the values list. In this case however, we can do some macro hacking to reduce the boilerplate a bit:

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

object Macros {
  def caseObjectsFor[T]: List[T] = macro caseObjectsFor_impl[T]

  def caseObjectsFor_impl[T: c.WeakTypeTag](c: Context): c.Expr[List[T]] = {
    import c.universe._

    val baseClassSymbol = weakTypeOf[T].typeSymbol.asClass
    val caseObjectSymbols = baseClassSymbol.knownDirectSubclasses.toList.collect {
      case s if s.isModuleClass && s.asClass.isCaseClass => s.asClass.module
    }

    val listObjectSym = typeOf[List.type].termSymbol
    c.Expr[List[T]](Apply(Ident(listObjectSym), caseObjectSymbols.map(s => Ident(s))))
  }
}

Then we can do this:

val values = Macros.caseObjectsFor[DataField]

instead of manually listing all the case objects.

For this to work, it is essential that the base class is declared as sealed.

ghik
  • 10,706
  • 1
  • 37
  • 50
  • Awesome. This looks like it will work. So if i'm doing a job that i have to share with Java, I will keep enums. But if im doing something Scala only, this is what I will do. Thakns – scphantm Jan 12 '14 at 23:35
2

You could always do what I do, and keep writing the enums in Java.

Out of 62 .java files in my source tree, 61 are enums, and the other one is a package-info.java.

Alex Cruise
  • 7,939
  • 1
  • 27
  • 40
  • I considered that. Since im using spring, there's quite a few Java files in my project. Just wondering if there was a "scala way" to pull this off. – scphantm Jan 11 '14 at 23:46