Reputation: 941
I want to implement a thin wrapper for enum generated by protobuf compiler, because I need to annotate some methods. Protobuf generates final class so I implement a new generic class that stores a enum instance instead of extending the enum subclass.
Protobuf-generated class is like bellow
public enum CardType
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>NONE = 0;</code>
*/
NONE(0, 0)
}
and I implement a wrapper class
object ProtoEnumWrapper {
def fromString[T <: Enum[T] with ProtocolMessageEnum](s: String): ProtoEnumWrapper[T] =
Enum.valueOf[T](classOf[T], s) match {
case null => throw new InvalidStringForEnum(s"${s} is not value of ${classOf[T]}")
}
def fromProto[T <: Enum[T] with ProtocolMessageEnum](proto: T): ProtoEnumWrapper[T] =
new ProtoEnumWrapper[T](proto)
class InvalidStringForEnum(message: String) extends RuntimeException(message)
}
class ProtoEnumWrapper[T <: Enum[T] with ProtocolMessageEnum](proto: T) {
override def equals(o: Any) = o match {
case x: ProtoEnumWrapper[T] => proto == x.proto
case e: T => proto == e
case s: String => toString == s
case _ => false
}
override def toString = proto.toString
}
as I want to use this like
val wrapper = ProtoEnumWrapper.fromString[CardType]("NONE")
val proto = CardType.valueOf("NONE")
val wrapper2 = ProtoEnumWrapper.fromProto[CardType](proto)
wrapper == proto // true
wrapper == wrapper2 // true
wrapper == "NONE" // true
but, this causes compile errors
Error:(7, 29) class type required but T found
Enum.valueOf[T](classOf[T], s) match {
^
Error:(8, 84) class type required but T found
case null => throw new InvalidStringForEnum(s"${s} is not value of ${classOf[T]}")
^
Error:(19, 47) value proto is not a member of jp.pocket_change.voucher.domain.ProtoEnumWrapper[T]
case x: ProtoEnumWrapper[T] => proto == x.proto
^
Upvotes: 1
Views: 775
Reputation: 2900
There's a thing called erasure - because of it, when your code is compiled to JVM bytecode, there is no trace of T
in it. Most of the time. In Scala though, there is a workaround for it: you may specify an implicit scala.reflect.ClassTag
which is generated by the compiler and passed to respective method invocations.
I updated the signatures of ProtoEnumWrapper.fromString
, ProtoEnumWrapper.fromProto
and ProtoEnumWrapper
in your code:
object ProtoEnumWrapper {
def fromString[T <: Enum[T] with ProtocolMessageEnum](s: String)(implicit ct: ClassTag[T]: ProtoEnumWrapper[T] = try {
new ProtoEnumWrapper(Enum.valueOf[T](ct.runtimeClass, s))
} catch {
case e: IllegalArgumentException => throw new InvalidStringForEnum(s"${s} is not a value of ${ct.runtimeClass}")
}
def fromProto[T <: Enum[T] with ProtocolMessageEnum : ClassTag](proto: T): ProtoEnumWrapper[T] =
new ProtoEnumWrapper[T](proto)
class InvalidStringForEnum(message: String) extends RuntimeException(message)
}
class ProtoEnumWrapper[T <: Enum[T] with ProtocolMessageEnum : ClassTag](proto: T) {
override def equals(o: Any) = o match {
case x: ProtoEnumWrapper[T] => proto == x.proto
case e: T => proto == e
case s: String => toString == s
case _ => false
}
override def toString = proto.toString
}
Note the 2 alternative definitions: def a[T : ClassTag]
is just a shorter way to write def a[T](implicit ct: ClassTag[T])
in case you don't need an actual instance of ct
in your code.
Upvotes: 2