Статьи

Повторный код

Вступление

Обычно не хорошо иметь код для копирования / вставки в нашем Java-приложении, но иногда это неизбежно. Например, проект License3j предоставляет метод isXXX в классе Feature для каждого поддерживаемого им типа XXX . В этом случае мы можем сделать не лучше, чем написать

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public boolean isBinary() {
        return type == Type.BINARY;
    }
 
    public boolean isString() {
        return type == Type.STRING;
    }
 
    public boolean isByte() {
        return type == Type.BYTE;
    }
 
    public boolean isShort() {
        return type == Type.SHORT;
    }
 
and so on

для каждого типа функции приложение поддерживает. И там есть несколько типов: двоичные, строковые, байтовые, короткие, инт, длинные, плавающие, двойные, BigInteger, BigDecimal, Date, UUID. Набирать все очень похожие методы не только скучно, но и подвержено ошибкам. Очень немногие люди способны делать такие повторяющиеся задачи. Чтобы избежать этого, мы можем использовать инфраструктуру Java :: Geci и в качестве простейшего решения мы можем использовать генератор Iterate.

POM зависимость

Чтобы использовать генератор, мы должны добавить зависимость

1
2
3
4
5
6
<dependency>
    <groupId>com.javax0.geci</groupId>
    <artifactId>javageci-core</artifactId>
    <scope>test</scope>
    <version>1.4.0</version>
</dependency>

Библиотека выполняется только во время выполнения тестов, поэтому ее использование не подразумевает каких-либо дополнительных зависимостей. Тот, кто хочет использовать библиотеку License3j, не должен использовать Java :: Geci. Это всего лишь инструмент разработки, используемый в области test .

Модульный тест работает

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

01
02
03
04
05
06
07
08
09
10
11
12
@Test
    @DisplayName("run Iterate on the sources")
    void runIterate() throws IOException {
        Geci geci = new Geci();
        Assertions.assertFalse(
            geci.register(Iterate.builder()
                              .define(ctx -> ctx.segment().param("TYPE", ctx.segment().getParam("Type").orElse("").toUpperCase()))
                              .build())
                .generate()
            , geci.failed()
        );
    }

Он создает объект Geci , создает экземпляр генератора с использованием компоновщика, а затем вызывает generate() в сконфигурированном каркасном объекте Geci. Вызов define() пока выглядит немного загадочно. Мы проясним это позже.

Подготовка исходного кода

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

1
2
3
4
5
6
7
8
9
/* TEMPLATE
    LOOP Type=Binary|String|Byte|Short|Int|Long|Float|Double|BigInteger|BigDecimal|Date|UUID
    public boolean is{{Type}}() {
        return type == Type.{{TYPE}};
    }
 
     */
    //<editor-fold id="iterate">
    //</editor-fold>

Когда мы запустим генератор через каркас, он оценит шаблон для каждого значения заполнителя Type и заменит каждый {{Type}} действительным значением. Полученный код будет вставлен в сегмент редактора с id «iterate».

Глядя на шаблон, вы можете видеть, что есть заполнитель {{TYPE}} , который не определен в списке. Вот тут-то и возникает тест unite define() . Он определяет потребителя, который использует контекст, и, используя этот контекст, читает фактическое значение Type , создает версию значения в верхнем регистре и присваивает его параметру сегмента с именем TYPE .

Вообще то так и есть. Существуют и другие функции, использующие генератор, такие как определение нескольких значений за итерацию, назначенных различным заполнителям, экранирование или пропуск строк и так далее. О тех, что здесь, есть выдержка из документации, которую вы можете прочитать в обновленном и полном виде. Https://github.com/verhas/javageci/blob/master/ITERATE.adoc

Выдержка из документации

В исходных файлах Java, где вы хотите использовать генератор, вы должны аннотировать класс аннотацией @Geci("iterate") .
@Iterate этого вы также можете использовать аннотацию @Iterate , которая определяется в
javageci-core-annotations . Это проинструктирует платформу Geci, что вы хотите использовать генератор iterate в данном классе.

TEMPLATE

