use*_*993 6 java annotations scala annotation-processor
我一直在学习注释以及注释处理器是什么。我正在查看 Java 示例,似乎有一种正确的方法可以做到这一点。但是,在 Scala 中,我没有合适的网站/文档来创建自定义注释和注释处理器。
如果在 Scala 中不可能,有没有办法在 Scala 类中使用 Java 自定义注释处理器?
有人可以指出我正确的方向吗?
Scala中有宏注释
https://docs.scala-lang.org/overviews/macros/annotations.html
我猜这类似于Java中的编译时处理注释。
注释处理器可以用 Scala 编写。但注解必须用Java编写(scala注解不能注解Java代码)。并且注释处理器不会处理 Scala 源。Java编译时注释处理由Java编译器处理,它不能编译Scala源代码。
Scala 编译器不知道任何注释处理器。在 Scala 编译时注释处理是宏注释(类似地,它们可以处理 Scala 源,而不是 Java 源)。Scala 宏注释和 Java 注释处理器是两种完全不同的机制,它们协调地使用 Scala 源和 Java 源执行类似的操作。
因此,如果您想以类似的方式处理 Java 和 Scala 源代码,您将不得不重复工作。您必须创建处理 Java 源的注释处理器和宏注释,以执行与 Scala 源类似的操作。
这是创建构建器的示例。注释处理器在 中创建一个构建器target/scala-2.13/classes,宏注释在伴生对象内创建一个构建器。这是处理器和宏注释之间的区别:处理器可以生成代码但不能重写它(没有 Java 编译器内部1 2),宏注释可以重写代码,但只能在类及其同伴中重写。另一个区别是处理器生成 Java 源代码,而宏注释生成 Scala AST。
注释处理器/src/main/java/org/example/BuilderProperty.java
package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface BuilderProperty {
}
Run Code Online (Sandbox Code Playgroud)
注释处理器/src/main/resources/META-INF/services/javax.annotation.processing.Processor
org.example.BuilderProcessor
Run Code Online (Sandbox Code Playgroud)
注释处理器/src/main/scala/org/example/BuilderProcessor.scala
org.example.BuilderProcessor
Run Code Online (Sandbox Code Playgroud)
(由于某种原因println,.asScala, 并在处理过程中Option.when抛出NoSuchMethodError。)
注释处理器/src/main/scala/org/example/scalaBuilderProperty.scala
package org.example
//import com.google.auto.service.AutoService
import javax.annotation.processing._
import javax.lang.model.SourceVersion
import javax.lang.model.element.{Element, TypeElement}
import javax.lang.model.`type`.ExecutableType
import javax.tools.Diagnostic
import java.io.IOException
import java.io.PrintWriter
import java.util
import scala.collection.immutable
import scala.jdk.CollectionConverters._
import scala.util.Using
@SupportedAnnotationTypes(Array("org.example.BuilderProperty"))
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//@AutoService(Array(classOf[Processor])) // can't use AutoService because the processor is written in Scala, so using the file in META-INF
class BuilderProcessor extends AbstractProcessor {
override def process(annotations: util.Set[_ <: TypeElement], roundEnv: RoundEnvironment): Boolean = {
System.out.println("process")
// println("process") // java.lang.RuntimeException: java.lang.NoSuchMethodError: scala.Predef$.println(Ljava/lang/Object;)V //com.sun.tools.javac.main.Main.compile //sbt.internal.inc.javac.LocalJavaCompiler.run
// annotations.asScala.toSet[TypeElement].foreach { annotation => //java.lang.RuntimeException: java.lang.NoSuchMethodError: scala.jdk.CollectionConverters$.SetHasAsScala(Ljava/util/Set;)Lscala/collection/convert/AsScalaExtensions$SetHasAsScala;
new SetHasAsScala(annotations).asScala.toSet[TypeElement].foreach { annotation =>
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
val (setters: Set[Element @unchecked], otherMethods) = new SetHasAsScala(annotatedElements).asScala.toSet.partition(element =>
element.asType.asInstanceOf[ExecutableType].getParameterTypes.size == 1 &&
element.getSimpleName.toString.startsWith("set")
)
otherMethods.foreach(element =>
processingEnv.getMessager.printMessage(Diagnostic.Kind.ERROR,
"@BuilderProperty must be applied to a setXxx method with a single argument", element)
)
setters.headOption.foreach { head =>
val className = head.getEnclosingElement.asInstanceOf[TypeElement].getQualifiedName.toString
val setterMap = setters.map(setter =>
setter.getSimpleName.toString -> setter.asType.asInstanceOf[ExecutableType].getParameterTypes.get(0).toString
)
writeBuilderFile(className, setterMap)
}
}
true
}
@throws[IOException]
private def writeBuilderFile(className: String, setterMap: immutable.Set[(String, String)]): Unit = {
val lastDot = className.lastIndexOf('.')
val packageName = if (lastDot > 0) Some(className.substring(0, lastDot)) else None
// val packageName = Option.when(lastDot > 0)(className.substring(0, lastDot)) //java.lang.RuntimeException: java.lang.NoSuchMethodError: scala.Option$.when(ZLscala/Function0;)Lscala/Option; //com.sun.tools.javac.main.Main.compile //sbt.internal.inc.javac.LocalJavaCompiler.run
val simpleClassName = className.substring(lastDot + 1)
val builderClassName = className + "Builder"
val builderSimpleClassName = builderClassName.substring(lastDot + 1)
val builderFile = processingEnv.getFiler.createSourceFile(builderClassName)
Using(new PrintWriter(builderFile.openWriter)) { out =>
val packageStr = packageName.map(name => s"package $name;\n\n").getOrElse("")
out.print(
s"""${packageStr}public class $builderSimpleClassName {
|
| private $simpleClassName object = new $simpleClassName();
|
| public $simpleClassName build() {
| return object;
| }
|
|${ setterMap.map { case methodName -> argumentType =>
s""" public $builderSimpleClassName $methodName($argumentType value) {
| object.$methodName(value);
| return this;
| }
|""".stripMargin }.mkString("\n") }
|}
|""".stripMargin
)
}
}
}
Run Code Online (Sandbox Code Playgroud)
注释-用户/src/main/java/org/example/Person.java
package org.example
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
@compileTimeOnly("enable macro annotations")
class scalaBuilderProperty extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro BuilderPropertyMacro.impl
}
object BuilderPropertyMacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def modifyObject(cls: Tree, obj: Tree): Tree = (cls, obj) match {
case
(
q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }",
q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }"
) =>
val builder = TypeName(s"${tpname}Builder")
def isBuilderPropertyAnnotated(mods: Modifiers): Boolean = {
def removeMetaAnnotations(tpe: Type): Type = tpe match {
case tp: AnnotatedType => removeMetaAnnotations(tp.underlying)
case _ => tpe
}
def getType(tree: Tree): Type = c.typecheck(tree, mode = c.TYPEmode, silent = true).tpe
mods.annotations
.collect {
case q"new { ..$_ } with ..$parents { $_ => ..$_ }" => parents
}
.flatten
.map(t => removeMetaAnnotations(getType(t)))
.exists(_ =:= typeOf[BuilderProperty])
}
val setters = paramss.flatten.collect {
case q"$mods var $tname: $tpt = $_" if isBuilderPropertyAnnotated(mods) =>
val setter = TermName(s"set${tname.toString.capitalize}")
q"""def $setter(value: $tpt): $builder = {
this.`object`.$setter(value)
this
}"""
}
q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
class $builder {
private val `object`: $tpname = new $tpname()
def build: $tpname = this.`object`
..$setters
}
}"""
}
def modify(cls: Tree, obj: Tree): Tree = q"..${Seq(cls, modifyObject(cls, obj))}"
annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil =>
modify(cls, obj)
case (cls@q"$_ class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
modify(cls, q"object ${tpname.toTermName}")
case _ => c.abort(c.enclosingPosition, "@scalaBuilderProperty must annotate classes")
}
}
}
Run Code Online (Sandbox Code Playgroud)
注释-用户/target/scala-2.13/classes/org/example/PersonBuilder.java
package org.example;
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
@BuilderProperty
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
@BuilderProperty
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Run Code Online (Sandbox Code Playgroud)
注释-用户/src/main/scala/org/example/ScalaPerson.scala
// GENERATED JAVA SOURCE
//package org.example;
//
//public class PersonBuilder {
//
// private Person object = new Person();
//
// public Person build() {
// return object;
// }
//
// public PersonBuilder setAge(int value) {
// object.setAge(value);
// return this;
// }
//
// public PersonBuilder setName(java.lang.String value) {
// object.setName(value);
// return this;
// }
//
//}
Run Code Online (Sandbox Code Playgroud)
注释-用户/src/main/scala/org/example/Main.scala
package org.example
//import scala.annotation.meta.beanSetter
import scala.beans.BeanProperty
@scalaBuilderProperty
case class ScalaPerson(
@BeanProperty
@(BuilderProperty /*@beanSetter @beanSetter*/)
var age: Int = 0,
@BeanProperty
@(BuilderProperty /*@beanSetter*/)
var name: String = ""
)
// GENERATED SCALA AST (-Ymacro-debug-lite)
//object ScalaPerson extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// class ScalaPersonBuilder extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// private val `object`: ScalaPerson = new ScalaPerson();
// def build: ScalaPerson = this.`object`;
// def setAge(value: Int): ScalaPersonBuilder = {
// this.`object`.setAge(value);
// this
// };
// def setName(value: String): ScalaPersonBuilder = {
// this.`object`.setName(value);
// this
// }
// }
// };
// ()
//}
Run Code Online (Sandbox Code Playgroud)
构建.sbt
package org.example
object Main {
def main(args: Array[String]): Unit = {
val person = new PersonBuilder()
.setAge(25)
.setName("John")
.build
println(person)//Person{age=25, name='John'}
val person1 = new ScalaPerson.ScalaPersonBuilder()
.setAge(25)
.setName("John")
.build
println(person1)//ScalaPerson(25,John)
}
}
Run Code Online (Sandbox Code Playgroud)
sbt clean compile annotation-user/run
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
589 次 |
| 最近记录: |