Автоматически генерировать методы замены

Я сталкиваюсь с большим количеством шаблонного кода при создании языковых файлов для приложения, которое я делаю. В настоящее время у меня есть класс со всеми языковыми строками, а затем я использую отражение для записи этих строк в файл.

Я довольно часто сталкиваюсь с тем, что у меня есть определенные заполнители в моих строках, которые я хочу заменить, например, у меня может быть такая строка:

public static String USER_INFO = "Username: %name% money: %balance%";

Чего я хотел бы добиться, так это создать несколько методов на основе аннотаций, например, я могу генерировать геттеры/сеттеры и другие методы с помощью ломбока. Основываясь на приведенной выше строке, у меня была бы аннотация под названием Arguments (правильно должна была быть названа Replacers или что-то более значимое), как показано здесь:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Arguments {
  String[] value();
}

Я хотел бы добавить аннотацию следующим образом:

@Arguments(
        value = {"%balance%", "%name%"}
)
public static String USER_INFO = "Username: %name% - money: %balance%";

и автоматически сгенерируйте следующие методы замены:

public static String USER_INFONameReplacement(String name) {
    return USER_INFO.replace("%name%", name);
}
public static String USER_INFOAllReplacement(String name, String balance) {
    return USER_INFO.replace("%name%", name).replace("%balance%", balance);
}
public static String USER_INFOBalanceReplacement(String balance) {
    return USER_INFO.replace("%balance%", balance);
}

После некоторых поисков я попытался реализовать AbstractProcessor в таком классе:

@SupportedAnnotationTypes(
    {"io.github.freakyville.configHelper.annotations.Arguments"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class SuggestProcessor extends AbstractProcessor {

@Override
public synchronized void init(ProcessingEnvironment env) {
}

@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
    for (TypeElement annoation : annoations) {
        Set<? extends Element> annotatedElements = env.getElementsAnnotatedWith(annoation);
        Map<Boolean, List<Element>> annotatedFields = annotatedElements.stream().collect(
                Collectors.partitioningBy(element ->
                        ((ArrayType) element.asType()).getComponentType().getClass().equals(PrimitiveType.class)));
        List<Element> setters = annotatedFields.get(true);
        if (setters.isEmpty()) {
            continue;
        }
        String className = ((TypeElement) setters.get(0)
                .getEnclosingElement()).getQualifiedName().toString();

        Map<String, List<String>> setterMap = setters.stream().collect(Collectors.toMap(
                setter -> setter.getSimpleName().toString(),
                setter -> Arrays.asList(setter.getAnnotation(Arguments.class).value()))
        );
        try {
            writeBuilderFile(className, setterMap);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return true;
}

private void writeBuilderFile(
        String className, Map<String, List<String>> setterMap)
        throws IOException {

    String packageName = null;
    int lastDot = className.lastIndexOf('.');
    if (lastDot > 0) {
        packageName = className.substring(0, lastDot);
    }

    String builderSimpleClassName = className
            .substring(lastDot + 1);

    JavaFileObject builderFile = processingEnv.getFiler()
            .createSourceFile(className);

    try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {

        if (packageName != null) {
            out.print("package ");
            out.print(packageName);
            out.println(";");
            out.println();
        }

        out.print("public class ");
        out.print(builderSimpleClassName);
        out.println(" {");
        out.println();

        setterMap.forEach((key, orgArgNames) -> {

            for (int i = 0; i < orgArgNames.size(); i++) {
                List<String> subList = orgArgNames.subList(0, i + 1);
                List<String> argNames = subList.stream().map(v -> v.replace("%", "") + "Replacement").collect(Collectors.toList());
                List<String> argsWithTypes = argNames.stream().map(v -> "String " + v).collect(Collectors.toList());
                String argumentList = "(" + String.join("", argsWithTypes).substring(0, argsWithTypes.size() - 3) + ")";
                String methodName;

                if (orgArgNames.size() <= 1) {
                    methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "")).collect(Collectors.joining(""));
                } else {
                    methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "").substring(0, 1).toUpperCase() + v.substring(1)).collect(Collectors.joining(""));
                }

                out.print("    public static ");
                out.print(methodName);
                out.print(argumentList);
                out.println("{");
                StringBuilder replaceSB = new StringBuilder();
                replaceSB.append(key);
                for (int i1 = 0; i1 < subList.size(); i1++) {
                    replaceSB
                            .append(".replace(")
                            .append("\"")
                            .append(subList.get(i))
                            .append("\"")
                            .append(",")
                            .append(argNames.get(i))
                            .append(")");

                }
                String replace = replaceSB.toString();
                out.println("return " + replace + ";");
                out.println("}");
                out.println("");
            }
        });

        out.println("}");
    }
}


}

