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 подзадачи должен совпадать"); + } +}