The writer automatically generates a CSV header based on
+ * the object's fields and ensures header consistency when appending data.
+ *
+ *
This class is not thread-safe.
+ */
+public class CsvFileWriter implements Writable {
+ /***
+ * Writes a list of objects to a CSV file
+ *
+ * @param data list of objects to be written; must not be empty
+ * @param fileName name of file where data will be written
+ */
@Override
public void writeToFile(List> data, String fileName) {
- validateData(data);
-
- Path path = Path.of(fileName);
- Class> clazz = data.get(0).getClass();
+ CsvContext csvContext = new CsvContext(data, fileName);
- Field[] fields = getAccessibleFields(clazz);
- String header = Arrays.stream(fields)
- .map(Field::getName)
- .collect(Collectors.joining(","));
+ validateData(csvContext);
+ resolveMetadata(csvContext);
+ checkFile(csvContext);
- boolean fileExists = isFileExists(path);
- if (fileExists) {
- validateHeader(path, header);
- }
try (BufferedWriter writer = Files.newBufferedWriter(
- path,
+ csvContext.getPath(),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
)) {
- if (!fileExists) {
- writeCsvRow(writer, header);
+ if (!csvContext.isFileExists()) {
+ writeCsvRow(writer, csvContext.getHeader());
}
- for (Object element : data) {
- String csvRow = joinFieldsToCsvRow(element, fields);
+ for (Object element : csvContext.getData()) {
+ String csvRow = joinFieldsToCsvRow(element, csvContext.getFields());
writeCsvRow(writer, csvRow);
}
} catch (IOException e) {
@@ -49,27 +52,36 @@ public void writeToFile(List> data, String fileName) {
}
}
- private String joinFieldsToCsvRow(Object objectToRow, Field[]fields){
+ /***
+ * Joins field values into a CSV string
+ * @param objectToRow the object whose data we will connect
+ * @param fields the object whose data we will connect
+ * @return joined row for CSV
+ */
+ private String joinFieldsToCsvRow(Object objectToRow, Field[] fields) {
return Arrays.stream(fields)
.map(field -> {
try {
Object value = field.get(objectToRow);
return escapeCsv(value == null ? "" : value.toString());
} catch (IllegalAccessException e) {
- throw new RuntimeException(e);
+ throw new RuntimeException("Access was not permitted ", e);
}
})
.collect(Collectors.joining(","));
}
- private Field[] getAccessibleFields(Class> clazz){
- Field[] fields = clazz.getDeclaredFields();
- Arrays.sort(fields, Comparator.comparing(Field::getName));
- for (Field field : fields) {
- field.setAccessible(true);
- }
- return fields;
- }
+
+ /**
+ * Writes a single CSV row to the given writer.
+ *
+ *
The method appends the row followed by a line separator.
+ *
+ * @param writer the {@link BufferedWriter} used to write data to the CSV file
+ * @param csvRow the CSV-formatted row to write
+ *
+ * @throws RuntimeException if an I/O error occurs while writing the row
+ */
private void writeCsvRow(BufferedWriter writer, String csvRow) {
try {
writer.write(csvRow);
@@ -79,6 +91,16 @@ private void writeCsvRow(BufferedWriter writer, String csvRow) {
}
}
+ /**
+ * Escapes a value according to CSV format rules.
+ *
+ *
If the value contains a comma, double quote, or line break,
+ * it is wrapped in double quotes and internal quotes are escaped
+ * by doubling them.
+ *
+ * @param value the raw value to escape
+ * @return a CSV-safe representation of the value
+ */
private String escapeCsv(String value) {
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
@@ -88,33 +110,89 @@ private String escapeCsv(String value) {
return value;
}
- private boolean isFileExists(Path path) {
+ /**
+ * Checks whether the target CSV file exists and validates its header if present.
+ *
+ *
If the file exists and is not empty, the method verifies that the existing
+ * CSV header matches the header generated from the current data structure.
+ *
+ * @param context the CSV processing context containing file path and header
+ *
+ * @throws IllegalStateException if the existing CSV header does not match
+ * @throws RuntimeException if an I/O error occurs while accessing the file
+ */
+ private void checkFile(CsvContext context) {
try {
- return Files.exists(path) && Files.size(path) > 0;
+ boolean isFileExists = Files.exists(context.getPath()) && Files.size(context.getPath()) > 0;
+ context.setFileExists(isFileExists);
+ if (isFileExists){
+ validateHeader(context.getPath(), context.getHeader());
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- private void validateData(List> data) {
- if (data.isEmpty()) {
+ /**
+ * Validates that the CSV context contains data to be written.
+ *
+ * @param context the CSV processing context
+ *
+ * @throws ArrayIsEmptyException if the data list is empty
+ */
+ private void validateData(CsvContext context) {
+ if (context.getData().isEmpty()) {
throw new ArrayIsEmptyException("Fill array with data!");
}
}
- private void validateHeader(Path path, String existingHeader) {
+ /**
+ * Validates that the header of an existing CSV file matches the expected header.
+ *
+ * @param path the path to the existing CSV file
+ * @param dataHeader the expected CSV header generated from the data structure
+ *
+ * @throws IllegalStateException if the headers do not match
+ * @throws RuntimeException if an I/O error occurs while reading the file
+ */
+
+ private void validateHeader(Path path, String dataHeader) {
try (BufferedReader reader = Files.newBufferedReader(path)) {
String header = reader.readLine();
- boolean isHeaderEquals = header.equals(existingHeader);
+ boolean isHeaderEquals = header.equals(dataHeader);
if (!isHeaderEquals) {
throw new IllegalStateException(
"CSV header mismatch. Expected: "
- + header + ", actual: " + existingHeader
+ + header + ", actual: " + dataHeader
);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
+
+ /**
+ * Resolves reflection metadata required for CSV serialization.
+ *
+ *
The method determines the class of the data elements, extracts
+ * declared fields, sorts them in a deterministic order, and prepares
+ * them for access. It also generates a CSV header based on the field names.
*/
public class CsvFileWriter implements Writable {
- /***
- * Writes a list of objects to a CSV file
+ /**
+ * Writes a list of objects to a CSV file.
+ *
+ *
All objects in the list must be of the same type. The CSV header
+ * is generated automatically using reflection.
*
- * @param data list of objects to be written; must not be empty
- * @param fileName name of file where data will be written
+ * @param data list of objects to be written; must not be empty and must contain
+ * objects of the same type
+ * @param fileName name of the target CSV file
+ * @throws ArrayIsEmptyException if the data list is empty
+ * @throws IllegalArgumentException if the list contains objects of different types
+ * @throws IllegalStateException if an existing CSV header does not match
*/
@Override
public void writeToFile(List> data, String fileName) {
@@ -34,7 +41,6 @@ public void writeToFile(List> data, String fileName) {
resolveMetadata(csvContext);
checkFile(csvContext);
-
try (BufferedWriter writer = Files.newBufferedWriter(
csvContext.getPath(),
StandardOpenOption.CREATE,
@@ -52,10 +58,11 @@ public void writeToFile(List> data, String fileName) {
}
}
- /***
- * Joins field values into a CSV string
+ /**
+ * Joins field values into a CSV string
+ *
* @param objectToRow the object whose data we will connect
- * @param fields the object whose data we will connect
+ * @param fields fields of the object to be serialized
* @return joined row for CSV
*/
private String joinFieldsToCsvRow(Object objectToRow, Field[] fields) {
@@ -79,7 +86,6 @@ private String joinFieldsToCsvRow(Object objectToRow, Field[] fields) {
*
* @param writer the {@link BufferedWriter} used to write data to the CSV file
* @param csvRow the CSV-formatted row to write
- *
* @throws RuntimeException if an I/O error occurs while writing the row
*/
private void writeCsvRow(BufferedWriter writer, String csvRow) {
@@ -117,15 +123,14 @@ private String escapeCsv(String value) {
* CSV header matches the header generated from the current data structure.
*/
public class CsvFileWriter implements Writable {
+ private Pipeline writeToCsvFilePipeline;
+ private CsvContext csvContext;
+
+
/**
* Writes a list of objects to a CSV file.
*
@@ -37,167 +36,27 @@ public class CsvFileWriter implements Writable {
*/
@Override
public void writeToFile(List> data, String fileName) {
- CsvContext csvContext = new CsvContext(data, fileName);
-
- validateData(csvContext);
- resolveMetadata(csvContext);
- checkFile(csvContext);
-
- try (BufferedWriter writer = Files.newBufferedWriter(
- csvContext.getPath(),
- StandardOpenOption.CREATE,
- StandardOpenOption.APPEND
- )) {
- if (!csvContext.isFileExists()) {
- writeCsvRow(writer, csvContext.getHeader());
- }
- for (Object element : csvContext.getData()) {
- String csvRow = joinFieldsToCsvRow(element, csvContext.getFields());
- writeCsvRow(writer, csvRow);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Joins field values into a CSV string
- *
- * @param objectToRow the object whose data we will connect
- * @param fields fields of the object to be serialized
- * @return joined row for CSV
- */
- private String joinFieldsToCsvRow(Object objectToRow, Field[] fields) {
- return Arrays.stream(fields)
- .map(field -> {
- try {
- Object value = field.get(objectToRow);
- return escapeCsv(value == null ? "" : value.toString());
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Access was not permitted ", e);
- }
- })
- .collect(Collectors.joining(","));
-
- }
-
- /**
- * Writes a single CSV row to the given writer.
- *
- *
The method appends the row followed by a line separator.
- *
- * @param writer the {@link BufferedWriter} used to write data to the CSV file
- * @param csvRow the CSV-formatted row to write
- * @throws RuntimeException if an I/O error occurs while writing the row
- */
- private void writeCsvRow(BufferedWriter writer, String csvRow) {
- try {
- writer.write(csvRow);
- writer.newLine();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Escapes a value according to CSV format rules.
- *
- *
If the value contains a comma, double quote, or line break,
- * it is wrapped in double quotes and internal quotes are escaped
- * by doubling them.
- *
- * @param value the raw value to escape
- * @return a CSV-safe representation of the value
- */
- private String escapeCsv(String value) {
-
- if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
- value = value.replace("\"", "\"\"");
- return "\"" + value + "\"";
- }
- return value;
+ this.csvContext = new CsvContext(data, fileName);
+ this.writeToCsvFilePipeline = buildPipeline();
+ writeToCsvFilePipeline.execute(csvContext);
}
- /**
- * Checks whether the target CSV file exists and validates its header if present.
- *
- *
If the file exists and is not empty, the method verifies that the existing
- * CSV header matches the header generated from the current data structure.
- *
- * @param context the CSV processing context containing file path and header
- * @throws IllegalStateException if the existing CSV header does not match
- * @throws RuntimeException if an I/O error occurs while accessing the file
- */
- private void checkFile(CsvContext context) {
- try {
- boolean isFileExists = Files.exists(context.getPath()) && Files.size(context.getPath()) > 0;
- context.setFileExists(isFileExists);
- if (isFileExists) {
- validateHeader(context.getPath(), context.getHeader());
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- /**
- * Validates that the CSV context contains data to be written.
- *
- * @param context the CSV processing context
- * @throws ArrayIsEmptyException if the data list is empty
- */
- private void validateData(CsvContext context) {
- if (context.getData().isEmpty()) {
- throw new ArrayIsEmptyException("Fill array with data!");
- }
- }
+ private Pipeline buildPipeline() {
+ Pipeline writeToCsvFilePipeline = new Pipeline<>();
- /**
- * Validates that the header of an existing CSV file matches the expected header.
- *
- * @param path the path to the existing CSV file
- * @param dataHeader the expected CSV header generated from the data structure
- * @throws IllegalStateException if the headers do not match
- * @throws RuntimeException if an I/O error occurs while reading the file
- */
-
- private void validateHeader(Path path, String dataHeader) {
- try (BufferedReader reader = Files.newBufferedReader(path)) {
- String header = reader.readLine();
- boolean isHeaderEquals = header.equals(dataHeader);
-
- if (!isHeaderEquals) {
- throw new IllegalStateException(
- "CSV header mismatch. Expected: "
- + header + ", actual: " + dataHeader
- );
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ writeToCsvFilePipeline.addStep(new ValidateDataStep());
+ writeToCsvFilePipeline.addStep(new CheckFileExistsStep());
- /**
- * Resolves reflection metadata required for CSV serialization.
- *
- *
The method determines the class of the data elements, extracts
- * declared fields, sorts them in a deterministic order, and prepares
- * them for access. It also generates a CSV header based on the field names.
The method appends the row followed by a line separator.
+ *
+ * @param writer the {@link BufferedWriter} used to write data to the CSV file
+ * @param csvRow the CSV-formatted row to write
+ * @throws RuntimeException if an I/O error occurs while writing the row
+ */
+ private void writeCsvRow(BufferedWriter writer, String csvRow) {
+ try {
+ writer.write(csvRow);
+ writer.newLine();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Escapes a value according to CSV format rules.
+ *
+ *
If the value contains a comma, double quote, or line break,
+ * it is wrapped in double quotes and internal quotes are escaped
+ * by doubling them.
+ *
+ * @param value the raw value to escape
+ * @return a CSV-safe representation of the value
+ */
+ private String escapeCsv(String value) {
+
+ if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
+ value = value.replace("\"", "\"\"");
+ return "\"" + value + "\"";
+ }
+ return value;
+ }
+}
diff --git a/src/main/java/org/writer/exception/CsvWriteException.java b/src/main/java/org/writer/exception/CsvWriteException.java
new file mode 100644
index 0000000..232b80c
--- /dev/null
+++ b/src/main/java/org/writer/exception/CsvWriteException.java
@@ -0,0 +1,8 @@
+package org.writer.exception;
+
+import java.io.IOException;
+
+public class CsvWriteException extends RuntimeException {
+ public CsvWriteException(String failedToWriteCsvFile, IOException e) {
+ }
+}
diff --git a/students.csv b/students.csv
index 15aafdf..bd5792c 100644
--- a/students.csv
+++ b/students.csv
@@ -9,13 +9,3 @@ Daniel,"[C, B, A]"
Sofia,"[A, A, B]"
Mihai,"[B, C, C]"
Laura,"[A, B, A]"
-Alex,"[A, B, A]"
-Maria,"[A, A, A]"
-John,"[B, C, B]"
-Elena,"[A, B, C]"
-Victor,"[B, B, B]"
-Anna,"[A, C, B]"
-Daniel,"[C, B, A]"
-Sofia,"[A, A, B]"
-Mihai,"[B, C, C]"
-Laura,"[A, B, A]"
From b45bbbce354429c3b00605aed18e414e3c4ccead Mon Sep 17 00:00:00 2001
From: Alexeev
Date: Thu, 15 Jan 2026 17:23:16 +0200
Subject: [PATCH 8/9] fix: fix header generation
---
persons.csv | 32 ++++++++++++-------
src/main/java/org/writer/Main.java | 2 +-
.../java/org/writer/csv/CsvFileWriter.java | 6 +---
.../org/writer/csv/context/CsvContext.java | 2 ++
.../pipeline/steps/CheckFileExistsStep.java | 2 ++
.../steps/CsvHeaderMismatchException.java | 6 ++++
.../pipeline/steps/GenerateHeaderStep.java | 24 ++++++++++++++
.../pipeline/steps/ResolveMetadataStep.java | 1 +
.../csv/pipeline/steps/ValidateDataStep.java | 1 +
.../pipeline/steps/ValidateHeaderStep.java | 27 ----------------
.../steps/WriteDataToCsvFileStep.java | 3 ++
.../org/writer/csv/CsvFileWriterTest.java | 3 +-
students.csv | 10 ++++++
13 files changed, 74 insertions(+), 45 deletions(-)
create mode 100644 src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java
delete mode 100644 src/main/java/org/writer/csv/pipeline/steps/ValidateHeaderStep.java
diff --git a/persons.csv b/persons.csv
index 0d71db3..15aafdf 100644
--- a/persons.csv
+++ b/persons.csv
@@ -1,11 +1,21 @@
-dayOfBirth,firstName,lastName,monthOfBirth,yearOfBirth
-12,Alex,Ivanov,JANUARY,1995
-3,Maria,Petrova,FEBRUARY,1998
-21,John,Smith,MARCH,1990
-9,Elena,Popescu,APRIL,1997
-30,Victor,Radu,MAY,1993
-14,Anna,Kowalski,JUNE,2000
-7,Daniel,Novak,JULY,1992
-18,Sofia,Marin,AUGUST,1996
-25,Mihai,Dumitru,SEPTEMBER,1994
-2,Laura,Bianchi,OCTOBER,1999
+name,score
+Alex,"[A, B, A]"
+Maria,"[A, A, A]"
+John,"[B, C, B]"
+Elena,"[A, B, C]"
+Victor,"[B, B, B]"
+Anna,"[A, C, B]"
+Daniel,"[C, B, A]"
+Sofia,"[A, A, B]"
+Mihai,"[B, C, C]"
+Laura,"[A, B, A]"
+Alex,"[A, B, A]"
+Maria,"[A, A, A]"
+John,"[B, C, B]"
+Elena,"[A, B, C]"
+Victor,"[B, B, B]"
+Anna,"[A, C, B]"
+Daniel,"[C, B, A]"
+Sofia,"[A, A, B]"
+Mihai,"[B, C, C]"
+Laura,"[A, B, A]"
diff --git a/src/main/java/org/writer/Main.java b/src/main/java/org/writer/Main.java
index 02b55dd..f9affbc 100644
--- a/src/main/java/org/writer/Main.java
+++ b/src/main/java/org/writer/Main.java
@@ -17,7 +17,7 @@ public static void main(String[] args) {
CsvFileWriter csvFileWriter = new CsvFileWriter();
- csvFileWriter.writeToFile(personList, peopleFileName);
+ csvFileWriter.writeToFile(studentList, peopleFileName);
csvFileWriter.writeToFile(studentList, studentshipName);
diff --git a/src/main/java/org/writer/csv/CsvFileWriter.java b/src/main/java/org/writer/csv/CsvFileWriter.java
index 7275364..e022e5b 100644
--- a/src/main/java/org/writer/csv/CsvFileWriter.java
+++ b/src/main/java/org/writer/csv/CsvFileWriter.java
@@ -50,11 +50,7 @@ private Pipeline buildPipeline() {
writeToCsvFilePipeline.addStep(new ResolveMetadataStep());
- writeToCsvFilePipeline.addStepByCondition(
- this.csvContext.isFileExists(),
- new ValidateDataStep(),
- new GenerateHeaderStep()
- );
+ writeToCsvFilePipeline.addStep(new GenerateHeaderStep());
writeToCsvFilePipeline.addStep(new WriteDataToCsvFileStep());
return writeToCsvFilePipeline;
diff --git a/src/main/java/org/writer/csv/context/CsvContext.java b/src/main/java/org/writer/csv/context/CsvContext.java
index 1dafd89..6e53aab 100644
--- a/src/main/java/org/writer/csv/context/CsvContext.java
+++ b/src/main/java/org/writer/csv/context/CsvContext.java
@@ -1,5 +1,6 @@
package org.writer.csv.context;
+import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -9,6 +10,7 @@
@Getter
@Setter
+//@Builder
public class CsvContext implements ContextInterface {
private List> data;
private Path path;
diff --git a/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java b/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
index 20e8401..f8104ad 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
@@ -12,6 +12,8 @@ public void execute(CsvContext csvcontext) {
try {
boolean isFileExists = Files.exists(csvcontext.getPath()) && Files.size(csvcontext.getPath()) > 0;
csvcontext.setFileExists(isFileExists);
+
+ System.out.println("File exists: " + isFileExists);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java b/src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java
new file mode 100644
index 0000000..4e7714b
--- /dev/null
+++ b/src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java
@@ -0,0 +1,6 @@
+package org.writer.csv.pipeline.steps;
+
+public class CsvHeaderMismatchException extends RuntimeException {
+ public CsvHeaderMismatchException(String s) {
+ }
+}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java b/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
index 13b96c0..bc3d667 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
@@ -1,17 +1,41 @@
package org.writer.csv.pipeline.steps;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;
+import java.io.BufferedReader;
+import java.io.IOException;
import java.lang.reflect.Field;
+import java.nio.file.Files;
import java.util.Arrays;
import java.util.stream.Collectors;
+
public class GenerateHeaderStep implements PipelineStep {
@Override
public void execute(CsvContext csvContext) {
csvContext.setHeader(Arrays.stream(csvContext.getFields())
.map(Field::getName)
.collect(Collectors.joining(",")));
+
+ if (csvContext.isFileExists()) {
+ try (BufferedReader reader = Files.newBufferedReader(csvContext.getPath())) {
+ String fileHeader = reader.readLine();
+
+ if (!fileHeader.equals(csvContext.getHeader())) {
+ throw new CsvHeaderMismatchException(
+ "CSV header mismatch. Expected: "
+ + csvContext.getHeader()
+ + ", Actual: "
+ + fileHeader
+ );
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java b/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
index 8b244dc..337600d 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
@@ -18,5 +18,6 @@ public void execute(CsvContext csvContext) {
f.setAccessible(true);
}
csvContext.setFields(fields);
+ System.out.println("Metadata is set...");
}
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java b/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
index 000c2a4..7aa5907 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
@@ -7,6 +7,7 @@
public class ValidateDataStep implements PipelineStep {
@Override
public void execute(CsvContext csvContext) {
+ System.out.println("Validate data...");;
if (csvContext.getData().isEmpty()) {
throw new ArrayIsEmptyException("Fill array with data!");
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/ValidateHeaderStep.java b/src/main/java/org/writer/csv/pipeline/steps/ValidateHeaderStep.java
deleted file mode 100644
index 8c02ee5..0000000
--- a/src/main/java/org/writer/csv/pipeline/steps/ValidateHeaderStep.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.writer.csv.pipeline.steps;
-
-import org.writer.csv.context.CsvContext;
-import org.writer.csv.pipeline.PipelineStep;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.nio.file.Files;
-
-public class ValidateHeaderStep implements PipelineStep {
- @Override
- public void execute(CsvContext csvContext) {
- try (BufferedReader reader = Files.newBufferedReader(csvContext.getPath())) {
- String header = reader.readLine();
- boolean isHeaderEquals = header.equals(csvContext.getHeader());
-
- if (!isHeaderEquals) {
- throw new IllegalStateException(
- "CSV header mismatch. Expected: "
- + header + ", Generated: " + csvContext.getHeader()
- );
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java b/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
index fcad5bf..5efb737 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
@@ -14,6 +14,7 @@
public class WriteDataToCsvFileStep implements PipelineStep {
@Override
public void execute(CsvContext csvContext) {
+ System.out.println("Start to file writing");
try (BufferedWriter writer = Files.newBufferedWriter(
csvContext.getPath(),
StandardOpenOption.CREATE,
@@ -26,9 +27,11 @@ public void execute(CsvContext csvContext) {
String csvRow = joinFieldsToCsvRow(element, csvContext.getFields());
writeCsvRow(writer, csvRow);
}
+ System.out.println("The data was successfully written");
} catch (IOException e) {
throw new RuntimeException(e);
}
+
}
diff --git a/src/test/java/org/writer/csv/CsvFileWriterTest.java b/src/test/java/org/writer/csv/CsvFileWriterTest.java
index fa79388..65c495c 100644
--- a/src/test/java/org/writer/csv/CsvFileWriterTest.java
+++ b/src/test/java/org/writer/csv/CsvFileWriterTest.java
@@ -3,6 +3,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.writer.csv.pipeline.steps.CsvHeaderMismatchException;
import org.writer.csv.testdata.StudentTestData;
import org.writer.exception.ArrayIsEmptyException;
import org.writer.model.Student;
@@ -99,7 +100,7 @@ void shouldThrowExceptionWhenHeaderDoesNotMatch() throws IOException {
Files.write(file, List.of("wrong,header"));
assertThrows(
- IllegalStateException.class,
+ CsvHeaderMismatchException.class,
() -> writer.writeToFile(data, file.toString())
);
}
diff --git a/students.csv b/students.csv
index bd5792c..15aafdf 100644
--- a/students.csv
+++ b/students.csv
@@ -9,3 +9,13 @@ Daniel,"[C, B, A]"
Sofia,"[A, A, B]"
Mihai,"[B, C, C]"
Laura,"[A, B, A]"
+Alex,"[A, B, A]"
+Maria,"[A, A, A]"
+John,"[B, C, B]"
+Elena,"[A, B, C]"
+Victor,"[B, B, B]"
+Anna,"[A, C, B]"
+Daniel,"[C, B, A]"
+Sofia,"[A, A, B]"
+Mihai,"[B, C, C]"
+Laura,"[A, B, A]"
From 0a8c7329740c2f6a50ecd4d970d8eda5eb8a4962 Mon Sep 17 00:00:00 2001
From: Alexeev
Date: Thu, 15 Jan 2026 17:32:42 +0200
Subject: [PATCH 9/9] add: javadoc
---
.../csv/pipeline/steps/CheckFileExistsStep.java | 16 +++++++++++-----
.../csv/pipeline/steps/GenerateHeaderStep.java | 13 +++++++++++--
.../csv/pipeline/steps/ResolveMetadataStep.java | 10 +++++++++-
.../csv/pipeline/steps/ValidateDataStep.java | 7 ++++++-
.../pipeline/steps/WriteDataToCsvFileStep.java | 9 +++++++++
.../CsvHeaderMismatchException.java | 2 +-
.../java/org/writer/csv/CsvFileWriterTest.java | 2 +-
7 files changed, 48 insertions(+), 11 deletions(-)
rename src/main/java/org/writer/{csv/pipeline/steps => exception}/CsvHeaderMismatchException.java (76%)
diff --git a/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java b/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
index f8104ad..7683624 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/CheckFileExistsStep.java
@@ -7,13 +7,19 @@
import java.nio.file.Files;
public class CheckFileExistsStep implements PipelineStep {
+ /**
+ * Checks whether the target CSV file exists and is not empty.
+ * Stores the result in the CsvContext.
+ *
+ * @param csvContext context containing the path to the CSV file
+ * @throws RuntimeException if an I/O error occurs while checking the file
+ */
+
@Override
- public void execute(CsvContext csvcontext) {
+ public void execute(CsvContext csvContext) {
try {
- boolean isFileExists = Files.exists(csvcontext.getPath()) && Files.size(csvcontext.getPath()) > 0;
- csvcontext.setFileExists(isFileExists);
-
- System.out.println("File exists: " + isFileExists);
+ boolean isFileExists = Files.exists(csvContext.getPath()) && Files.size(csvContext.getPath()) > 0;
+ csvContext.setFileExists(isFileExists);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java b/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
index bc3d667..e3598f2 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/GenerateHeaderStep.java
@@ -1,9 +1,8 @@
package org.writer.csv.pipeline.steps;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;
+import org.writer.exception.CsvHeaderMismatchException;
import java.io.BufferedReader;
import java.io.IOException;
@@ -14,8 +13,18 @@
public class GenerateHeaderStep implements PipelineStep {
+ /**
+ * Generates a CSV header based on the model fields and stores it in the context.
+ * If the CSV file already exists, validates that the generated header matches
+ * the header stored in the file.
+ *
+ * @param csvContext context containing file path and model metadata
+ * @throws CsvHeaderMismatchException if the existing file header does not match the generated one
+ * @throws RuntimeException if an I/O error occurs while reading the file
+ */
@Override
public void execute(CsvContext csvContext) {
+
csvContext.setHeader(Arrays.stream(csvContext.getFields())
.map(Field::getName)
.collect(Collectors.joining(",")));
diff --git a/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java b/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
index 337600d..f899898 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/ResolveMetadataStep.java
@@ -8,6 +8,15 @@
import java.util.Comparator;
public class ResolveMetadataStep implements PipelineStep {
+ /**
+ * Resolves metadata required for CSV generation.
+ * Determines the model class from the data list, extracts its fields,
+ * sorts them by name and makes them accessible.
+ *
+ * @param csvContext context containing the data to be written
+ * @throws IndexOutOfBoundsException if the data list is empty
+ */
+
@Override
public void execute(CsvContext csvContext) {
csvContext.setClazz(csvContext.getData().get(0).getClass());
@@ -18,6 +27,5 @@ public void execute(CsvContext csvContext) {
f.setAccessible(true);
}
csvContext.setFields(fields);
- System.out.println("Metadata is set...");
}
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java b/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
index 7aa5907..c342ef5 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
@@ -5,9 +5,14 @@
import org.writer.exception.ArrayIsEmptyException;
public class ValidateDataStep implements PipelineStep {
+ /**
+ * Validates that the data list is not empty.
+ *
+ * @param csvContext context containing the data to be written
+ * @throws ArrayIsEmptyException if the data list is empty
+ */
@Override
public void execute(CsvContext csvContext) {
- System.out.println("Validate data...");;
if (csvContext.getData().isEmpty()) {
throw new ArrayIsEmptyException("Fill array with data!");
}
diff --git a/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java b/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
index 5efb737..5a1b4d5 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
+++ b/src/main/java/org/writer/csv/pipeline/steps/WriteDataToCsvFileStep.java
@@ -12,6 +12,15 @@
import java.util.stream.Collectors;
public class WriteDataToCsvFileStep implements PipelineStep {
+ /**
+ * Writes data from the context to a CSV file.
+ * Writes the header if the file does not already exist,
+ * then appends all data rows.
+ *
+ * @param csvContext context containing file path, header and data
+ * @throws RuntimeException if an I/O error occurs while writing to the file
+ */
+
@Override
public void execute(CsvContext csvContext) {
System.out.println("Start to file writing");
diff --git a/src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java b/src/main/java/org/writer/exception/CsvHeaderMismatchException.java
similarity index 76%
rename from src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java
rename to src/main/java/org/writer/exception/CsvHeaderMismatchException.java
index 4e7714b..47b79e6 100644
--- a/src/main/java/org/writer/csv/pipeline/steps/CsvHeaderMismatchException.java
+++ b/src/main/java/org/writer/exception/CsvHeaderMismatchException.java
@@ -1,4 +1,4 @@
-package org.writer.csv.pipeline.steps;
+package org.writer.exception;
public class CsvHeaderMismatchException extends RuntimeException {
public CsvHeaderMismatchException(String s) {
diff --git a/src/test/java/org/writer/csv/CsvFileWriterTest.java b/src/test/java/org/writer/csv/CsvFileWriterTest.java
index 65c495c..b3dc7bf 100644
--- a/src/test/java/org/writer/csv/CsvFileWriterTest.java
+++ b/src/test/java/org/writer/csv/CsvFileWriterTest.java
@@ -3,7 +3,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import org.writer.csv.pipeline.steps.CsvHeaderMismatchException;
+import org.writer.exception.CsvHeaderMismatchException;
import org.writer.csv.testdata.StudentTestData;
import org.writer.exception.ArrayIsEmptyException;
import org.writer.model.Student;