Kotlin are o sintaxă concisă pentru a declara clase de date:

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

Sintaxa Java echivalentă este detaliată. Trebuie să creați o clasă Java cu câmpuri private. Și getter și setter metode pentru câmpuri. Și metode suplimentare precum equals(), hashCode() și toString().

Dar cine spune că trebuie să creați manual codul Java?

În acest articol, vă voi arăta cum să generați fișiere sursă Java dintr-un YAML fişier.

Iată exemplul fișierului YAML:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

Exemplul de ieșire al generatorului de cod este două fișiere sursă Java, User.java și Name.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 este similar.

Scopul acestui articol este: Veți învăța cum să programați un generator de cod de la zero. Și este ușor să îl adaptați la nevoile dvs.

Principala metodă

main() metoda face două lucruri:

  • Pasul 1: Citiți fișierul YAML, în specificațiile clasei
  • Pasul 2: Generați fișiere sursă Java din specificațiile clasei

Decuplează citirea și generarea. Puteți schimba formatul de intrare în viitor sau puteți accepta mai multe formate de intrare.

Iată main() metodă:

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 path to the YAML file.");
        return;
    }
  
    // Get the YAML file's handle & 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<ClassSpecification> classSpecifications = yamlReader.read(yamlFile);
    
    // Step 2: Generate Java source files from class specifications
    JavaDataClassGenerator javaDataClassGenerator = new JavaDataClassGenerator();
    javaDataClassGenerator.generateJavaSourceFiles(classSpecifications, outputDirectory);
    
    System.out.println("Successfully generated files to: " + outputDirectory.getAbsolutePath());
}

Pasul 1: Citiți fișierul YAML în specificațiile clasei

Permiteți-mi să explic ce se întâmplă în această linie:

List<ClassSpecification> classSpecifications =  yamlReader.read(yamlFile);

O specificație de clasă este o definiție a unei clase care trebuie generată și a câmpurilor sale.
Amintiți-vă User în exemplul fișier YAML?

User:
    name: Name
    age: Integer

Cand Cititor YAML citește asta, va crea unul ClassSpecification obiect, cu numele User. Și specificația de clasă va face referire la două FieldSpecification obiecte, numite name și age.

Codul pentru ClassSpecification clasa și FieldSpecification clasa este simplă.

Conținutul ClassSpecification.java este prezentat mai jos:

public class ClassSpecification {
    private String name;
    private List<FieldSpecification> fieldSpecifications;
  
    public ClassSpecification(String className, List<FieldSpecification> fieldSpecifications) {
        this.name = className;
        this.fieldSpecifications = fieldSpecifications;
    }
  
    public String getName() {
        return name;
    }
  
    public List<FieldSpecification> getFieldSpecifications() {
        return Collections.unmodifiableList(fieldSpecifications);
    }
}

Conținutul FieldSpecification.java este:

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;
    }
}

Singura întrebare rămasă pentru Pasul 1 este: cum ajungeți dintr-un fișier YAML la obiecte din aceste clase?

Cititor YAML folosește ȘarpeYAML bibliotecă pentru a analiza fișierele YAML.
SnakeYAML face conținutul unui fișier YAML disponibil în structuri de date precum hărți și liste.

Pentru acest articol, trebuie doar să înțelegeți hărțile – pentru că asta este ceea ce folosim în fișierele YAML.

Uită-te din nou la exemplu:

User:
    name: Name
    age: Integer

Name:
    firstName: String
    lastName: String

Ceea ce vedeți aici sunt două hărți imbricate.

Cheia hărții exterioare este numele clasei (cum ar fi User).

Când obțineți valoarea pentru User cheie, veți obține o hartă a câmpurilor clasei:

name: Name
age: Integer

Cheia acestei hărți interioare este numele câmpului, iar valoarea este tipul câmpului.

Este o hartă a șirurilor la o hartă a șirurilor la șiruri. Este important să înțelegeți codul Cititor YAML.

Iată metoda care citește conținutul complet al fișierului YAML:

private Map<String, Map<String, String>> 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<String, Map<String, String>> yamlClassSpecifications = 
		(Map<String, Map<String, String>>) yaml.load(reader);

	return yamlClassSpecifications;
}

