Kotlin Annotation:获取接口内部注释属性

Kotlin Annotation: Get interface inner annotated properties

概览

我正在尝试构建我的第一个注释处理器,并且进展顺利。我正在创建一个代码生成处理器,它基本上为定义的接口生成 SharedPreferences。我当前的注释是 SharedPrefsDefault@SharedPrefs 通知处理器该文件是一个接口,需要一个生成的 prefs 文件。 @Default 是我在界面中注释的一些属性,以便让处理器知道将默认值设置为什么。可以有多个文件定义为 @SharedPrefs.

实施

我目前使用以下代码获取用 @SharedPrefs@Default 注释的文件列表:

roundEnv.getElementsAnnotatedWith(SharedPrefs::class.java)?.forEach { element ->
  ...
  roundEnv.getElementsAnnotatedWith(Default::class.java)?.forEach {
    ...
  }
}

@SharedPrefs:

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class SharedPrefs(val showTraces: Boolean = false)

@Default:

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.PROPERTY)
annotation class Default(val defaultValue: String = "[null]")

正在使用:

@SharedPrefs
interface InstanceSettings {
    var wifiPassword: String
    @Default("20")
    var count: Int
}

问题

照原样,内部 forEach 是 returning all 属性 all 文件 all@Default。代码生成工作正常,但这似乎不是最好的方法。有没有办法只获取我正在处理的当前 @SharedPrefs class 的属性 w/in?例如,类似于:

roundEnv.getElementsAnnotatedWith(SharedPrefs::class.java)?.forEach { element ->
  ...
  element.methodToGetAnnotatedProperties(Default::class.java)?.forEach {
    ...
  }
}

* 编辑 *

我发现,对于我注释的方法

@SomeAnnotation
fun someMethod()

我可以遍历 element.enclosingElements 并使用 enclosingElement.getAnnotation(<SomeAnnotation::class.java>) 查找它是否有注释。不幸的是,如果我在这里错了请纠正我,我不能用带有 AnnotationTarget.FIELD 的注释来注释接口变量,因为它们没有支持字段,因为它是一个接口并且没有实现。因此,我使用 AnnotationTarget.PROPERTY。当遍历封闭元素时,所有变量都显示为 getter 和 setter。对于上面 InstanceSettings 的示例,我得到 getWifiPasswordsetWifiPasswordgetCountsetCount。我 没有 只得到 wifiPasswordcount 的元素。调用 getAnnotation(Default::class.java) 将始终 return 空这些,因为它们是生成的。

此外,任何人都知道的关于注释处理的任何其他资源都将在评论中得到很好的补充。谢谢!

在 java 中提出了类似的问题 here

这里是创建扩展函数的方法,它将为您完成工作!

roundEnv.getElementsAnnotatedWith(SharedPrefs::class.java)?.forEach { element ->
    ...
    roundEnv.findNestedElements(SharedPrefs::class, Default::class)?.forEach {
        ...
    }
}

fun <T> RoundEnvironment.findNestedElements(parent: KClass<*>, child: KClass<T>): List<Element>? {
    val childs = this.getElementsAnnotatedWith(child.java)
    val list = ArrayList<Element>()
    for (element in childs)
    {
        if (element.getEnclosingElement().getAnnotation(parent.java) != null)
        {
            list.add(element)
        }
    }
    return if(list.isEmpty()) null else list
}

所以我想我找到了解决方案:

InterfaceSettings为例:

@SharedPrefs
interface InstanceSettings {
    var wifiPassword: String
    @Default("20")
    var count: Int
}

生成的简化 Java 代码是:

public interface InstanceSettings {
   @NotNull
   String getWifiPassword();

   void setWifiPassword(@NotNull String var1);

   int getCount();

   void setCount(int var1);

   public static final class DefaultImpls {
      public static void count$annotations() {
      }
   }
}

调用roundEnv.getElementsAnnotatedWith(Default::class.java)时返回的元素是count$annotations。因此,当我调用 getEnclosingElement() 时,会返回 DefaultImpls。如果我再次调用 getEnclosingElement() ,将返回 InstanceSettings。我实际上对该结果调用了 getSimpleName(),并将其与用 @SharedPrefs 注释的项目中的类名进行比较,看它是否是 child:

roundEnv.getElementsAnnotatedWith(SharedPrefs::class.java)?.forEach { element ->
  val className = element.simpleName.toString()
  val defaults = roundEnv.getElementsAnnotatedWith(Default::class.java)?.filter {
    // it.enclosingElement -> DefaultImpls
    // DefaultImpls.enclosingElement -> com.mypackage.InstanceSettings
    it.enclosingElement.enclosingElement.simpleName == className
  }
}