Шаблон начинается после строки /\*TEMPLATE или TEMPLATE .
Могут быть пробелы до и после и между /* и словом
TEMPLATE но на линии не должно быть ничего другого.
Когда генератор видит такую ​​строку, он начинает собирать следующие строки в качестве содержимого шаблона.

Конец шаблона обозначается линией, в которой есть */ и больше ничего (кроме пробелов).

Содержимое шаблона может содержать параметры между {{ и }}
символы так же, как это используется в программе шаблонов усов.
(Генератор не использует усы, обработка шаблонов проще.)

LOOP

При сборе строк шаблона некоторые строки распознаются как определения параметров шаблона. Эти строки не попадают в ствол шаблона. (Имена команд в этих строках всегда заглавные.)

Как вы могли видеть во введении, строка

1
LOOP type=int|long|short

не является частью текста шаблона. Он указывает генератору перебирать типы и устанавливать для параметра {{type}} в тексте значение int первое, long второе и short последнее. Таким образом, вы можете перебирать несколько значений одного параметра.

Более сложному шаблону может потребоваться более одного параметра. В этом случае вы можете перечислить их в строке LOOP как

1
LOOP type,var=int,aInt|long,aLong|short,aShort

Это скажет генератору установить параметр {{type}} таким же образом, как и ранее для трех итераций, но в то же время также установить для параметра {{var}} значение aInt в первом цикле, значение aLong во втором цикле и aShort в последнем цикле.

Если список значений слишком длинный, возможно разделить список на несколько строк LOOP . В этом случае, однако, переменные должны повторяться во второй, третьей и т. Д. LOOP .
Их порядок может быть другим, но если в некоторых строках LOOP есть переменная undefined, заполнитель, ссылающийся на нее, будет разрешен и останется в форме {{placeholder}} .

Приведенный выше пример также можно записать

1
2
3
LOOP type,var=int,aInt
    LOOP var,type=aLong,long
    LOOP type,var=short,aShort

и это приведет к тем же значениям, что и повторенный выше LOOP :

1
LOOP type,var=int,aInt|long,aLong|short,aShort

editor-fold умолчанию

Шаблоны обрабатываются от начала файла до конца, и сгенерированный код также готовится в этом порядке.
Содержимое сгенерированного кода будет вставлено в сегмент editor-fold который следует непосредственно за шаблоном. Хотя, таким образом, id
Сегмент editor-fold не очень интересен, вы должны указать уникальный id для каждого сегмента. Это ограничение платформы Java :: Geci.

Расширенное использование

EDITOR-FOLD-ID

Может случиться так, что у вас есть несколько шаблонов, зацикливающихся на разных значениях, и вы хотите, чтобы результат помещался в один и тот же editor-fold
сегмент. Это возможно с помощью EDITOR_FOLD_ID .

В следующем примере

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package javax0.geci.iterate.sutclasses;
 
public class IterateOverMultipleValues {
    /* TEMPLATE
    {{type}} get_{{type}}Value(){
      {{type}} {{variable}} = 0;
      return {{variable}};
    }
 
    LOOP type,variable=int,i|long,l|short,s
    EDITOR-FOLD-ID getters
     */
    //
            // nothing gets here
    //
 
    //
    int get_intValue(){
      int i = 0;
      return i;
    }
 
    long get_longValue(){
      long l = 0;
      return l;
    }
 
    short get_shortValue(){
      short s = 0;
      return s;
    }
 
    //
}

сгенерированный код попадает в editor-fold с именем id
getters хотя это не тот, который следует за определением шаблона.

Используйте эту функцию для отправки сгенерированного кода в один сегмент из нескольких итерационных шаблонов. Обычно рекомендуется хранить шаблон и сегмент вместе.

ESCAPE и SKIP

Конец шаблона обозначается строкой */ . Это по сути конец комментария. Что произойдет, если вы захотите включить комментарий, например, JavaDoc в шаблон. Вы можете написать символы */ в конце строк комментария, в которых все еще есть символы. Это решение не элегантно, и по сути это обходной путь.

Чтобы иметь строку, которая является точно закрытием комментария, или просто любой строкой, которая будет интерпретироваться обработкой шаблона, например, строкой LOOP вас должна быть строка, не содержащая ничего кроме ESCAPE в предыдущей строке. Это скажет обработке шаблона включить следующую строку в текст шаблона и продолжить нормальную обработку строки после.

