DomainTemplateCsvCodec.java
package fr.tiogars.domaintemplate.domains.domaintemplate.services;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import fr.tiogars.domaintemplate.domains.domaintemplate.entities.DomainTemplate;
import fr.tiogars.domaintemplate.domains.domaintemplate.models.CreateDomainTemplateRequest;
/**
* Serializes and parses DomainTemplates in the CSV import/export format.
*
* @since 0.1.0
*/
@Component
public class DomainTemplateCsvCodec {
private static final String HEADER = "id,code,label,description,createdDate,lastModifiedDate";
/**
* Serializes records to CSV.
*
* @param records records to serialize
* @return CSV document
*/
public String serialize(List<DomainTemplate> records) {
List<String> lines = new ArrayList<>();
lines.add(HEADER);
for (DomainTemplate template : records) {
lines.add(String.join(",",
escape(String.valueOf(template.id())),
escape(template.code()),
escape(template.label()),
escape(template.description() == null ? "" : template.description()),
escape(template.createdDate().toString()),
escape(template.lastModifiedDate().toString())));
}
return String.join("\n", lines) + "\n";
}
/**
* Parses records from CSV.
*
* @param csv CSV document
* @return parsed records
*/
public List<CreateDomainTemplateRequest> parse(String csv) {
List<List<String>> rows = parseRows(csv);
if (rows.isEmpty()) {
return List.of();
}
if (!HEADER.equals(String.join(",", rows.getFirst()))) {
throw new IllegalArgumentException("CSV header must be " + HEADER + ".");
}
List<CreateDomainTemplateRequest> records = new ArrayList<>();
for (int index = 1; index < rows.size(); index++) {
List<String> row = rows.get(index);
if (row.size() == 1 && row.getFirst().isBlank()) {
continue;
}
if (row.size() != 6) {
throw new IllegalArgumentException("CSV row " + (index + 1) + " must contain six columns.");
}
records.add(parseRecord(row, index + 1));
}
return records;
}
private CreateDomainTemplateRequest parseRecord(List<String> row, int lineNumber) {
String code = row.get(1).trim();
String label = row.get(2).trim();
if (code.isBlank() || label.isBlank()) {
throw new IllegalArgumentException("CSV row " + lineNumber + " must contain code and label.");
}
String description = row.get(3).isBlank() ? null : row.get(3);
return new CreateDomainTemplateRequest(code, label, description);
}
private static List<List<String>> parseRows(String csv) {
List<List<String>> rows = new ArrayList<>();
List<String> row = new ArrayList<>();
StringBuilder field = new StringBuilder();
boolean quoted = false;
for (int index = 0; index < csv.length(); index++) {
char character = csv.charAt(index);
if (quoted) {
if (character == '"') {
if (index + 1 < csv.length() && csv.charAt(index + 1) == '"') {
field.append('"');
index++;
} else {
quoted = false;
}
} else {
field.append(character);
}
} else if (character == '"') {
quoted = true;
} else if (character == ',') {
row.add(field.toString());
field.setLength(0);
} else if (character == '\n') {
row.add(field.toString());
rows.add(row);
row = new ArrayList<>();
field.setLength(0);
} else if (character != '\r') {
field.append(character);
}
}
if (quoted) {
throw new IllegalArgumentException("CSV contains an unterminated quoted field.");
}
if (!field.isEmpty() || !row.isEmpty()) {
row.add(field.toString());
rows.add(row);
}
return rows;
}
private static String escape(String value) {
if (value.contains(",") || value.contains("\n") || value.contains("\r") || value.contains("\"")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
}