diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100644
index 0000000..742470c
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,16 @@
+
+
+
+ 13.3.0
+ JavaOnly
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 353a9b5..4559b5d 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
# java-kanban
-Repository for homework project.
+
+Проект - Трекер задач.
+
+## Автор
+Ксения
\ No newline at end of file
diff --git a/java-kanban.iml b/java-kanban.iml
index 66c15d2..019c6c8 100644
--- a/java-kanban.iml
+++ b/java-kanban.iml
@@ -1,5 +1,8 @@
+
+
+
diff --git a/src/model/Epic.java b/src/model/Epic.java
index 00edfd5..2cd2cae 100644
--- a/src/model/Epic.java
+++ b/src/model/Epic.java
@@ -14,7 +14,7 @@ public ArrayList getSubtaskIds() {
}
public void setSubtaskIds(ArrayList subtaskIds) {
- this.subtaskIds = subtaskIds;
+ this.subtaskIds = new ArrayList<>(subtaskIds);
}
// Очищение всех ID подзадач
@@ -34,6 +34,11 @@ public void removeSubtaskId(Integer subtaskId) {
subtaskIds.remove(subtaskId);
}
+ @Override
+ public TaskType getType() {
+ return TaskType.EPIC;
+ }
+
@Override
public String toString() {
return "Epic{" +
diff --git a/src/model/Subtask.java b/src/model/Subtask.java
index 50b0071..e888412 100644
--- a/src/model/Subtask.java
+++ b/src/model/Subtask.java
@@ -21,6 +21,11 @@ public void setEpicId(int epicId) {
this.epicId = epicId;
}
+ @Override
+ public TaskType getType() {
+ return TaskType.SUBTASK;
+ }
+
@Override
public void setId(int id) {
if (id == this.getEpicId()) {
diff --git a/src/model/Task.java b/src/model/Task.java
index 8ed25ad..0e96c0f 100644
--- a/src/model/Task.java
+++ b/src/model/Task.java
@@ -14,6 +14,10 @@ public Task(String name, String description, Status status) {
this.status = status;
}
+ public TaskType getType() {
+ return TaskType.TASK;
+ }
+
public int getId() {
return id;
}
diff --git a/src/model/TaskType.java b/src/model/TaskType.java
new file mode 100644
index 0000000..3e5d34b
--- /dev/null
+++ b/src/model/TaskType.java
@@ -0,0 +1,7 @@
+package model;
+
+public enum TaskType {
+ TASK,
+ EPIC,
+ SUBTASK
+}
diff --git a/src/service/FileBackedTaskManager.java b/src/service/FileBackedTaskManager.java
new file mode 100644
index 0000000..44e50ce
--- /dev/null
+++ b/src/service/FileBackedTaskManager.java
@@ -0,0 +1,267 @@
+package service;
+
+import model.Epic;
+import model.Status;
+import model.Subtask;
+import model.Task;
+import model.TaskType;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.TreeMap;
+
+public class FileBackedTaskManager extends InMemoryTaskManager {
+ private final File file;
+
+ public FileBackedTaskManager(File file) {
+ this.file = file;
+ }
+
+ // Сохраняет текущее состояние менеджера в CSV-файл
+ private void save() {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ writer.write("id,type,name,status,description,epic");
+ writer.newLine();
+
+ TreeMap allTasks = new TreeMap<>();
+
+ for (Task task : getTasks()) {
+ allTasks.put(task.getId(), task);
+ }
+
+ for (Epic epic : getEpics()) {
+ allTasks.put(epic.getId(), epic);
+ }
+
+ for (Subtask subtask : getSubtasks()) {
+ allTasks.put(subtask.getId(), subtask);
+ }
+
+ for (Task task : allTasks.values()) {
+ writer.write(toString(task));
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ throw new ManagerSaveException("Failed to save tasks to file: " + file, e);
+ }
+ }
+
+ // Преобразует задачу в строку формата CSV
+ private String toString(Task task) {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append(task.getId()).append(",");
+ builder.append(task.getType()).append(",");
+
+ builder.append(task.getName()).append(",");
+ builder.append(task.getStatus()).append(",");
+ builder.append(task.getDescription()).append(",");
+
+ if (task instanceof Subtask) {
+ Subtask subtask = (Subtask) task;
+ builder.append(subtask.getEpicId());
+ }
+
+ return builder.toString();
+ }
+
+ // Преобразует строку CSV в объект задачи
+ private static Task fromString(String value) {
+ String[] fields = value.split(",", -1);
+
+ int id = Integer.parseInt(fields[0]);
+ TaskType taskType = TaskType.valueOf(fields[1]);
+ String name = fields[2];
+ Status status = Status.valueOf(fields[3]);
+ String description = fields[4];
+
+ Task task;
+
+ if (taskType == TaskType.TASK) {
+ task = new Task(name, description, status);
+ } else if (taskType == TaskType.EPIC) {
+ task = new Epic(name, description);
+ task.setStatus(status);
+ } else {
+ int epicId = Integer.parseInt(fields[5]);
+ task = new Subtask(name, description, status, epicId);
+ }
+
+ task.setId(id);
+ return task;
+ }
+
+ // Восстанавливает менеджер из CSV-файла
+ public static FileBackedTaskManager loadFromFile(File file) {
+ FileBackedTaskManager manager = new FileBackedTaskManager(file);
+ int maxId = 0;
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line = reader.readLine(); // пропуск заголовка
+
+ while ((line = reader.readLine()) != null) {
+ if (line.isBlank()) {
+ continue;
+ }
+
+ Task task = fromString(line);
+
+ switch (task.getType()) {
+ case TASK:
+ manager.putLoadedTask(task);
+ break;
+ case EPIC:
+ manager.putLoadedEpic((Epic) task);
+ break;
+ case SUBTASK:
+ manager.putLoadedSubtask((Subtask) task);
+ break;
+ }
+
+ if (task.getId() > maxId) {
+ maxId = task.getId();
+ }
+ }
+
+ manager.setNextId(maxId + 1);
+ return manager;
+ } catch (IOException e) {
+ throw new ManagerSaveException("Failed to load tasks from file: " + file, e);
+ }
+ }
+
+ // После каждого изменения сохраняем состояние менеджера в файл
+ @Override
+ public void clearTasks() {
+ super.clearTasks();
+ save();
+ }
+
+ @Override
+ public void clearEpics() {
+ super.clearEpics();
+ save();
+ }
+
+ @Override
+ public void clearSubtasks() {
+ super.clearSubtasks();
+ save();
+ }
+
+ @Override
+ public Task createTask(Task task) {
+ Task createdTask = super.createTask(task);
+ save();
+ return createdTask;
+ }
+
+ @Override
+ public Epic createEpic(Epic epic) {
+ Epic createdEpic = super.createEpic(epic);
+ save();
+ return createdEpic;
+ }
+
+ @Override
+ public Subtask createSubtask(Subtask subtask) {
+ Subtask createdSubtask = super.createSubtask(subtask);
+ save();
+ return createdSubtask;
+ }
+
+ @Override
+ public void updateTask(Task task) {
+ super.updateTask(task);
+ save();
+ }
+
+ @Override
+ public void updateEpic(Epic epic) {
+ super.updateEpic(epic);
+ save();
+ }
+
+ @Override
+ public void updateSubtask(Subtask subtask) {
+ super.updateSubtask(subtask);
+ save();
+ }
+
+ @Override
+ public void deleteTask(int id) {
+ super.deleteTask(id);
+ save();
+ }
+
+ @Override
+ public void deleteEpic(int id) {
+ super.deleteEpic(id);
+ save();
+ }
+
+ @Override
+ public void deleteSubtask(int id) {
+ super.deleteSubtask(id);
+ save();
+ }
+
+ // Демонстрационный сценарий работы файлового менеджера
+ public static void main(String[] args) {
+ File file = new File("tasks.csv");
+
+ FileBackedTaskManager manager = new FileBackedTaskManager(file);
+
+ // Создание обычных задач
+ Task task1 = new Task("Task1", "Description of task 1", Status.NEW);
+ Task task2 = new Task("Task2", "Description of task 2", Status.NEW);
+ manager.createTask(task1);
+ manager.createTask(task2);
+
+ // Создание эпика
+ Epic epic1 = new Epic("Epic1", "Description of epic 1");
+ manager.createEpic(epic1);
+
+ // Создание подзадач для эпика
+ Subtask subtask1 = new Subtask("Subtask1", "Description of subtask 1", Status.NEW, epic1.getId());
+ Subtask subtask2 = new Subtask("Subtask2", "Description of subtask 2", Status.NEW, epic1.getId());
+ manager.createSubtask(subtask1);
+ manager.createSubtask(subtask2);
+
+ // Меняем статус одной подзадачи для проверки пересчёта эпика
+ subtask1.setStatus(Status.DONE);
+ manager.updateSubtask(subtask1);
+
+ // Загрузка нового менеджера из того же файла
+ FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(file);
+
+ // Вывод данных из нового менеджера
+ System.out.println("Обычные задачи из загруженного менеджера:");
+ for (Task task : loadedManager.getTasks()) {
+ System.out.println(task);
+ }
+
+ System.out.println("Эпики из загруженного менеджера:");
+ for (Epic epic : loadedManager.getEpics()) {
+ System.out.println(epic);
+ }
+
+ System.out.println("Подзадачи из загруженного менеджера:");
+ for (Subtask subtask : loadedManager.getSubtasks()) {
+ System.out.println(subtask);
+ }
+
+ // Проверка, что данные действительно загрузились
+ if (loadedManager.getTasks().size() == manager.getTasks().size()
+ && loadedManager.getEpics().size() == manager.getEpics().size()
+ && loadedManager.getSubtasks().size() == manager.getSubtasks().size()) {
+ System.out.println("Данные успешно сохранены и загружены.");
+ } else {
+ System.out.println("Ошибка: данные после загрузки не совпадают.");
+ }
+ }
+}
diff --git a/src/service/InMemoryTaskManager.java b/src/service/InMemoryTaskManager.java
index 1c4e95a..b2d6126 100644
--- a/src/service/InMemoryTaskManager.java
+++ b/src/service/InMemoryTaskManager.java
@@ -191,7 +191,7 @@ public void updateEpic(Epic epic) {
}
// Метод для обновления статуса Эпик при обновлении Подзадач
- private void updateEpicStatus(Epic epic) {
+ protected void updateEpicStatus(Epic epic) {
if (epic.getSubtaskIds().isEmpty()) {
epic.setStatus(Status.NEW);
return;
@@ -276,4 +276,30 @@ public ArrayList getEpicSubtasks(int epicId) {
}
return result;
}
+
+ // Служебные методы для восстановления менеджера из файла
+ protected void putLoadedTask(Task task) {
+ tasks.put(task.getId(), task);
+ }
+
+ protected void putLoadedEpic(Epic epic) {
+ epics.put(epic.getId(), epic);
+ }
+
+ protected void putLoadedSubtask(Subtask subtask) {
+ subtasks.put(subtask.getId(), subtask);
+
+ Epic epic = epics.get(subtask.getEpicId());
+ if (epic != null) {
+ epic.addSubtaskId(subtask.getId());
+ }
+ }
+
+ protected void setNextId(int nextId) {
+ this.nextId = nextId;
+ }
+
+ protected ArrayList getLoadedEpics() {
+ return new ArrayList<>(epics.values());
+ }
}
diff --git a/src/service/ManagerSaveException.java b/src/service/ManagerSaveException.java
new file mode 100644
index 0000000..40b42b4
--- /dev/null
+++ b/src/service/ManagerSaveException.java
@@ -0,0 +1,11 @@
+package service;
+
+public class ManagerSaveException extends RuntimeException {
+ public ManagerSaveException(String message) {
+ super(message);
+ }
+
+ public ManagerSaveException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/test/service/FileBackedTaskManagerTest.java b/test/service/FileBackedTaskManagerTest.java
new file mode 100644
index 0000000..2e2bbe1
--- /dev/null
+++ b/test/service/FileBackedTaskManagerTest.java
@@ -0,0 +1,100 @@
+package service;
+
+import model.Epic;
+import model.Status;
+import model.Subtask;
+import model.Task;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FileBackedTaskManagerTest {
+
+ // Тест на сохранение и загрузку пустого менеджера
+ @Test
+ void shouldSaveAndLoadEmptyManager() throws IOException {
+ File tempFile = File.createTempFile("tasks", ".csv");
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
+ writer.write("id,type,name,status,description,epic");
+ writer.newLine();
+ }
+
+ FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile);
+
+ assertTrue(loadedManager.getTasks().isEmpty(), "Список задач должен быть пустым");
+ assertTrue(loadedManager.getEpics().isEmpty(), "Список эпиков должен быть пустым");
+ assertTrue(loadedManager.getSubtasks().isEmpty(), "Список подзадач должен быть пустым");
+ }
+
+ // Тест на сохранение нескольких задач
+ @Test
+ void shouldSaveMultipleTasks() throws IOException {
+ File tempFile = File.createTempFile("tasks", ".csv");
+
+ FileBackedTaskManager manager = new FileBackedTaskManager(tempFile);
+
+ Task task = new Task("Task1", "Description1", Status.NEW);
+ manager.createTask(task);
+
+ Epic epic = new Epic("Epic1", "Description epic");
+ manager.createEpic(epic);
+
+ Subtask subtask = new Subtask("Subtask1", "Description sub", Status.NEW, epic.getId());
+ manager.createSubtask(subtask);
+
+ FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile);
+
+ assertEquals(1, loadedManager.getTasks().size(), "Должна быть 1 задача");
+ assertEquals(1, loadedManager.getEpics().size(), "Должен быть 1 эпик");
+ assertEquals(1, loadedManager.getSubtasks().size(), "Должна быть 1 подзадача");
+ }
+
+ // Тест на загрузку нескольких задач из файла
+ @Test
+ void shouldLoadMultipleTasks() throws IOException {
+ File tempFile = File.createTempFile("tasks", ".csv");
+
+ FileBackedTaskManager manager = new FileBackedTaskManager(tempFile);
+
+ Task task = new Task("Task1", "Description1", Status.NEW);
+ manager.createTask(task);
+
+ Epic epic = new Epic("Epic1", "Description epic");
+ manager.createEpic(epic);
+
+ Subtask subtask = new Subtask("Subtask1", "Description sub", Status.NEW, epic.getId());
+ manager.createSubtask(subtask);
+
+ subtask.setStatus(Status.DONE);
+ manager.updateSubtask(subtask);
+
+ FileBackedTaskManager loadedManager = FileBackedTaskManager.loadFromFile(tempFile);
+
+ Task loadedTask = loadedManager.getTasks().get(0);
+ Epic loadedEpic = loadedManager.getEpics().get(0);
+ Subtask loadedSubtask = loadedManager.getSubtasks().get(0);
+
+ assertEquals("Task1", loadedTask.getName(), "Имя задачи должно совпадать");
+ assertEquals("Description1", loadedTask.getDescription(), "Описание задачи должно совпадать");
+ assertEquals(Status.NEW, loadedTask.getStatus(), "Статус задачи должен совпадать");
+
+ assertEquals("Epic1", loadedEpic.getName(), "Имя эпика должно совпадать");
+ assertEquals("Description epic", loadedEpic.getDescription(), "Описание эпика должно совпадать");
+ assertEquals(Status.DONE, loadedEpic.getStatus(), "Статус эпика должен пересчитаться по подзадаче");
+
+ assertEquals("Subtask1", loadedSubtask.getName(), "Имя подзадачи должно совпадать");
+ assertEquals("Description sub", loadedSubtask.getDescription(), "Описание подзадачи должно совпадать");
+ assertEquals(Status.DONE, loadedSubtask.getStatus(), "Статус подзадачи должен совпадать");
+ assertEquals(epic.getId(), loadedSubtask.getEpicId(), "Epic ID подзадачи должен совпадать");
+
+ assertEquals(task.getId(), loadedTask.getId(), "ID задачи должен совпадать");
+ assertEquals(epic.getId(), loadedEpic.getId(), "ID эпика должен совпадать");
+ assertEquals(subtask.getId(), loadedSubtask.getId(), "ID подзадачи должен совпадать");
+ }
+}