-
Notifications
You must be signed in to change notification settings - Fork 56
StringTemplate
String template (http://www.stringtemplate.org/) (далее ST) - это библиотека для обработки шаблонов. ST является подпроектом в составе Antlr (http://www.antlr.org/). Собственно Antlr использует ST для генерации кода. ST был специально реализован для обработки шаблонов и генерации кода.
Создадим простой проект для иллюстрации работы ST.
Добавим зависимость ST в pom.xml
.
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>4.0.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
Добавим класс с методом main
.
package stringtemplate.learn;
import org.stringtemplate.v4.ST;
public class Main {
public static void main(String[] args) {
ST st = new ST("Hello, <place>!");
st.add("place", "world");
System.out.println(st.render());
}
}
Выполним приложение.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
Hello, world!
Как видим атрибут был заменён на строку "world".
ST отличается от других шаблонизаторов тем, что позволяет описывать шаблоны с параметрами.
Например, создадим шаблон для вывода кода объявление поля в Java. В качестве параметров будет передавать имя и тип поля.
Шаблон будет иметь следующий вид:
fieldDeclaration(type, name) ::= <<
protected <type> <name>;
>>
Таким образом, был объявлен шаблон с именем fieldDeclaration
с 2-мя параметрами:
type
name
Описание шаблона имеет следующий вид:
ИМЯ_ШАБЛОНА(ПАРАМЕТРЫ) ::= <<
МНОГОСТРОЧНЫЙ_ШАБЛОН
>>
Так как мы создаём не "плоский" шаблон, а набор шаблонов, то нам необходимо внести ряд изменений для поддержки групп шаблонов.
Для начала объявим объект типа STGroupTemplate
, который поддерживает описание групп шаблонов. Получение объекта шаблона осуществляется с помощью метода getInstanceOf
куда передаётся название шаблона.
Результирующий код будет выглядеть так:
package stringtemplate.learn;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupString;
public class Main {
public static void main(String[] args) {
STGroupString st =
new STGroupString(
"fieldDeclaration(type, name) ::= <<\n" +
" protected <type> <name>;\n" +
">>"
);
ST tpl = st.getInstanceOf("fieldDeclaration");
tpl.add("type", "DefaultSmppSession");
tpl.add("name", "smppSession");
System.out.println(tpl.render());
}
}
Запустим данный код и посмотрим на вывод.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
protected DefaultSmppSession smppSession;
Как видно изменения были применены к нашим параметрам и на выходе была получена ожидаемая строка.
Как видно из предыдущего примера, шаблоны имеют имена и что более важно, могут быть использованы в тексте другого шаблона.
Например, мы можем реализовать вывод методов get*
и set*
для создаваемого поля.
Объявим соответствующие шаблоны:
setter(type, name) ::= <<
public void set<name>(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name>() {
return this.<name>;
}
>>
Код же изменился только в части описания шаблона.
package stringtemplate.learn;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupString;
public class Main {
public static void main(String[] args) {
STGroupString st =
new STGroupString(
"fieldDeclaration(type, name) ::= <<\n" +
" protected <type> <name>;\n" +
" <setter(type, name)>\n" +
" <getter(type, name)>\n" +
">>" +
"setter(type, name) ::= <<\n" +
"public void set<name>(<type> <name>) {\n" +
" this.<name> = <name>;\n" +
"}\n" +
">>\n" +
"\n" +
"getter(type, name) ::= <<\n" +
"public <type> get<name>() {\n" +
" return this.<name>;\n" +
"}\n" +
">>"
);
ST tpl = st.getInstanceOf("fieldDeclaration");
tpl.add("type", "DefaultSmppSession");
tpl.add("name", "smppSession");
System.out.println(tpl.render());
}
}
Запустим приложение и посмотрим на вывод.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
protected DefaultSmppSession smppSession;
public void setsmppSession(DefaultSmppSession smppSession) {
this.smppSession = smppSession;
}
public DefaultSmppSession getsmppSession() {
return this.smppSession;
}
Как видим были созданы соответствующие методы.
ST осуществляет не простой вывод в поток, а применяет некоторые преобразования. Кроме того, при рендеринге значений возможна передача параметров в объект рендерера, который осуществляет вывод.
Например, подключим StringRenderer
.
import org.stringtemplate.v4.StringRenderer;
...
st.registerRenderer(String.class, new StringRenderer());
StringRenderer
- это класс, который отвечает за рендеринг строк. Ему передаётся в частности параметр format
, который изменяет строку.
Возможные форматы:
-
upper
- привести к верхнему регистру -
lower
- привести к нижнему регистру -
cap
- сделать первую букву заглавной -
url-encode
- провести URL-кодирование
Передача параметров осуществляется посредством задания параметра и значения, после точки с запятой (;) после имени атрибута.
Пример.
<name;format="cap">
Данный механизм позволит нам реализовать преобразования для задания правильных имён для геттеров и сеттеров.
Изменим шаблоны следующим образом:
setter(type, name) ::= <<
public void set<name; format="cap">(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name; format="cap">() {
return this.<name>;
}
>>
Как видим добавилось указание параметра формат для формирования имени геттеров и сеттеров.
Полный код класса.
package stringtemplate.learn;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupString;
import org.stringtemplate.v4.StringRenderer;
public class Main {
public static void main(String[] args) {
STGroupString st =
new STGroupString(
"fieldDeclaration(type, name) ::= <<\n" +
" protected <type> <name>;\n" +
" <setter(type, name)>\n" +
" <getter(type, name)>\n" +
">>" +
"setter(type, name) ::= <<\n" +
"public void set<name; format=\"cap\">(<type> <name>) {\n" +
" this.<name> = <name>;\n" +
"}\n" +
">>\n" +
"\n" +
"getter(type, name) ::= <<\n" +
"public <type> get<name; format=\"cap\">() {\n" +
" return this.<name>;\n" +
"}\n" +
">>"
);
st.registerRenderer(String.class, new StringRenderer());
ST tpl = st.getInstanceOf("fieldDeclaration");
tpl.add("type", "DefaultSmppSession");
tpl.add("name", "smppSession");
System.out.println(tpl.render());
}
}
Запустим и посмотрим на вывод.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
protected DefaultSmppSession smppSession;
public void setSmppSession(DefaultSmppSession smppSession) {
this.smppSession = smppSession;
}
public DefaultSmppSession getSmppSession() {
return this.smppSession;
}
Теперь названия геттеров и сеттеров соответствуют правилам именования для java beans.
ST поддерживает возможность получения шаблонов из файлов, а не в виде строки, как было показано ранее.
Создадим файл tpl.stg
в директории src/main/resources
следующего содержания
fieldDeclaration(type, name) ::= <<
protected <type> <name>;
<setter(type, name)>
<getter(type, name)>
>>
setter(type, name) ::= <<
public void set<name; format="cap">(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name; format="cap">() {
return this.<name>;
}
>>
Изменим код основного класса на загрузку из файла.
package stringtemplate.learn;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.StringRenderer;
public class Main {
public static void main(String[] args) {
STGroupFile st = new STGroupFile("src/main/resources/tpl.stg");
st.registerRenderer(String.class, new StringRenderer());
ST tpl = st.getInstanceOf("fieldDeclaration");
tpl.add("type", "DefaultSmppSession");
tpl.add("name", "smppSession");
System.out.println(tpl.render());
}
}
Запустим проект для проверки.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
protected DefaultSmppSession smppSession;
public void setSmppSession(DefaultSmppSession smppSession) {
this.smppSession = smppSession;
}
public DefaultSmppSession getSmppSession() {
return this.smppSession;
}
Как видно вывод остался прежним, хотя шаблоны берутся не из строки, а из файла с шаблонами.
ST умеет извлекать атрибуты из объектов. Например, чтобы обратиться к атрибуту atr
в объекте obj
необходимо написать.
<obj.atr>
Кроме того, ST позволяет применять шаблоны итеративно с помощью следующего синтаксиса:
<АТРИБУТ_СО_СПИСКОМ:{ НАЗВАНИЕ_НОВОГО_АТРИБУТА | ПРИМЕНЯЕМЫЙ_ШАБЛОН }>
Изменим наш шаблон таким образом, чтобы генерировать класс с полями и методами.
Файл шаблонов tpl.stg
classDefinition(name, fields) ::=<<
public class <name> {
<fields:{field | <fieldDeclaration(field.type, field.name)>}>
<fields:{field | <setter(field.type, field.name)>}>
<fields:{field | <getter(field.type, field.name)>}>
}
>>
fieldDeclaration(type, name) ::= <<
protected <type> <name>;
>>
setter(type, name) ::= <<
public void set<name; format="cap">(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name; format="cap">() {
return this.<name>;
}
>>
Перевод строки (и пустая строка) обязательны, чтобы выводы располагались на разных строках.
Изменим основной класс.
package stringtemplate.learn;
import java.util.ArrayList;
import java.util.List;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.StringRenderer;
public class Main {
public static void main(String[] args) {
STGroupFile st = new STGroupFile("src/main/resources/tpl.stg");
st.registerRenderer(String.class, new StringRenderer());
ST tpl = st.getInstanceOf("classDefinition");
List<Field> fields = new ArrayList<>();
fields.add(new Field("connection", "SmppServerConnection"));
fields.add(new Field("size", "long"));
fields.add(new Field("timeout", "long"));
tpl.add("fields", fields);
tpl.add("name", "ClientConnection");
System.out.println(tpl.render());
}
public static class Field {
protected String name;
protected String type;
public Field() {
}
public Field(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
В данном классе создаётся список описаний полей и передаётся в шаблон, а также название класса. На выходе мы должны получить описание класса с полями, геттерами и сеттерами.
Запустим изменённое приложение.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
public class ClientConnection {
protected SmppServerConnection connection;
protected long size;
protected long timeout;
public void setConnection(SmppServerConnection connection) {
this.connection = connection;
}
public void setSize(long size) {
this.size = size;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public SmppServerConnection getConnection() {
return this.connection;
}
public long getSize() {
return this.size;
}
public long getTimeout() {
return this.timeout;
}
}
То есть на выходе получаем полностью созданный класс.
ST позволяет объявлять параметры поумолчанию для шаблонов путём указания значения для формального параметра.
Пример,
template(param1, param2 = "someValue") ::= <<
param1 = param2;
>>
В нашем случае добавим указание квалификатора доступа в качестве параметра поумолчанию.
fieldDeclaration(type, name, qualifier="protected") ::= <<
<qualifier> <type> <name>;
>>
ST позволяет использовать в коде условные конструкции. Добавим к шаблону группировку полей по типу квалификаторов доступа.
Для начала расширим класс поля (Field
), добавив поле qualifier
. Кроме того, условная логика в ST требует, чтобы выражения в условных блоках были булевыми и состояли только из обращений к атрибутам и методам (это необходимо для соблюдения принципа разделения обработки и отображения), поэтому нам потребутеся реализовать 3 метода (isPublic
, isProtected
, isPrivate
).
public static class Field {
protected String name;
protected String type;
protected String qualifier = "protected";
public Field() {
}
public Field(String name, String type) {
this.name = name;
this.type = type;
}
public Field(String name, String type, String qualifier) {
this.name = name;
this.type = type;
this.qualifier = qualifier;
}
public boolean isPublic() {
return "public".equals(getQualifier());
}
public boolean isProtected() {
return "protected".equals(getQualifier());
}
public boolean isPrivate() {
return "private".equals(getQualifier());
}
...
}
Сделаем поле size
публичным.
fields.add(new Field("connection", "SmppServerConnection"));
fields.add(new Field("size", "long", "public"));
fields.add(new Field("timeout", "long"));
Добавим условные рендеринг в шаблон.
Пример.
<fields:{field | <if(field.public)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
Условная конструкция заключена между тэгами <if()>
и <endif>
.
Полный код шаблона.
classDefinition(name, fields) ::=<<
public class <name> {
<fields:{field | <if(field.public)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <if(field.protected)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <if(field.private)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <setter(field.type, field.name)>}>
<fields:{field | <getter(field.type, field.name)>}>
}
>>
fieldDeclaration(type, name, qualifier="protected") ::= <<
<qualifier> <type> <name>;
>>
setter(type, name) ::= <<
public void set<name; format="cap">(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name; format="cap">() {
return this.<name>;
}
>>
Запустим приложение.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
public class ClientConnection {
public long size;
protected SmppServerConnection connection;
protected long timeout;
public void setConnection(SmppServerConnection connection) {
this.connection = connection;
}
public void setSize(long size) {
this.size = size;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public SmppServerConnection getConnection() {
return this.connection;
}
public long getSize() {
return this.size;
}
public long getTimeout() {
return this.timeout;
}
}
Как видно поля отсортированы по типу квалификаторов доступа.
ST позволяет выводить массивы значений с указанием раздлителя.
Например, добавим в класс набор целоцисленных констант.
Шаблон tpl.stg
.
classDefinition(name, fields, values) ::=<<
...
public static int[] CONSTANTS = new int[]{<values; separator=", ">};
Добавим изменения в класс Main
.
tpl
.add(
"values",
new int[] {
1,5,8,7,4,5,8,7,5,6,9,5,4,1,
2,5,4,7,8,5,4,1,2,54,21,5,46,
4,64,6,4,6,87,3,2,1,1,4,5,7,23,
4,4,6,7,8,4,2,2,5,6,8,9,0,6,7,2,4,
5,5,8,5,2,6,5,54,4,8,61,9,8,54,5,5,
49,1
}
);
Вывод в данном случае будет не очень красивым.
...
public static int[] CONSTANTS = new int[]{ 1, 5, 8, 7, 4, 5, 8, 7, 5, 6, 9, 5, 4, 1, 2, 5, 4, 7, 8, 5, 4, 1, 2, 54, 21, 5, 46, 4, 64, 6, 4, 6, 87, 3, 2, 1, 1, 4, 5, 7, 23, 4, 4, 6, 7, 8, 4, 2, 2, 5, 6, 8, 9, 0, 6, 7, 2, 4, 5, 5, 8, 5, 2, 6, 5, 54, 4, 8, 61, 9, 8, 54, 5, 5, 49, 1 };
...
Все значения выведены в одну строку.
Допустим мы генерируем файл с максимальной длиной строки в 80 символов, тогда необходимо указать данную настройку при рендеринге.
System.out.println(tpl.render(80));
Если указать длину строки, то можно воспользоваться опцией wrap
, которая позволяет переносить текст на новую строку.
...
public static int[] CONSTANTS = new int[]{ <values; wrap, separator=", " > };
...
Вывод будет выглядеть так:
...
public static int[] CONSTANTS = new int[]{ 1, 5, 8, 7, 4, 5, 8, 7, 5, 6, 9,
5, 4, 1, 2, 5, 4, 7, 8, 5, 4, 1, 2, 54, 21, 5, 46, 4, 64, 6, 4, 6, 87, 3, 2, 1,
1, 4, 5, 7, 23, 4, 4, 6, 7, 8, 4, 2, 2, 5, 6, 8, 9, 0, 6, 7, 2, 4, 5, 5, 8, 5, 2,
6, 5, 54, 4, 8, 61, 9, 8, 54, 5, 5, 49, 1 };
...
Данный вывод уже лучше, но хотелось бы сделать вывод на уровне открывающейся фигурной скобки. Для этого есть опция anchor
, которая позволяет выводить значения с учётом отступов.
...
public static int[] CONSTANTS = new int[]{
<values; wrap, anchor, separator=", " >
};
...
Вывод в данном случае будет таким:
...
public static int[] CONSTANTS = new int[]{
1, 5, 8, 7, 4, 5, 8, 7, 5, 6, 9, 5, 4, 1,
2, 5, 4, 7, 8, 5, 4, 1, 2, 54, 21, 5, 46,
4, 64, 6, 4, 6, 87, 3, 2, 1, 1, 4, 5, 7,
23, 4, 4, 6, 7, 8, 4, 2, 2, 5, 6, 8, 9,
0, 6, 7, 2, 4, 5, 5, 8, 5, 2, 6, 5, 54,
4, 8, 61, 9, 8, 54, 5, 5, 49, 1
};
...
Полный код шаблона:
classDefinition(name, fields, values) ::=<<
public class <name> {
public static int[] CONSTANTS = new int[]{
<values; wrap, anchor, separator=", " >
};
<fields:{field | <if(field.public)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <if(field.protected)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <if(field.private)><fieldDeclaration(field.type, field.name, field.qualifier)><endif>}>
<fields:{field | <setter(field.type, field.name)>}>
<fields:{field | <getter(field.type, field.name)>}>
}
>>
fieldDeclaration(type, name, qualifier="protected") ::= <<
<qualifier> <type> <name>;
>>
setter(type, name) ::= <<
public void set<name; format="cap">(<type> <name>) {
this.<name> = <name>;
}
>>
getter(type, name) ::= <<
public <type> get<name; format="cap">() {
return this.<name>;
}
>>
Полный код класса (Main.java
):
package stringtemplate.learn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.antlr.runtime.Token;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.StringRenderer;
public class Main {
public static void main(String[] args) {
STGroupFile st = new STGroupFile("src/main/resources/tpl.stg");
st.registerRenderer(String.class, new StringRenderer());
ST tpl = st.getInstanceOf("classDefinition");
List<Field> fields = new ArrayList<>();
fields.add(new Field("connection", "SmppServerConnection"));
fields.add(new Field("size", "long", "public"));
fields.add(new Field("timeout", "long"));
tpl.add("fields", fields);
tpl.add("name", "ClientConnection");
tpl
.add(
"values",
new int[] {
1,5,8,7,4,5,8,7,5,6,9,5,4,1,
2,5,4,7,8,5,4,1,2,54,21,5,46,
4,64,6,4,6,87,3,2,1,1,4,5,7,23,
4,4,6,7,8,4,2,2,5,6,8,9,0,6,7,2,4,
5,5,8,5,2,6,5,54,4,8,61,9,8,54,5,5,
49,1
}
);
System.out.println(tpl.render(80));
}
public static class Field {
protected String name;
protected String type;
protected String qualifier = "protected";
public Field() {
}
public Field(String name, String type) {
this.name = name;
this.type = type;
}
public Field(String name, String type, String qualifier) {
this.name = name;
this.type = type;
this.qualifier = qualifier;
}
public boolean isPublic() {
return "public".equals(getQualifier());
}
public boolean isProtected() {
return "protected".equals(getQualifier());
}
public boolean isPrivate() {
return "private".equals(getQualifier());
}
public String getQualifier() {
return qualifier;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
Вывод приложения.
$ mvn clean compile exec:java -D'exec.mainClass=stringtemplate.learn.Main'
...
public class ClientConnection {
public static int[] CONSTANTS = new int[]{
1, 5, 8, 7, 4, 5, 8, 7, 5, 6, 9, 5, 4, 1,
2, 5, 4, 7, 8, 5, 4, 1, 2, 54, 21, 5, 46,
4, 64, 6, 4, 6, 87, 3, 2, 1, 1, 4, 5, 7,
23, 4, 4, 6, 7, 8, 4, 2, 2, 5, 6, 8, 9,
0, 6, 7, 2, 4, 5, 5, 8, 5, 2, 6, 5, 54,
4, 8, 61, 9, 8, 54, 5, 5, 49, 1
};
public long size;
protected SmppServerConnection connection;
protected long timeout;
public void setConnection(SmppServerConnection connection) {
this.connection = connection;
}
public void setSize(long size) {
this.size = size;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public SmppServerConnection getConnection() {
return this.connection;
}
public long getSize() {
return this.size;
}
public long getTimeout() {
return this.timeout;
}
}