I want to automatically convert/coerce strings into Scala Enumeration Values, but without knowing beforehand what Enumeration subclass (subobject?) the declaration is from.
So, given:
object MyEnumeration extends Enumeration {
type MyEnumeration = Value
val FirstValue, SecondValue = Value
}
and
class MyThing {
import MyEnumeration._
var whichOne: MyEnumeration = FirstValue
}
how would I implement the following?
val myThing = new MyThing()
setByReflection(myThing, "whichOne", "SecondValue")
What I'm finding is that when I get the class of MyThing.whichOne (via java.lang.Field), the return is 'scala.Enumeration$Value', which isn't specific enough for me to enumerate the names of all the possible values.
The specific type is lost at runtime however you can capture it at compile time through implicits. You can provide an implicit in your enum like the following.
object MyEnumeration extends Enumeration {
type MyEnumeration = Value
val FirstValue, SecondValue = Value
implicit def convertMyEnumeration( value: String ) =
MyEnumeration.withName( value )
}
class MyThing {
import MyEnumeration._
var whichOne: MyEnumeration = FirstValue
}
val myThing = new MyThing()
myThing.whichOne = "SecondValue"
You could also do the following as well if the type system is unable to resolve your enum to apply the correct implicit in your usages you can default to using polymorphism if and provide a setter like the following.
class MySuperThing {
def setWhichOne( value: String }
}
class MyThing extends MySuperThing {
import MyEnumeration._
var whichOne: MyEnumeration = FirstValue
def setWhichOne( value: String ) = MyEnumeration.withName( value )
}
val myThing: MySuperThing = new MyThing()
myThing.setWhichOne( "SecondValue" )
Check out my EnumReflector, which will tell you all about enum field types, as long as they're declared in the package scope (no inner-class enums, function scope enums).
You need to supply it the scala type of the enum field (which can found through scala's reflection interface) (see below for how to get this from a Class[_])
val enumObjectType:Type = ... object's scala.Enumeration.Value field
val isEnum = EnumReflector.isEnumeration(enumObjectType)
val reflector = EnumReflector(typ)
val eid = reflector.toID(enumObject)
val enum = reflector.fromID(eid)
assertTrue(eid eq enum)
How to get the enumObjectType?
val typ = typeOf[EC]
// OR
val typ = getTypeForClass(classOf[EC]) // or from your package scanner
val enumGetters = classAccessors( typ ).filter(EnumReflector.isEnumeration(_))
// Let's looks at the 1st enum field
val reflector = EnumReflector(enumGetters.head.returnType)
val fieldName = enumGetters.head.name.toString
... now you need to use Java reflection to find the same method for this class. Scala has instance reflection but requires you do reflection on the instance at runtime (which is slow). Better to get a handle to Java reflection invoke method:
val enumGetter = toJavaClass(typ).getMethods.filter(_.getName==fieldName).head
Starting with your
val obj = new MyThing()
you can now get the object's enum field and interpret its name or id through the reflector:
val enumObj = enumGetter.invoke(obj)
val name = reflector.toName(enumObj)
val enumObj2 reflector.fromName(name)
assertTrue( enumObj2 eq enumObj )
Support functions:
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(this.getClass.getClassLoader)
def toJavaClass(tpe:Type) = mirror.runtimeClass(tpe.typeSymbol.asClass)
def getTypeForClass(clazz: Class[_]): Type = mirror.classSymbol(clazz).toType
def classGetters(typ:Type) = typ.members.collect{case m:MethodSymbol=> m}.filter(_.isGetter)
def classAccessors(typ:Type) = typ.members.collect{case m:MethodSymbol=> m}.filter(_.isAccessor)
There's a unit test in the project that demonstrates it.