View Javadoc
1   package fr.tiogars.inception.domains.domaintemplate.services;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import org.springframework.stereotype.Component;
7   
8   import fr.tiogars.inception.domains.domaintemplate.models.CreateDomainTemplateRequest;
9   import fr.tiogars.inception.domains.domaintemplate.models.DomainTemplate;
10  
11  /**
12   * Serializes and parses DomainTemplates in the CSV import/export format.
13   *
14   * @since 0.1.0
15   */
16  @Component
17  public class DomainTemplateCsvCodec {
18  
19      private static final String HEADER = "id,code,label,description,createdDate,lastModifiedDate";
20  
21      /**
22       * Serializes records to CSV.
23       *
24       * @param records records to serialize
25       * @return CSV document
26       */
27      public String serialize(List<DomainTemplate> records) {
28          List<String> lines = new ArrayList<>();
29          lines.add(HEADER);
30          for (DomainTemplate template : records) {
31              lines.add(String.join(",",
32                      escape(String.valueOf(template.id())),
33                      escape(template.code()),
34                      escape(template.label()),
35                      escape(template.description() == null ? "" : template.description()),
36                      escape(template.createdDate().toString()),
37                      escape(template.lastModifiedDate().toString())));
38          }
39          return String.join("\n", lines) + "\n";
40      }
41  
42      /**
43       * Parses records from CSV.
44       *
45       * @param csv CSV document
46       * @return parsed records
47       */
48      public List<CreateDomainTemplateRequest> parse(String csv) {
49          List<List<String>> rows = parseRows(csv);
50          if (rows.isEmpty()) {
51              return List.of();
52          }
53          if (!HEADER.equals(String.join(",", rows.getFirst()))) {
54              throw new IllegalArgumentException("CSV header must be " + HEADER + ".");
55          }
56  
57          List<CreateDomainTemplateRequest> records = new ArrayList<>();
58          for (int index = 1; index < rows.size(); index++) {
59              List<String> row = rows.get(index);
60              if (row.size() == 1 && row.getFirst().isBlank()) {
61                  continue;
62              }
63              if (row.size() != 6) {
64                  throw new IllegalArgumentException("CSV row " + (index + 1) + " must contain six columns.");
65              }
66              records.add(parseRecord(row, index + 1));
67          }
68          return records;
69      }
70  
71      private CreateDomainTemplateRequest parseRecord(List<String> row, int lineNumber) {
72          String code = row.get(1).trim();
73          String label = row.get(2).trim();
74          if (code.isBlank() || label.isBlank()) {
75              throw new IllegalArgumentException("CSV row " + lineNumber + " must contain code and label.");
76          }
77          String description = row.get(3).isBlank() ? null : row.get(3);
78          return new CreateDomainTemplateRequest(code, label, description);
79      }
80  
81      private static List<List<String>> parseRows(String csv) {
82          List<List<String>> rows = new ArrayList<>();
83          List<String> row = new ArrayList<>();
84          StringBuilder field = new StringBuilder();
85          boolean quoted = false;
86  
87          for (int index = 0; index < csv.length(); index++) {
88              char character = csv.charAt(index);
89              if (quoted) {
90                  if (character == '"') {
91                      if (index + 1 < csv.length() && csv.charAt(index + 1) == '"') {
92                          field.append('"');
93                          index++;
94                      } else {
95                          quoted = false;
96                      }
97                  } else {
98                      field.append(character);
99                  }
100             } else if (character == '"') {
101                 quoted = true;
102             } else if (character == ',') {
103                 row.add(field.toString());
104                 field.setLength(0);
105             } else if (character == '\n') {
106                 row.add(field.toString());
107                 rows.add(row);
108                 row = new ArrayList<>();
109                 field.setLength(0);
110             } else if (character != '\r') {
111                 field.append(character);
112             }
113         }
114 
115         if (quoted) {
116             throw new IllegalArgumentException("CSV contains an unterminated quoted field.");
117         }
118         if (!field.isEmpty() || !row.isEmpty()) {
119             row.add(field.toString());
120             rows.add(row);
121         }
122         return rows;
123     }
124 
125     private static String escape(String value) {
126         if (value.contains(",") || value.contains("\n") || value.contains("\r") || value.contains("\"")) {
127             return "\"" + value.replace("\"", "\"\"") + "\"";
128         }
129         return value;
130     }
131 }