Но я не могу заставить его зарегистрировать его?

Итак, мой первый вопрос: подойдет ли AbstractProcessor, если я хочу этого добиться? Если нет, то как? если да, то почему это не регистрируется? Я использую IntelliJ и зашел в настройки -> компилятор -> build-> и изменил процессоры аннотаций, чтобы включить, и установить путь процессора к моему SuggestProcessor.

Вероятно, это хороший вариант использования ByteBuddy. Ответ Этот может быть полезен.

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

Ответы 2

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

Плагины Java Annotation Processing (APT) предназначены для генерации кода на основе других классов. Эти классы попадают в сгенерированную папку с исходными кодами, которая затем также компилируется. Эти подключаемые модули APT обнаруживаются в конфигурации инструмента classpath/build и также запускаются компилятором IntelliJ. Имейте в виду: APT предназначен для генерации сгенерированного исходного кода, а вовсе не для замены существующих классов. Единственная причина, по которой Lombok все еще может это делать, заключается в том, что они проникают очень глубоко в компилятор и, таким образом, могут манипулировать AST классов при компиляции.

Поскольку этот подход в значительной степени противоречив и подвержен ошибкам в будущих версиях Java, крайне маловероятно, что кто-либо когда-либо даже попытается создать инфраструктуру замены класса на основе APT или расширение Lombok, способное сделать это (не было это связано с тем, что Lombok — единственный инструмент, который можно считать «каркасом» для такого типа использования APT, а сам Lombok вовсе не расширяемый).

В заключение: APT, вероятно, правильный путь, но вашему процессору придется создать новый класс, а не пытаться модифицировать существующий.

Пример того, как должен быть создан обработчик аннотаций, вы можете посмотреть в следующем репозитории: https://github.com/galberola/java-apt-simple-пример

Я не уверен, почему ваш текущий процессор аннотаций неправильно связан с вашим компилятором. Если вы используете Maven, вы можете попытаться установить артефакт для своего процессора локально и добавить его в качестве зависимости компиляции к другому проекту. Не забудьте также зарегистрировать класс в качестве процессора аннотаций в вашем компиляторе, пример проекта, на который я ссылался, делает это здесь: https://github.com/galberola/java-apt-simple-example/blob/master/example/pom.xml#L29-L31. Эту же конфигурацию можно применить и к другим системам сборки.

В Java нет реального способа модифицировать классы при компиляции, поэтому, если вы действительно должны иметь метод в том же классе, то это, к сожалению, означает, что это невозможно сделать.

Вместо фактического создания файла и записи в него вы можете изменить абстрактное синтаксическое дерево (AST), как это делает Lombok. Это не рекомендуется, и разные компиляторы реализуют AST по-разному, но вы можете расширить исходный код Lombok из github (https://github.com/rzwitserloot/ломбок) и создать обработчик аннотаций, если хотите. Тем не менее, это немного сложно, поэтому убедитесь, что вам это действительно нужно.

Я не правильно прочитал ваш вопрос, извините. Чтобы зарегистрировать его, вы хотите создать каталог META-INF\services в проекте, который использует аннотацию и процессор аннотаций. В этом каталоге создайте текстовый файл с именем «javax.annotation.processing.Processor», который содержит имя процессора, например mypackage.SuggestProcessor. Если вы решили использовать java 9, вы также можете объявить процессор в файле module-info. Модуль процессора должен включать «предоставляет javax.annotation.processing.Processor что-то.SuggestProcessor», а модуль, который использует аннотацию, должен включать «использует javax.annotation.processing.Processor». Вот как javac регистрирует процессоры аннотаций.

Спасибо за ответ - однако могу ли я расширить ломбок и создать обработчик аннотаций самостоятельно в intellij? Я прочитал здесь: baeldung.com/lombok-custom-аннотация, что он должен поддерживаться плагином intellij, который не подходит для меня, не так ли?

Sumsar1812 08.08.2019 02:31

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