Точно так же у вас может быть строка SKIP чтобы полностью игнорировать следующую строку.
Используя эти две команды, вы можете включить в шаблон все что угодно.

Пример показывает, как вы можете включить комментарий JavaDoc в шаблон:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package javax0.geci.iterate.sutclasses;
 
public class SkippedLines {
    /* TEMPLATE
    /**
     * A simple zero getter serving as a test example
     * @return zero in the type {{type}}
    ESCAPE
     */
    // SKIP
    /*
    {{type}} get_{{type}}Value(){
      {{type}} {{variable}} = 0;
      return {{variable}};
    }
    LOOP type,variable=int,i|long,l|short,s
    EDITOR-FOLD-ID getters
     */
    //
    /**
     * A simple zero getter serving as a test example
     * @return zero in the type int
     */
    int get_intValue(){
      int i = 0;
      return i;
    }
    /**
     * A simple zero getter serving as a test example
     * @return zero in the type long
     */
    long get_longValue(){
      long l = 0;
      return l;
    }
    /**
     * A simple zero getter serving as a test example
     * @return zero in the type short
     */
    short get_shortValue(){
      short s = 0;
      return s;
    }
    //
}

Шаблон начинается с комментария, и комментарий может фактически содержать любой другой стартовый комментарий. Комментарии Java не являются вложенными. Однако конец шаблона — это строка, содержащая строку */ . Мы хотим, чтобы эта строка была частью шаблона, поэтому мы предшествуем ей строкой
ESCAPE поэтому он не будет интерпретироваться как конец шаблона. С другой стороны, для Java это заканчивается комментарием. Чтобы продолжить шаблон, мы должны вернуться в режим комментариев, поскольку мы не хотим, чтобы компилятор Java обрабатывал шаблон как код. (Не в последнюю очередь, потому что шаблон, использующий заполнители, вероятно, не является синтаксически правильным фрагментом кода Java.) Нам нужна новая строка /* , которую мы не хотим вставлять в шаблон.
Поэтому этой строке предшествует строка, содержащая // SKIP . (Пропускать строки можно не обязательно // перед командой.)

Результат вы можете увидеть в сгенерированном коде. Все методы имеют соответствующую документацию JavaDoc.

SEP1 и SEP2

Зацикливаясь на значениях, вы должны разделить имена заполнителей , и | список значений. Например, приведенный выше образец содержит

1
LOOP type,variable=int,i|long,l|short,s

два имени заполнителя type и variable и три значения для каждого.
Заполнители не должны содержать специальные символы, и лучше всего, если они являются стандартными идентификаторами. Однако значения могут содержать запятую или вертикальную черту. В этом случае вы можете переопределить строку (не только один символ), которую команда шаблона LOOP может использовать вместо строк из одного символа , и | ,

Например, строка

1
SEP1 /

говорит, что имена и значения должны быть разделены / вместо только одного и

1
SEP2 &

список значений должен быть разделен одним символом &
строка. SEP1 и SEP2 будут иметь эффект, только если они предшествуют
Команда LOOP и они действуют только для шаблона, в котором они используются. Следуя приведенным выше командам, пример LOOP будет выглядеть следующим образом

1
LOOP type/variable=int/i&long/l&short/s

Таким образом, ничто не мешает нам добавить еще один список значений

1
LOOP type/variable=int/i&long/l&short/s&byte,int/z

что в конечном итоге приведет к синтаксической ошибке с примером шаблона, но демонстрирует точку, переопределяющую разделители имени и списка значений.

конфигурация

В генераторе реализованы инструменты конфигурирования, поддерживаемые фреймворком Geci, и все параметры настраиваются. Вы можете переопределить регулярные выражения, соответствующие шаблону start, end, skip и т. Д. В строках в модульном тесте, где создается объект генератора, в аннотации класса или в параметрах сгиба редактора.

навынос

Генератор итераций — чрезвычайно простой в использовании генератор для создания кода, который повторяется. Это также главная опасность: вы должны быть достаточно сильны, чтобы найти лучшее решение и использовать его только тогда, когда это лучшее решение.

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Повторный код

Мнения, высказанные участниками Java Code Geeks, являются их собственными.