Привет, я начинаю изучать обработку аннотаций в Java с использованием gradle, следуйте многопроектной структуре:
ROOT
|
|-sample # contains main classes
|-annotation # annotations read by processor
|-processor # processor to generate code
Я пытаюсь создать аннотацию, похожую на ломбок @Builder, я могу сгенерировать класс в сборке/сгенерированном/источниках/аннотации/java... но если я попытаюсь сгенерировать класс в том же пакете в аннотированном классе, но в сборке задача не удалась, говоря, что я пытаюсь заменить файл
образец:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(project(":annotation"))
compileOnly(project(":annotation"))
implementation(project(":processor"))
annotationProcessor(project(":processor"))
}
процессор:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
val autoServiceVersion = "1.1.1"
dependencies {
compileOnly("com.google.auto.service:auto-service:$autoServiceVersion")
annotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion")
implementation("com.squareup:javapoet:1.13.0")
}
аннотация:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
}
settings.gradle.kts
rootProject.name = "annotation-processor"
include("processor")
include("sample")
include("annotation")
следуйте моему коду:
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
Map<String, TypeMirror> fields =element.getEnclosedElements().stream()
.filter(e -> e.getKind() == FIELD)
.collect(Collectors.toMap(e -> e.getSimpleName().toString(), Element::asType));
final var packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
final var className = element.getSimpleName().toString();
final var fullNameClass = packageName + "." + className;
final var typeSpec = new BuilderCreate().create(packageName, className, fields);
var javaFile = JavaFile.builder(packageName, typeSpec)
.indent(" ")
.build();
try(
var out = new PrintWriter(processingEnv.getFiler()
.createSourceFile(className)
.openWriter()
)
){
out.println(javaFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return true;
}
}
@Slaw Я добавил фрагмент кода, который я создал, чтобы попробовать получить класс
Вы пытаетесь получить java.lang.Class, связанный с аннотированным классом, который в данный момент обрабатывается. Это невозможно, поскольку класс еще не скомпилирован. Другими словами, это не проблема конфигурации проекта, а проблема неправильного использования API. Используйте API javax.lang.model.*, чтобы получить необходимую информацию.
@Slaw Я меняю свой вопрос, спасибо за помощь, чтобы больше понять об процессоре аннотаций, я почти закончил свой код :)
Обработка аннотаций не поддерживает изменение обрабатываемых классов. Вы можете создавать только новые классы и/или ресурсы. Ломбок обходит это ограничение путем «взлома» внутренних компонентов компилятора для изменения AST. Это делает реализации специфичными для компилятора (например, Lombok работает только с javac и ECJ, если я не ошибаюсь; конечно, это единственные два компилятора Java, о которых я знаю).




Чтобы закрыть этот вопрос, я закончил создание класса построителя в другом файле с использованием обработчика аннотаций. Я подумал, как я мог бы изменить файлы генерации и создать внутренний построитель, например ломбок, но в комментариях Slaw помог мне понять, что это более сложно ( и я впервые использую обработчик аннотаций) следуйте моему окончательному коду:
@SupportedAnnotationTypes("br.com.dio.annotation.Builder")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
Map<String, TypeMirror> fields =element.getEnclosedElements().stream()
.filter(e -> e.getKind() == FIELD)
.collect(Collectors.toMap(e -> e.getSimpleName().toString(), Element::asType));
final var packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
final var className = element.getSimpleName().toString();
final var builderName = className + "Builder";
final var typeSpec = new BuilderCreate().create(packageName, className, builderName,fields);
var javaFile = JavaFile.builder(packageName, typeSpec)
.indent(" ")
.build();
try(
var out = new PrintWriter(processingEnv.getFiler()
.createSourceFile(className + "Builder")
.openWriter()
)
){
out.write(javaFile.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return true;
}
}
public class BuilderCreate {
public TypeSpec create(final String packageName, final String className,
final String builderName,final Map<String, TypeMirror> fields){
var generatedBuilderClass = TypeSpec.classBuilder(builderName)
.addModifiers(PUBLIC);
fields.forEach((k, v) -> generatedBuilderClass.addField(TypeName.get(v), k, PRIVATE));
fields.forEach((k, v) -> generatedBuilderClass.addMethod(buildBuilderSetter(
packageName,
builderName,
k,
TypeName.get(v))
)
);
var buildMethod = MethodSpec.methodBuilder("build")
.addModifiers(PUBLIC)
.returns(ClassName.get(packageName, className))
.addStatement("var target = new $N()", className);
fields.keySet().forEach(f -> buildMethod.addStatement(
"target.set$N($N)",
f.substring(0,1).toUpperCase() + f.substring(1, f.length()),
f)
);
buildMethod.addStatement("return target");
return generatedBuilderClass.addMethod(buildMethod.build())
.build();
}
private MethodSpec buildBuilderSetter(final String packageName, final String name, final String param, final TypeName type){
return MethodSpec.methodBuilder(param)
.addModifiers(PUBLIC)
.returns(ClassName.get(packageName, name))
.addParameter(type, param, FINAL)
.addStatement("this.$N = $N", param, param)
.addStatement("return this")
.build();
}
}
Вы хотите сказать, что ваш процессор аннотаций (
:processor) не может найти класс в коде, который он обрабатывает (:sample)? Если да, то этого и следовало ожидать, поскольку обрабатываемый код в настоящее время компилируется. Вам необходимо использовать APIElementиTypeMirror.