Процессор аннотаций для создания класса типа ломбок

Привет, я начинаю изучать обработку аннотаций в 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;
    }

}

Вы хотите сказать, что ваш процессор аннотаций (:processor) не может найти класс в коде, который он обрабатывает (:sample)? Если да, то этого и следовало ожидать, поскольку обрабатываемый код в настоящее время компилируется. Вам необходимо использовать API Element и TypeMirror.

Slaw 03.04.2024 19:24

@Slaw Я добавил фрагмент кода, который я создал, чтобы попробовать получить класс

Jose Luiz Junior 03.04.2024 20:01

Вы пытаетесь получить java.lang.Class, связанный с аннотированным классом, который в данный момент обрабатывается. Это невозможно, поскольку класс еще не скомпилирован. Другими словами, это не проблема конфигурации проекта, а проблема неправильного использования API. Используйте API javax.lang.model.*, чтобы получить необходимую информацию.

Slaw 03.04.2024 21:01

@Slaw Я меняю свой вопрос, спасибо за помощь, чтобы больше понять об процессоре аннотаций, я почти закончил свой код :)

Jose Luiz Junior 04.04.2024 23:53

Обработка аннотаций не поддерживает изменение обрабатываемых классов. Вы можете создавать только новые классы и/или ресурсы. Ломбок обходит это ограничение путем «взлома» внутренних компонентов компилятора для изменения AST. Это делает реализации специфичными для компилятора (например, Lombok работает только с javac и ECJ, если я не ошибаюсь; конечно, это единственные два компилятора Java, о которых я знаю).

Slaw 05.04.2024 00:33
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы закрыть этот вопрос, я закончил создание класса построителя в другом файле с использованием обработчика аннотаций. Я подумал, как я мог бы изменить файлы генерации и создать внутренний построитель, например ломбок, но в комментариях 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();
    }

}

Другие вопросы по теме