diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100644
index 0000000..95a8d96
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,16 @@
+
+
+
+ 10.26.1
+ JavaOnly
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..52d3977
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/people.csv b/people.csv
new file mode 100644
index 0000000..945fda0
--- /dev/null
+++ b/people.csv
@@ -0,0 +1,11 @@
+first_name,last_name,day_of_birth,month_of_birth,year_of_birth
+Юлия,Авдеев,22,JULY,1978
+София,Киселев,27,MAY,2005
+Валентин,Поляков,10,MAY,1983
+Аркадий,Куликов,10,OCTOBER,1981
+Валерий,Матвеев,15,AUGUST,1992
+Сергей,Баранова,19,APRIL,1979
+Надежда,Зуев,5,JANUARY,1984
+Матвей,Крылова,22,SEPTEMBER,1980
+Олег,Лобанова,17,FEBRUARY,1999
+Анжелика,Ширяева,11,AUGUST,1972
diff --git a/pom.xml b/pom.xml
index effc1bd..05c5d95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,6 +27,17 @@
5.8.1
test
+
+ com.github.javafaker
+ javafaker
+ 1.0.2
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
\ No newline at end of file
diff --git a/src/main/java/org/writer/CsvColumn.java b/src/main/java/org/writer/CsvColumn.java
new file mode 100644
index 0000000..f28540f
--- /dev/null
+++ b/src/main/java/org/writer/CsvColumn.java
@@ -0,0 +1,19 @@
+package org.writer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Аннотация для пометки полей, которые должны попадать в CSV. * Поле order используется для задания порядка колонок в выходном файле.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface CsvColumn {
+
+ String name() default "";
+
+ int order() default Integer.MAX_VALUE;
+
+}
diff --git a/src/main/java/org/writer/CsvWritable.java b/src/main/java/org/writer/CsvWritable.java
new file mode 100644
index 0000000..c6d1637
--- /dev/null
+++ b/src/main/java/org/writer/CsvWritable.java
@@ -0,0 +1,117 @@
+package org.writer;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.util.stream.Collectors.joining;
+
+/**
+ * Реализация Writable, сохраняющая список объектов в CSV файл
+ */
+public class CsvWritable implements Writable {
+
+ private final String separator;
+
+ /**
+ * Создаёт CsvWritable с разделителем по умолчанию (запятая).
+ */
+ public CsvWritable() {
+ this(",");
+ }
+
+ /**
+ * Создаёт CsvWritable с указанным разделителем.
+ *
+ * @param separator строка-разделитель
+ */
+ public CsvWritable(String separator) {
+ if (StringUtils.isEmpty(separator)) {
+ throw new IllegalArgumentException("Сепаратор не заполнен");
+ }
+ this.separator = separator;
+ }
+
+ /**
+ * Сохраняет список объектов в CSV файл
+ *
+ * @param data список объектов одного типа
+ * @param fileName путь к файлу (создаётся/перезаписывается)
+ */
+ @Override
+ public void writeToFile(List> data, String fileName) {
+ if (data == null || data.isEmpty()) {
+ throw new IllegalArgumentException("Данные не заполнены");
+ }
+ Class> clazz = data.get(0).getClass();
+ List columns = getAnnotatedFields(clazz);
+ List header = columns.stream().map(f -> {
+ CsvColumn ann = f.getAnnotation(CsvColumn.class);
+ return ann.name().isEmpty()
+ ? f.getName()
+ : ann.name();
+ }).toList();
+ Path path = Path.of(fileName);
+ try (BufferedWriter writer = Files.newBufferedWriter(path, CREATE, TRUNCATE_EXISTING)) {
+ writer.write(String.join(separator, header));
+ writer.newLine();
+ for (Object obj : data) {
+ List row = new ArrayList<>();
+ for (Field field : columns) {
+ field.setAccessible(true);
+ Object value = field.get(obj);
+ row.add(escapeCsv(formatValue(value)));
+ }
+ writer.write(String.join(separator, row));
+ writer.newLine();
+ }
+ } catch (IOException | IllegalAccessException e) {
+ throw new RuntimeException("Ошибка записи CSV файла", e);
+ }
+ }
+
+ private List getAnnotatedFields(Class> clazz) {
+ Field[] fields = clazz.getDeclaredFields();
+ return Arrays.stream(fields)
+ .filter(f -> f.isAnnotationPresent(CsvColumn.class))
+ .sorted(Comparator.comparingInt(f -> f.getAnnotation(CsvColumn.class).order()))
+ .toList();
+ }
+
+ private String formatValue(Object value) {
+ if (value == null) {
+ return "";
+ }
+ if (value instanceof Enum>) {
+ return ((Enum>) value).name();
+ }
+ if (value instanceof Collection>) {
+ return ((Collection>) value).stream()
+ .map(Objects::toString)
+ .collect(joining(";"));
+ }
+ return value.toString();
+ }
+
+ private String escapeCsv(String field) {
+ if (StringUtils.isEmpty(field)) {
+ return "";
+ }
+ boolean needQuotes = field.contains(",")
+ || field.contains("\"")
+ || field.contains("\n")
+ || field.contains("\r")
+ || field.contains(";");
+ String escaped = field.replace("\"", "\"\"");
+ return needQuotes
+ ? "\"" + escaped + "\""
+ : escaped;
+ }
+}
diff --git a/src/main/java/org/writer/Main.java b/src/main/java/org/writer/Main.java
index 40e8213..718943c 100644
--- a/src/main/java/org/writer/Main.java
+++ b/src/main/java/org/writer/Main.java
@@ -1,7 +1,40 @@
package org.writer;
+import com.github.javafaker.Faker;
+import org.writer.model.Months;
+import org.writer.model.Person;
+import org.writer.model.Student;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.IntStream;
+
public class Main {
public static void main(String[] args) {
- System.out.println("Hello world!");
+ Faker faker = new Faker(new Locale("ru"));
+ List people = IntStream.range(0, 10)
+ .mapToObj(i -> Person.builder()
+ .firstName(faker.name().firstName())
+ .lastName(faker.name().lastName())
+ .dayOfBirth(faker.number().numberBetween(1, 28))
+ .monthOfBirth(Months.values()[faker.number().numberBetween(0, Months.values().length)])
+ .yearOfBirth(faker.number().numberBetween(1970, 2010))
+ .build())
+ .toList();
+ List students = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ List scores = IntStream.range(0, 4)
+ .mapToObj(j -> String.valueOf(faker.number().numberBetween(50, 100)))
+ .toList();
+ students.add(Student.builder()
+ .name(faker.name().fullName())
+ .score(scores)
+ .build());
+ }
+ Writable writer = new CsvWritable();
+ writer.writeToFile(people, "people.csv");
+ writer.writeToFile(students, "students.csv");
+ System.out.println("CSV файлы (people.csv, students.csv) успешно заполнены");
}
}
\ No newline at end of file
diff --git a/src/main/java/org/writer/model/Person.java b/src/main/java/org/writer/model/Person.java
index ba8576b..82ca71a 100644
--- a/src/main/java/org/writer/model/Person.java
+++ b/src/main/java/org/writer/model/Person.java
@@ -3,20 +3,26 @@
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
+import org.writer.CsvColumn;
@Data
@Builder
@AllArgsConstructor
public class Person {
+ @CsvColumn(name = "first_name", order = 1)
private String firstName;
+ @CsvColumn(name = "last_name", order = 2)
private String lastName;
+ @CsvColumn(name = "day_of_birth", order = 3)
private int dayOfBirth;
+ @CsvColumn(name = "month_of_birth", order = 4)
private Months monthOfBirth;
+ @CsvColumn(name = "year_of_birth", order = 5)
private int yearOfBirth;
}
diff --git a/src/main/java/org/writer/model/Student.java b/src/main/java/org/writer/model/Student.java
index 2b549c4..6011853 100644
--- a/src/main/java/org/writer/model/Student.java
+++ b/src/main/java/org/writer/model/Student.java
@@ -3,6 +3,7 @@
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
+import org.writer.CsvColumn;
import java.util.List;
@@ -11,7 +12,9 @@
@AllArgsConstructor
public class Student {
+ @CsvColumn(name = "name", order = 1)
private String name;
+ @CsvColumn(name = "score", order = 2)
private List score;
}
\ No newline at end of file
diff --git a/src/test/java/org/writer/CsvWritableTest.java b/src/test/java/org/writer/CsvWritableTest.java
new file mode 100644
index 0000000..c431787
--- /dev/null
+++ b/src/test/java/org/writer/CsvWritableTest.java
@@ -0,0 +1,68 @@
+package org.writer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.writer.model.Months;
+import org.writer.model.Person;
+import org.writer.model.Student;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CsvWritableTest {
+
+ private Writable writable;
+
+ @TempDir
+ Path tempDir;
+
+ @BeforeEach
+ void setUp() {
+ writable = new CsvWritable();
+ }
+
+ @Test
+ void writesPersonCsv() throws IOException {
+ Person p1 = Person.builder()
+ .firstName("Ivan")
+ .lastName("Petrov")
+ .dayOfBirth(5)
+ .monthOfBirth(Months.MARCH)
+ .yearOfBirth(1990)
+ .build();
+ Person p2 = Person.builder()
+ .firstName("Anna")
+ .lastName("Ivanova")
+ .dayOfBirth(12)
+ .monthOfBirth(Months.DECEMBER)
+ .yearOfBirth(1985)
+ .build();
+ Path file = tempDir.resolve("persons.csv");
+
+ writable.writeToFile(List.of(p1, p2), file.toString());
+
+ String content = Files.readString(file);
+ assertThat(content).contains("first_name,last_name,day_of_birth,month_of_birth,year_of_birth");
+ assertThat(content).contains("Ivan,Petrov,5,MARCH,1990");
+ assertThat(content).contains("Anna,Ivanova,12,DECEMBER,1985");
+ }
+
+ @Test
+ void writesStudentCsvWithListField() throws IOException {
+ Student s = Student.builder().name("Sidor Sidorov").score(List.of("80", "90", "75")).build();
+ Path file = tempDir.resolve("students.csv");
+
+ writable.writeToFile(List.of(s), file.toString());
+
+ String content = Files.readString(file);
+ assertThat(content).contains("name,score");
+ assertThat(content).contains("Sidor Sidorov");
+ assertThat(content).contains("80;90;75");
+ }
+
+}
\ No newline at end of file
diff --git a/students.csv b/students.csv
new file mode 100644
index 0000000..a7b3ff0
--- /dev/null
+++ b/students.csv
@@ -0,0 +1,6 @@
+name,score
+Илья Алексеев,"97;54;82;61"
+Валентин Анисимов,"60;67;53;55"
+Ирина Мельникова,"87;57;88;67"
+Анна Леонидовна Романова,"64;64;63;63"
+Мишина Клавдия,"74;81;98;89"