How to access scala annotation in scala code - scala

I have defined a simple scala annoation and use it to annotate my case class Person:
package com.my
import scala.annotation.StaticAnnotation
#scala.annotation.meta.field
class Description(value: String) extends StaticAnnotation{
}
Then I use it in my Person class:
package com.my
import scala.beans.BeanProperty
case class Person(
#Description(value = "name00")
name: String,
#Description(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
object Person {
def main(args: Array[String]): Unit = {
val p = Person("abc", 21)
classOf[Person].getDeclaredFields.foreach {
field =>
field.setAccessible(true)
val name = field.getName
val value = field.get(p)
//annotations always return empty array
val annotations = field.getDeclaredAnnotations
annotations.foreach {
annotation =>
val tpe = annotation.annotationType()
println(tpe)
}
println(s"name is $name, value is: $value")
}
}
}
In the main method of Person object, the annotations array is always empty,
I would like to ask how to get the annoation information defined on the field.

Firstly, annotations written in Scala are accessible in sources (if the annotations extend scala.annotation.Annotation) and class files (if if the annotations extend scala.annotation.StaticAnnotation). In order to be accessible at runtime the annotations must be written in Java
import java.lang.annotation.*;
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Description {
String value();
}
How to use Scala annotations in Java code
Why annotations written in Scala are not accessible at runtime?
https://www.reddit.com/r/scala/comments/81qzs2/how_to_write_annotations_in_scala/
Alternatively you can use the original #Description (written in Scala and extending StaticAnnotation) but then you have to access it at runtime via Scala reflection rather than Java reflection.
Secondly, you misuse meta-annotations (#scala.annotation.meta.field). They should annotate not the definition of #Description but its applications.
import scala.annotation.meta.field
case class Person(
#(Description #field)(value = "name00")
name: String,
#(Description #field)(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
Annotating case class parameters
How can I reflect on a field annotation (Java) in a Scala program?
Calling a method from Annotation using reflection
Output:
interface Description // appears
name is name, value is: abc
interface Description // appears
name is age, value is: 21
name is xyz, value is: xyz
With Scala reflection you can do
import scala.annotation.StaticAnnotation
import scala.beans.BeanProperty
import scala.annotation.meta.field
import scala.reflect.runtime.universe._
class Description(value: String) extends StaticAnnotation
case class Person(
#(Description #field)(value = "name00")
name: String,
#(Description #field)(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
def main(args: Array[String]): Unit = {
val p = Person("abc", 21)
typeOf[Person].decls
.collect { case t: TermSymbol if t.isVal => t.annotations }
.foreach(println)
}
//List(Description #scala.annotation.meta.field("name00"))
//List(Description #scala.annotation.meta.field("age00"))
//List(scala.beans.BeanProperty)

Related

Implementing Config class containing Enum Map by PureConfig and Enumeratum

I'm trying to read my config file into my case class whose one of attribute is a Map of Enumeratum key and Case Class value by using pureconfig and pureconfig-enumeratum libraries version 0.14.0 with scala 2.11.
When I change the Map key from Enumeratum key to String, it works, but it does not work with Enum key.
import enumeratum.EnumEntry.{Hyphencase}
import enumeratum._
import pureconfig.{ConfigSource}
import pureconfig.generic.auto._
import pureconfig.module.enumeratum._
object CheckPureConfig extends App {
private val myConf = ConfigSource.default.loadOrThrow[SsystemConf]
println(myConf)
}
case class SsystemConf(target: Map[Ssystem, MyConfig])
case class MyConfig(path: Ssystem, link: String)
sealed abstract class Ssystem(myField: String) extends EnumEntry with Hyphencase{
def printit() = myField
}
object Ssystem extends Enum[Ssystem] {
val values = findValues
case object MyEnumA extends Ssystem("testFieldEnum1")
case object MyEnumB extends Ssystem("testFieldEnum2")
}
And this is my application.conf
target {
my-enum-a= {
path : "samplepath1"
link : "samplehttp1"
}
my-enum-b = {
path : "samplepath2"
link : "samplehttp2"
}
}
You have to use configurable converter to tell pureconfig how to transform your enum to Map keys. You have genericMapReader for that:
implicit def enumMapReader[V: ConfigReader]: ConfigReader[Map[Ssystem, V]] =
genericMapReader { name =>
Ssystem.withNameOption(name)
.fold[Either[String, Ssystem]](Left(s"$name is not enum"))(Right(_))
}

Get only super class fields

case class Person(name: String,
override val age: Int,
override val address: String
) extends Details(age, address)
class Details(val age: Int, val address: String)
val person = Person("Alex", 33, "Europe")
val details = person.asInstanceOf[Details] // ???
println(details) // I want only Details class fields
I have these 2 classes. In reality, both have a lot of fields. Somewhere, I need only field of superclass, taken from Person class.
There is a nice way to get only super class values and not mapping them field by field?
*I'm pretty sure I'll have some problems with json writes for class Details (which is not a case class and have not a singleton object, but this is another subject)
If I get your question correctly, then you might be asking me runtime polymorphism or dynamic method dispatch from java.
If so, you may have to create both the class and not case class
class Details( val age: Int, val address: String)
class Person(name: String,
override val age: Int,
override val address: String
) extends Details(age, address) {
}
Now create the object of person and reference to superclass (Details)
val detail:Details = new Person("Alex", 33, "Europe")
println(detail.address)
println(detail.age)
This way you will be able to get the only address and age
Another way is like , why can't we create the Details a separate entity like:
case class Details( age: Int, address: String)
case class Person(name: String,
details: Details
)
val detail = Person("Alex", Details(10,"Europe") )
Output:
println(detail.details)
Details(10,Europe)
I will post a solution that leverages scala macro system (old kind, not the newest introduced with Scala 3.0). It could be an overkill for you...
BTW, if you want to access to only parent values (for example for getting key, value pair), you can:
given a type tag, get all parents;
from them, extract all the accessors (vals);
for each val, get its value;
and finally returns a list with all accessors taken
So, I try to solve each point step by step.
First of all, we have to write the macro definition as:
object Macros {
def accessors[T](element : T): String = macro MacrosImpl.accessors[T]
}
object MacrosImpl {
def accessors[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[String] = ...
}
for the first point, we can leverage the reflection macroprogramming API using c.universe:
import c.universe._
val weakType = weakTypeTag[T] //thanks to the WeakTypeTag typeclass
val parents = weakType.tpe.baseClasses
for the second point, we can iterate over the parent classes and then take only the public accessors:
val accessors = parents
.map(weakType.tpe.baseType(_))
.flatMap(_.members)
.filter(_.isPublic)
.filter(_.isMethod)
.map(_.asMethod)
.filter(_.isAccessor)
.toSet
So, for example, if the we write Macros.accessors[Details](person), accessors will yield age and address.
To take the value, we can leverage quasiqouting. So, first we take only the values name:
val names = accessors
.map(_.fullName)
.map(_.split("\\."))
.map(_.reverse.head)
Then we convert them into a TermName:
val terms = names.map(TermName(_))
And finally, we convert each term to a key value tuple containing the val name and its value:
val accessorValues = terms
.map(name => c.Expr[(String, Any)](q"(${name.toString}, ${element}.${name})"))
.toSeq
The last step consist in convert a Seq[Expr[(String, Any)] into a Expr[Seq[(String, Any)]. A way to do that, could be leveraging recursion, reify, and splicing expression:
def seqToExprs(seq: Seq[Expr[(String, Any)]]): c.Expr[Seq[(String, Any)]] =
seq.headOption match {
case Some(head) =>
c.universe.reify(
Seq((head.splice._1, head.splice._2)) ++
seqToExprs(seq.tail).splice
)
case _ => c.Expr[Seq[(String, Any)]](q"Seq.empty")
}
So now I decide to return a String representation (but you can manipulate it as you want):
val elements = seqToExprs(accessorValues)
c.Expr[String](q"${elements}.mkString")
You can use it as:
import Macros._
class A(val a : Int)
class B(val b : Int) extends A(b)
class C(val c: Int) extends B(c)
//println(typeToString[List[Set[List[Double]]]])
val c = new C(10)
println(accessors[C](c)) // prints (a, 10)(b, 10)(c, 10)
println(accessors[B](c)) // prints (a, 10)(b, 10)
println(accessors[A](c)) // prints (a, 10)
And, using your example:
// Your example:
case class Person(name: String,
override val age: Int,
override val address: String
) extends Details(age, address)
class Details(val age: Int, val address: String)
val person = Person("Alex", 33, "Europe")
println(accessors[Details](person)) // prints (address,Europe)(age,33)
println(accessors[Person](person)) // prints (address,Europe)(age,33)(name,Alex)
Here there is a repository with the macro implemented.
Scala 3.0 introduce a safer and cleaner macro system, if you use it and you want to go further you can read these articles:
macros tips and tricks
short tutorial
another tutorial

Passing a class type as a parameter in Scala

I want to parse a Json Object. I have defined the following class:
case class Metadata(
val messageTime: Option[String],
val messageUUID: Option[String],
val messageSource: Option[String],
val messageFormat: Option[String])
I generate a dummy object of this type:
jsonMsg = """{"messageTime": "2018-09-19T11:03:22.382+04:00",
"messageUUID": "4ef7fce7-81cf-4a27-82d8-9019c2cf09df",
"messageSource" : "SDG",
"messageFormat" : "json"}"""
Now I want to use mapper.readValue to parse that string into an Object of type Metadata:
fromJson(jsonMsg)
def fromJson(json : String) = {
mapper.readValue[Metadata](json, classOf[Metadata])
}
This works fine. But in this case fromJson can only parse Metadata objects.
Now I tried to make my method fromJson more generic, passing the class of the object to be parsed like this [I used this other question as a reference ]:
val obj = fromJson2(jsonMsg, classOf[Metadata])
def fromJson2(json : String, c: Class[_]) = {
mapper.readValue(json, c)
}
But I get the error:
forward reference extends over definition of value obj
What am I missing or how could I achieve what I am trying?
Just in case
mapper.readValue belongs to import com.fasterxml.jackson.databind.ObjectMapper
And this is the definition:
public <T> T readValue(String content, Class<T> valueType)
throws IOException, JsonParseException, JsonMappingException
{
return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
}

How to list annotations (custom java ones and others) on field members of Scala case class

So I'm trying to list fields with specific annotation in a Scala case class and I'm not able to get it working... Let's see come code right away
The case class (it's a simplified version of it, mine extends another class and is also nested in my test class where I use it for unit testing only):
case class Foo(#Unique var str: String) {}
The custom Java annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
public #interface Unique {}
And my class (simplified again) where I'm trying to do some stuffs with fields marked as unique
class SomeClass[T] (implicit typeTag: TypeTag[T]) {
val fields: Iterable[universe.TermSymbol] = typeOf(typeTag).members.collect { case s: TermSymbol => s }.
filter(s => s.isVal || s.isVar)
val list = fields.flatMap(f => f.annotations.find(_.tpe =:= TypeOf[Unique]).((f, _))).toList
}
But the val list in the last peace of code is always empty... fields has str listed in but without the annotation.
What am I missing?
The code listing the annotations is from the following answer:
How to list all fields with a custom annotation using Scala's reflection at runtime?
Seems the reference post is Scala 2.10 is old and is not compatible with the newest Scala version.
There is an example for how to get the specify annotation by type.
def listProperties[T: TypeTag]: List[universe.Annotation] = {
typeOf[T].typeSymbol.asClass
.asClass
.primaryConstructor
.typeSignature
.paramLists.flatten.flatMap(_.annotations)
}
val annotations = listProperties[Foo].filter(_.tree.tpe =:= typeOf[Unique])
println(annotations)
and there is way to get the annotation's field value:
case class Foo(#Unique(field = "bar") val str: String) {}
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
val tb = currentMirror.mkToolBox()
val result = tb.eval(tb.untypecheck(head.tree)).asInstanceOf[Unique]
and need to call out your annotation class is implemented by using Java style, In Scala maybe you want to use StaticAnnotation for creating Annotation, like:
class Unique extends StaticAnnotation

Scala Quasiquotes Destructuring a Type

Context:
I'm working on a library for working with JMX in Scala. One of the objectives is to have a strong typed interface to Managed Beans. I guess akin to to the Spring framework JMX library.
Objective: Macro to Deserialise TabularData to a case class:
// interface for which I'd like to generate an implementation using a macro
trait JMXTabularAssembler[T <: Product] {
def assemble(data: TabularData): T
}
object JMXAnnotations {
case class Attribute(name: String) extends StaticAnnotation
}
case class example(
#Attribute("name") name: String,
#Attribute("age") age: Int,
unmarked: String
)
Problem: There are plenty of examples of composing tree's using the q"" interpolators. But I can't figure out how to use the tq"" interpolator to
extract the fields out of a case class from a type context.
private def mkAssembler[T <: Product : c.WeakTypeTag](c: Context): c.universe.Tree = {
import c.universe._
val tt = weakTypeOf[T]
}
Question: How do I use the QuasiQuote machinery to destructure the fields of my case class so that I can loop over them and filter out fields with my annotation (my Attribute annotation is not available from the approach I am currently taking"). An implementation of the following that returns the fields with annotations in declaration order is what I am after.
private def harvestFieldsWithAnnotations[T<: Product: c.WeakTypeTag](c: Context):
List[(c.universe.Name, String, c.universe.Type, List[c.universe.Annotation])] = ???
Bonus: The objective is to get the attribute fields, generate trees for each field that extract the field from the TabularData and use these trees to create the JMXTabularAssembler Functor. If you could show me how to do this for the example above it would bootstrap my efforts :D.
What I have tried: I started solving the problem by using reflection. This does not seem the right way to do it. Snippets:
...
val dec = tt.decls.sorted
def getFields = dec.withFilter( t=> t.isTerm && ! t.isMethod)
def getCaseAccessors = dec.withFilter( t => t.isMethod && t.asMethod.isCaseAccessor)
dec.foreach { d=>
println(d.name, d.annotations)
}
getFields.foreach { f =>
println(f.annotations)
}
val types = getCaseAccessors.map { d =>
println(d.annotations)
(d.name, tt.member(d.name).asMethod.returnType)
}
...
The following method does the trick, it does not use quasi quotes. The key is to access the backing field of a symbol representing the field accessor of a case class (the accessed call).
private def harvestFieldsWithAnnotations[T <: Product : c.WeakTypeTag](c: Context) = {
import c.universe._
val tt = weakTypeOf[T]
tt.decls.sorted.filter(t => t.isMethod && t.asMethod.isCaseAccessor).map { ca =>
val asMethod = tt.member(ca.name).asMethod
(ca.name, asMethod.returnType, asMethod.accessed.annotations)
}
}
Field annotations won't get retained unless they are explicitly annotated with scala.annotation.meta.field.
So the Attribute annotation should be:
#field
case class Attribute(name: String) extends StaticAnnotation