gpt4 book ai didi

java - 有没有办法在 Scala 中创建自定义注释并编写自定义注释处理器来验证注释?

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:05:12 24 4
gpt4 key购买 nike

我一直在学习注解以及注解处理器是什么。我正在查看 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 源代码类似的事情的宏注解。

这是一个 creating 的例子一个 build 者。注释处理器在 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 {
}

注释处理器/src/main/resources/META-INF/services/javax.annotation.processing.Processor

org.example.BuilderProcessor

annotation-processor/src/main/scala/org/example/BuilderProcessor.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
)
}
}
}

(由于某些原因 println.asScalaOption.when 在处理过程中抛出 NoSuchMethodError。)

annotation-processor/src/main/scala/org/example/scalaBuilderProperty.scala

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")
}
}
}

annotation-user/src/main/java/org/example/Person.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 + '\'' +
'}';
}
}

annotation-user/target/scala-2.13/classes/org/example/PersonBuilder.java

  // 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;
// }
//
//}

annotation-user/src/main/scala/org/example/ScalaPerson.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
// }
// }
// };
// ()
//}

annotation-user/src/main/scala/org/example/Main.scala

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)
}
}

build.sbt

ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "2.13.10"

lazy val `annotation-processor` = project
.settings(
libraryDependencies ++= Seq(
// "com.google.auto.service" % "auto-service" % "1.0.1", //https://github.com/google/auto/tree/master/service
// "org.kohsuke.metainf-services" % "metainf-services" % "1.9", //https://github.com/kohsuke/metainf-services

scalaOrganization.value % "scala-reflect" % scalaVersion.value,
),
scalacOptions ++= Seq(
"-feature",
"-Ymacro-annotations",
),
javacOptions ++= Seq(
"-proc:none", // otherwise META-INF should be moved into annotation-processor-metainf or annotation-user
),
)

//lazy val `annotation-processor-metainf` = project
// .dependsOn(`annotation-processor`)

lazy val `annotation-user` = project
.settings(
compileOrder := CompileOrder.JavaThenScala, // can't use Scala in Java, but otherwise Main.scala should be moved into core
scalacOptions ++= Seq(
"-Ymacro-annotations",
"-Ymacro-debug-lite",
),
)
.dependsOn(`annotation-processor`)
// .dependsOn(`annotation-processor-metainf`)


//lazy val core = project
// .dependsOn(`annotation-user`)
sbt clean compile annotation-user/run

关于java - 有没有办法在 Scala 中创建自定义注释并编写自定义注释处理器来验证注释?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58443039/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com