Рубрики
Без рубрики

Генерация классов данных в Java

Kotlin имеет краткий синтаксис для объявления классов данных: класс данных User(val name: String, val age: В … С тегами java, kotlin, show dev.

Kotlin имеет краткий синтаксис для объявления классов данных:

data class User(val name: String, val age: Int)

Эквивалентный синтаксис Java является подробным. Вы должны создать класс Java с закрытыми полями. И методы получения и установки для полей. И дополнительные методы, такие как equals() , hashCode() и toString() .

Но кто сказал, что вы должны создавать Java-код вручную? В этой статье я покажу вам, как создавать исходные файлы Java из файла YAML .

Вот пример файла YAML:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

Примером вывода генератора кода являются два исходных файла Java, User.java и Name.java .

Содержание User.java :

public class User{
    private Name name;
    private Integer age;

    public User(){
    }

    public Name getName(){
        return name;
    }
    public void setName(Name name){
        this.name = name;
    }
    public Integer getAge(){
        return age;
    }
    public void setAge(Integer age){
        this.age = age;
    }
}

Name.java аналогично.

Суть этой статьи в том, что вы узнаете, как запрограммировать генератор кода с нуля. И его легко адаптировать к вашим потребностям.

Метод main() выполняет две задачи:

  • Шаг 1: Прочитайте в файле YAML спецификации класса
  • Шаг 2: Создайте исходные файлы Java на основе спецификаций класса

Это отделяет чтение от генерации. Таким образом, вы можете изменить формат ввода в будущем или поддерживать другие форматы ввода.

Вот метод main() :

public static void main(String[] args) throws Exception {
    // Make sure there is exactly one command line argument, the path to the YAML file
    if (args.length != 1) {
        System.out.println("Please supply exactly one argument, the absolute path of the YAML file.");
        return;
    }

    // Get the YAML file's handle, and the directory it's contained in
    // (generated files will be placed there)
    final String yamlFilePath = args[0];
    final File yamlFile = new File(yamlFilePath);
    final File outputDirectory = yamlFile.getParentFile();

    // Step 1: Read in the YAML file, into class specifications
    YamlClassSpecificationReader yamlReader = new YamlClassSpecificationReader();
    List classSpecifications = yamlReader.read(yamlFile);

    // Step 2: Generate Java source files from the class specifications
    JavaDataClassGenerator javaDataClassGenerator = new JavaDataClassGenerator();
    javaDataClassGenerator.generateJavaSourceFiles(classSpecifications, outputDirectory);

    System.out.println("Successfully generated files to: " + outputDirectory.getAbsolutePath());
}

Позвольте мне объяснить, что происходит в этой строке:

List classSpecifications = yamlReader.read(yamlFile);

Спецификация класса – это определение создаваемого класса и его полей. Помните User в примере файла YAML?

User:
    name: Name
    age: Integer

Когда YAML reader прочитает это, он создаст один Class Specification объект с именем User . И эта спецификация класса будет ссылаться на два Спецификация поля объекты, вызываемые имя и возраст .

Код для Class Specification class и Field Specification class прост.

Содержание ClassSpecification.java :

public class ClassSpecification {
    private String name;
    private List fieldSpecifications;

    public ClassSpecification(String className, List fieldSpecifications) {
        this.name = className;
        this.fieldSpecifications = fieldSpecifications;
    }

    public String getName() {
        return name;
    }

    public List getFieldSpecifications() {
        return Collections.unmodifiableList(fieldSpecifications);
    }
}

Содержание Содержание

public class FieldSpecification {
    private String name;
    private String type;

    public FieldSpecification(String fieldName, String fieldType) {
        this.name = fieldName;
        this.type = fieldType;
    }

    public String getName() {
        return name;
    }

    public String getType() {
        return type;
    }
}

Единственный оставшийся вопрос для шага 1: как вы получаете доступ из файла YAML к объектам этих классов?

Программа чтения YAML/| использует библиотеку SnakeYaml для анализа файлов YAML. SnakeYaml делает содержимое файла YAML доступным в структурах данных, таких как карты и списки. Для этой статьи вам нужно только разобраться в картах. Потому что это то, что мы используем в файлах YAML.

Посмотрите на пример еще раз:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

То, что вы видите здесь, – это две вложенные карты. Ключом внешней карты является имя класса (например, User ). Когда вы получите значение для Пользователь ключ, вы получаете карту полей класса:

    name: Name
    age: Integer

Ключом этой внутренней карты является имя поля, значением – тип поля.

Это сопоставление строк с сопоставлением строк со строками. Это важно для понимания кода программы чтения YAML . Вот метод, который считывает полное содержимое файла YAML:

private Map> readYamlClassSpecifications(Reader reader) {
    Yaml yaml = new Yaml();

    // Read in the complete YAML file to a map of strings to a map of strings to strings
    Map> yamlClassSpecifications = 
        (Map>) yaml.load(reader);

    return yamlClassSpecifications;
}

Используя спецификации класса yaml в качестве входных данных, программа чтения YAML создает объекты Спецификации класса :

private List createClassSpecificationsFrom(Map> yamlClassSpecifications) {
    final Map> classNameToFieldSpecificationsMap 
        = createClassNameToFieldSpecificationsMap(yamlClassSpecifications);

    List classSpecifications = 
        classNameToFieldSpecificationsMap.entrySet().stream()
            .map(e -> new ClassSpecification(e.getKey(), e.getValue()))
            .collect(toList());

    return classSpecifications;
}

Метод create Class Name To Field Specifications Map() создает

  • спецификации полей для каждого класса и на основе этих
  • сопоставление имени каждого класса со спецификациями его полей.

Затем/| YAML reader создает Class Specification объект для каждой записи на этой карте.

Содержимое файла YAML теперь доступно для шага 2 независимым от YAML способом. Мы закончили с шагом 1.

Apache FreeMarker – это движок шаблонов Java, который создает текстовые выходные данные. Шаблоны написаны на языке шаблонов FreeMarker (FTL). Это позволяет смешивать статический текст с содержимым объектов Java.

Вот шаблон для создания исходных файлов Java, класс данных java.ftl :

public class ${classSpecification.name}{
<#list classSpecification.fieldSpecifications as field>
    private ${field.type} ${field.name};


    public ${classSpecification.name}(){
    }

<#list classSpecification.fieldSpecifications as field>
    public ${field.type} get${field.name?cap_first}(){
        return ${field.name};
    }
    public void set${field.name?cap_first}(${field.type} ${field.name}){
        this.${field.name} = ${field.name};
    }
    
}

Давайте посмотрим на первую строку:

public class ${classSpecification.name}{

Вы видите, что он начинается со статического текста объявления класса: публичный класс . Интересный момент находится посередине: ${class Specification.name }

Когда Freemarker обрабатывает шаблон, он обращается к объекту class Specification в своей модели. Он вызывает для него метод getName() .

Как насчет этой части шаблона?

<#list classSpecification.fieldSpecifications as field>
    private ${field.type} ${field.name};
 

Сначала Freemarker вызывает class Specification.getFieldSpecifications() . Затем он выполняет итерацию по спецификациям полей.

И последнее. Эта строка немного странная:

public ${field.type} get${field.name?cap_first}(){

Допустим, поле примера равно age: Целое число (в YAML). Freemarker переводит это в:

public Integer getAge(){

Итак ? cap_first означает: заглавную первую букву, так как файл YAML содержит age строчными буквами.

Хватит о шаблонах. Как вы создаете исходные файлы Java? Во-первых, вам необходимо настроить FreeMarker, создав экземпляр Configuration . Это происходит в конструкторе Java Data Class Generator :

public JavaDataClassGenerator() throws IOException {        
    configuration = new Configuration(Configuration.VERSION_2_3_28);

    // Set the root of the class path ("") as the location to find templates
    configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "");

    configuration.setDefaultEncoding("UTF-8");
    configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
    configuration.setLogTemplateExceptions(false);
    configuration.setWrapUncheckedExceptions(true);
}

Для создания исходных файлов/| Java Data Class Generator перебирает спецификации классов и генерирует исходный файл для каждого:

public void generateJavaSourceFiles(Collection classSpecifications, File yamlFileDirectory) throws Exception {
    Map freemarkerDataModel = new HashMap<>();

    // Get the template to generate Java source files
    Template template = configuration.getTemplate("javadataclass.ftl");

    for (ClassSpecification classSpecification : classSpecifications) {
        // Put the classSpecification into the data model.
        // It can  be accessed in the template through ${classSpecification}
        freemarkerDataModel.put("classSpecification", classSpecification);

        // The Java source file will be generated in the same directory as the YAML file
        File javaSourceFile = new File(yamlFileDirectory, classSpecification.getName() + ".java");
        Writer javaSourceFileWriter = new FileWriter(javaSourceFile);

        // Generate the Java source file
        template.process(freemarkerDataModel, javaSourceFileWriter);
    }
}

И это все.

Я показал вам, как создать генератор исходного кода Java на основе файлов YAML. Я выбрал YAML, потому что его легко обрабатывать. И, таким образом, легко обучаем. Замените его другим форматом, если сочтете нужным.

Вы можете найти полный код на Github .

Чтобы сделать код максимально понятным, я воспользовался несколькими сокращениями:

  • Нет таких методов, как equals() , hashCode() и toString()
  • Отсутствие наследования классов данных
  • Сгенерированные классы Java находятся в пакете по умолчанию
  • Выходной каталог совпадает с входным каталогом
  • Обработка ошибок не была в центре моего внимания

Готовое к производству решение должно было бы решить эти проблемы. Кроме того, для классов данных альтернативой является Project Lombok .

Так что думайте об этой статье как о начале, а не как о конце. Представьте себе, что это возможно.

Несколько примеров:

  • Классы сущностей Scaffold JPA или репозитории Spring
  • Создайте несколько классов из одной спецификации на основе шаблонов в вашем приложении
  • Генерировать код на разных языках программирования
  • Подготовка документации

В настоящее время я использую этот подход для перевода требований к естественному языку непосредственно в код в исследовательских целях. Что ты будешь делать?

Оригинал: “https://dev.to/bertilmuth/generating-data-classes-in-java-4cef”