Cu yamlClassSpecifications ca intrare, Cititor YAML creează ClassSpecification obiecte:

private List<ClassSpecification> createClassSpecificationsFrom(Map<String, Map<String, String>> yamlClassSpecifications) {
	final Map<String, List<FieldSpecification>> classNameToFieldSpecificationsMap 
		= createClassNameToFieldSpecificationsMap(yamlClassSpecifications);

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

	return classSpecifications;
}

createClassNameToFieldSpecificationsMap() metoda creează

  • specificațiile de teren pentru fiecare clasă și pe baza acestora
  • o hartă a fiecărui nume de clasă cu specificațiile de câmp.

Apoi Cititor YAML creează o ClassSpecification obiect pentru fiecare intrare din acea hartă.

Conținutul fișierului YAML este acum disponibil la Pasul 2 într-un mod independent YAML. Am terminat cu Pasul 1.

Pasul 2: Generați fișiere sursă Java din specificațiile clasei

Apache FreeMarker este un motor șablon Java care produce ieșire textuală. Șabloanele sunt scrise în FreeMarker Template Language (FTL). Permite textului static să se amestece cu conținutul obiectelor Java.

Iată șablonul pentru a genera fișierele sursă Java, javadataclass.ftl:

public class ${classSpecification.name}{
<#list classSpecification.fieldSpecifications as field>
    private ${field.type} ${field.name};
</#list>
    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};
    }
</#list>    
}

Să vedem prima linie:

public class ${classSpecification.name}{

Puteți vedea că începe cu textul static al unei declarații de clasă: public class. Partea interesantă este la mijloc: ${classSpecification.name}.

Când Freemarker procesează șablonul, acesta accesează fișierul classSpecification obiect în modelul său. Se numește getName() metoda pe ea.

Ce zici de această parte a șablonului?

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

La început, Freemarker sună classSpecification.getFieldSpecifications(). Apoi itera peste specificațiile câmpului.

Inca un lucru. Această linie este puțin ciudată:

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

Să presupunem că câmpul de exemplu este age: Integer (în YAML). Freemarker se traduce prin:

public Integer getAge(){

Asa de ?cap_first înseamnă: scrieți cu majusculă prima literă, așa cum conține fișierul YAML age cu litere mici.

Destul despre șabloane. Cum generați fișierele sursă Java?

Mai întâi, trebuie să configurați FreeMarker creând un fișier Configuration instanță. Acest lucru se întâmplă în constructorul JavaDataClassGenerator:

Pentru a genera fișiere sursă, JavaDataClassGenerator repetă specificațiile clasei și generează un fișier sursă pentru fiecare:

Si asta e.

Concluzie

V-am arătat cum să construiți un generator de cod sursă Java bazat pe fișiere YAML. Am ales YAML deoarece este ușor de prelucrat și, prin urmare, ușor de predat. Puteți să-l înlocuiți cu un alt format, dacă doriți.

Puteți găsi codul complet pe Github.

Pentru a face codul cât mai ușor de înțeles, am luat câteva comenzi rapide:

  • fără metode de genul equals(), hashCode() și toString()
  • nici o moștenire a claselor de date
  • clasele Java generate sunt în pachetul implicit
  • directorul de ieșire este același cu directorul de intrare
  • gestionarea erorilor nu a fost accentul meu

O soluție pregătită pentru producție ar trebui să facă față acestor probleme. De asemenea, pentru clasele de date, Proiectul Lombok este o alternativă fără generarea de cod.

Deci, gândiți-vă la acest articol ca un început, nu un sfârșit. Imaginați-vă ce este posibil. Câteva exemple:

  • clase de entități JPA de schele sau depozite de primăvară
  • generați mai multe clase dintr-o singură specificație, pe baza modelelor din aplicația dvs.
  • generează cod în diferite limbaje de programare
  • produce documentație

În prezent folosesc această abordare pentru a traduce cerințele de limbaj natural
direct la cod, în scopuri de cercetare. Ce vei face?

Dacă doriți să știți pe ce hacking, vizitați proiectul meu GitHub.

Puteți să mă contactați pe Stare de nervozitate sau LinkedIn.

Versiunea originală a acestui articol a fost postată pe dev.to