diff --git a/app/pom.xml b/app/pom.xml
index 3555da9..d5108b2 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -54,6 +54,12 @@
3.1.5
+
+ org.apache.commons
+ commons-csv
+ 1.14.1
+
+
diff --git a/app/src/main/java/ru/project/io/StudentCsvWriter.java b/app/src/main/java/ru/project/io/StudentCsvWriter.java
new file mode 100644
index 0000000..8795fbc
--- /dev/null
+++ b/app/src/main/java/ru/project/io/StudentCsvWriter.java
@@ -0,0 +1,88 @@
+package ru.project.io;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import ru.project.collection.MyList;
+import ru.project.student.Student;
+
+public class StudentCsvWriter {
+
+ private static final String GROUP_NUMBER_HEADER = "groupNumber";
+ private static final String AVERAGE_GRADE_HEADER = "averageGrade";
+ private static final String RECORD_BOOK_NUMBER_HEADER = "recordBookNumber";
+
+ private final String absoluteFilePath;
+
+ public StudentCsvWriter(String absoluteFilePath) {
+ this.absoluteFilePath = absoluteFilePath;
+ }
+
+ public void write(Student student) throws IOException {
+ ensureFileExists();
+
+ boolean fileIsEmpty = isFileEmpty();
+
+ try (CSVPrinter printer = createPrinter()) {
+ if (fileIsEmpty) {
+ writeHeader(printer);
+ }
+
+ writeStudent(printer, student);
+ }
+ }
+
+ public void write(MyList[] students) throws IOException {
+ ensureFileExists();
+
+ boolean fileIsEmpty = isFileEmpty();
+
+ try (CSVPrinter printer = createPrinter()) {
+ if (fileIsEmpty) {
+ writeHeader(printer);
+ }
+
+ for (MyList studentList : students) {
+ for (int i = 0; i < studentList.size(); i++) {
+ writeStudent(printer, studentList.get(i));
+ }
+ }
+ }
+ }
+
+ private CSVPrinter createPrinter() throws IOException {
+ BufferedWriter writer =
+ new BufferedWriter(
+ new OutputStreamWriter(
+ new FileOutputStream(absoluteFilePath, true), StandardCharsets.UTF_8));
+
+ return new CSVPrinter(writer, CSVFormat.DEFAULT);
+ }
+
+ private void writeHeader(CSVPrinter printer) throws IOException {
+ printer.printRecord(GROUP_NUMBER_HEADER, AVERAGE_GRADE_HEADER, RECORD_BOOK_NUMBER_HEADER);
+ }
+
+ private void writeStudent(CSVPrinter printer, Student student) throws IOException {
+
+ printer.printRecord(
+ student.getGroupNumber(), student.getAverageGrade(), student.getRecordBookNumber());
+ }
+
+ private void ensureFileExists() throws IOException {
+ File file = new File(absoluteFilePath);
+
+ if (!file.isFile()) {
+ throw new IOException("CSV file does not exist: " + absoluteFilePath);
+ }
+ }
+
+ private boolean isFileEmpty() {
+ return new File(absoluteFilePath).length() == 0;
+ }
+}
diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java
index dc3a366..0c7fe16 100644
--- a/app/src/main/java/ru/project/search/StudentSearchService.java
+++ b/app/src/main/java/ru/project/search/StudentSearchService.java
@@ -1,7 +1,7 @@
package ru.project.search;
-import ru.project.collection.MyLinkedList;
import ru.project.collection.MyList;
+import ru.project.collection.MyLinkedList;
import ru.project.student.Student;
public class StudentSearchService {
diff --git a/app/src/test/java/ru/project/io/StudentCsvWriterTest.java b/app/src/test/java/ru/project/io/StudentCsvWriterTest.java
new file mode 100644
index 0000000..d5da367
--- /dev/null
+++ b/app/src/test/java/ru/project/io/StudentCsvWriterTest.java
@@ -0,0 +1,194 @@
+package ru.project.io;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import ru.project.collection.MyLinkedList;
+import ru.project.collection.MyList;
+import ru.project.student.Student;
+
+class StudentCsvWriterTest {
+
+ @TempDir Path tempDir;
+
+ @Test
+ void writeShouldWriteHeaderAndStudentToEmptyFile() throws Exception {
+ Path file = tempDir.resolve("students.csv");
+ Files.createFile(file);
+
+ Student student = new Student("A12", 4.5, 10001);
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(student);
+
+ List lines = Files.readAllLines(file);
+
+ assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
+ }
+
+ @Test
+ void writeShouldAppendStudentToExistingFile() throws Exception {
+ Path file = tempDir.resolve("students.csv");
+
+ Files.write(file, List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"));
+
+ Student student = new Student("B07", 3.8, 10002);
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(student);
+
+ List lines = Files.readAllLines(file);
+
+ assertEquals(
+ List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001", "B07,3.8,10002"),
+ lines);
+ }
+
+ @Test
+ void writeShouldNotCreateMissingFile() {
+ Path file = tempDir.resolve("students.csv");
+
+ Student student = new Student("A12", 4.5, 10001);
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ assertThrows(IOException.class, () -> writer.write(student));
+
+ assertFalse(Files.exists(file));
+ }
+
+ @Test
+ void writeShouldAppendSeveralStudentsInOrder() throws Exception {
+ Path file = tempDir.resolve("students.csv");
+ Files.createFile(file);
+
+ Student student1 = new Student("A12", 4.5, 10001);
+ Student student2 = new Student("B07", 3.8, 10002);
+ Student student3 = new Student("C25", 4.9, 10003);
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(student1);
+ writer.write(student2);
+ writer.write(student3);
+
+ List lines = Files.readAllLines(file);
+
+ assertEquals(
+ List.of(
+ "groupNumber,averageGrade,recordBookNumber",
+ "A12,4.5,10001",
+ "B07,3.8,10002",
+ "C25,4.9,10003"),
+ lines);
+ }
+
+ @Test
+ void writeArrayShouldAppendStudentsFromAllListsInOrder() throws Exception {
+
+ Path file = tempDir.resolve("students.csv");
+ Files.createFile(file);
+
+ Student student1 = new Student("A12", 4.5, 10001);
+ Student student2 = new Student("B07", 3.8, 10002);
+ Student student3 = new Student("C25", 4.9, 10003);
+ Student student4 = new Student("D15", 4.2, 10004);
+ Student student5 = new Student("E20", 3.6, 10005);
+
+ MyList firstList = new MyLinkedList<>();
+ firstList.add(student1);
+ firstList.add(student2);
+
+ MyList secondList = new MyLinkedList<>();
+ secondList.add(student3);
+
+ MyList thirdList = new MyLinkedList<>();
+ thirdList.add(student4);
+ thirdList.add(student5);
+
+ @SuppressWarnings("unchecked")
+ MyList[] students = new MyList[] {firstList, secondList, thirdList};
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(students);
+
+ List lines = Files.readAllLines(file);
+
+ assertEquals(
+ List.of(
+ "groupNumber,averageGrade,recordBookNumber",
+ "A12,4.5,10001",
+ "B07,3.8,10002",
+ "C25,4.9,10003",
+ "D15,4.2,10004",
+ "E20,3.6,10005"),
+ lines);
+ }
+
+ @Test
+ void writeArrayShouldSkipEmptyLists() throws Exception {
+ Path file = tempDir.resolve("students.csv");
+ Files.createFile(file);
+
+ Student student = new Student("A12", 4.5, 10001);
+
+ MyList emptyList = new MyLinkedList<>();
+
+ MyList nonEmptyList = new MyLinkedList<>();
+ nonEmptyList.add(student);
+
+ @SuppressWarnings("unchecked")
+ MyList[] students = new MyList[] {emptyList, nonEmptyList, new MyLinkedList<>()};
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(students);
+
+ List lines = Files.readAllLines(file);
+
+ assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
+ }
+
+ @Test
+ void writeEmptyArrayShouldNotModifyFile() throws Exception {
+ Path file = tempDir.resolve("students.csv");
+
+ List initialContent =
+ List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001");
+
+ Files.write(file, initialContent);
+
+ @SuppressWarnings("unchecked")
+ MyList[] students = new MyList[0];
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ writer.write(students);
+
+ assertEquals(initialContent, Files.readAllLines(file));
+ }
+
+ @Test
+ void writeEmptyArrayShouldNotCreateMissingFile() {
+ Path file = tempDir.resolve("students.csv");
+
+ @SuppressWarnings("unchecked")
+ MyList[] students = new MyList[0];
+
+ StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
+
+ assertThrows(IOException.class, () -> writer.write(students));
+
+ assertFalse(Files.exists(file));
+ }
+}
diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java
index db43fa1..9ef14ef 100644
--- a/app/src/test/java/ru/project/search/StudentSearchTest.java
+++ b/app/src/test/java/ru/project/search/StudentSearchTest.java
@@ -3,8 +3,8 @@ package ru.project.search;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
-import ru.project.collection.MyLinkedList;
import ru.project.collection.MyList;
+import ru.project.collection.MyLinkedList;
import ru.project.student.Student;
class StudentSearchTest {