diff --git a/src/ru/yandex/javacourse/exceptions/ManagerSaveException.java b/src/ru/yandex/javacourse/exceptions/ManagerSaveException.java new file mode 100644 index 0000000..6a3cfd0 --- /dev/null +++ b/src/ru/yandex/javacourse/exceptions/ManagerSaveException.java @@ -0,0 +1,7 @@ +package ru.yandex.javacourse.exceptions; + +public class ManagerSaveException extends RuntimeException { + public ManagerSaveException(String message) { + super(message); + } +} diff --git a/src/ru/yandex/javacourse/model/TaskType.java b/src/ru/yandex/javacourse/model/TaskType.java new file mode 100644 index 0000000..f70e871 --- /dev/null +++ b/src/ru/yandex/javacourse/model/TaskType.java @@ -0,0 +1,8 @@ +package ru.yandex.javacourse.model; + +public enum TaskType { + TASK, + EPIC, + SUBTASK +} + diff --git a/src/ru/yandex/javacourse/service/FileBackedTaskManager.java b/src/ru/yandex/javacourse/service/FileBackedTaskManager.java new file mode 100644 index 0000000..47b8ba9 --- /dev/null +++ b/src/ru/yandex/javacourse/service/FileBackedTaskManager.java @@ -0,0 +1,305 @@ +package ru.yandex.javacourse.service; + +import ru.yandex.javacourse.exceptions.ManagerSaveException; +import ru.yandex.javacourse.model.*; + +import java.io.*; +import java.util.ArrayList; + +public class FileBackedTaskManager extends InMemoryTaskManager { + private final File file; + + public static void main(String[] args) throws IOException { + File file = new File("src/tasks.csv"); + System.out.println("Файл создан: " + file.getAbsolutePath()); + FileBackedTaskManager fileBackedTaskManager = Managers.getDefaultFileBacked(file); + + Task task = fileBackedTaskManager.addTask(new Task("Задача", "Описание задачи")); + Epic epic = fileBackedTaskManager.addEpic(new Epic("Эпик", "Описание эпика")); + Subtask subtask = fileBackedTaskManager.addSubtask(new Subtask("Подзадача", + "Описание подзадачи", epic.getId())); + + FileBackedTaskManager newFileBackedTaskManager = loadFromFile(file); + Task newTask = newFileBackedTaskManager.addTask(new Task("Новая задача", + "Описание новой задачи")); + + Subtask updatedSubtask = new Subtask("Подзадача", "Описание подзадачи", epic.getId()); + updatedSubtask.setId(subtask.getId()); + updatedSubtask.setStatus(TaskStatus.DONE); + newFileBackedTaskManager.updateSubtask(updatedSubtask); + } + + public FileBackedTaskManager(File file) { + this.file = file; + } + + /** + * Сохраняет состояния менеджера в файл. + * Каждая задача становится отдельной строчкой + */ + public void save() throws ManagerSaveException { + try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { + bw.write("id,type,name,status,description,epic"); + bw.newLine(); + + ArrayList tasks = getAllTasks(); + for (Task task : tasks) { + String taskString = toString(task); + bw.write(taskString); + bw.newLine(); + } + + ArrayList epics = getAllEpics(); + for (Epic epic : epics) { + String epicString = toString(epic); + bw.write(epicString); + bw.newLine(); + } + + ArrayList subtasks = getAllSubtasks(); + for (Subtask subtask : subtasks) { + String subtaskString = toString(subtask); + bw.write(subtaskString); + bw.newLine(); + } + } catch (IOException exception) { + throw new ManagerSaveException("Ошибка во время работы метода автосохранения"); + } + + } + + /** + * Вызывает родительский метод удаления всех задач + * Сохраняет состояние в файл + */ + @Override + public void deleteAllTasks() { + super.deleteAllTasks(); + save(); + } + + /** + * Вызывает родительский метод добавления задачи + * Сохраняет состояние в файл + * + * @param task объект задачи, которую нужно добавить (может быть null) + * @return копия созданной задачи с присвоенным ID или null, если передан null + */ + @Override + public Task addTask(Task task) { + Task addedTask = super.addTask(task); + save(); + return addedTask; + } + + /** + * Вызывает родительский метод обновления задачи + * Сохраняет состояние в файл + * + * @param task объект задачи, которую нужно обновить (может быть null) + */ + @Override + public void updateTask(Task task) { + super.updateTask(task); + save(); + } + + /** + * Вызывает родительский метод удаления задачи по id + * Сохраняет состояние в файл + */ + @Override + public void deleteTaskById(int id) { + super.deleteTaskById(id); + save(); + } + + /** + * Вызывает родительский метод удаления всех эпиков вместе с подзадачами + * Сохраняет состояние в файл + */ + @Override + public void deleteAllEpics() { + super.deleteAllEpics(); + save(); + } + + /** + * Вызывает родительский метод добавления эпика + * Сохраняет состояние в файл + * + * @param epic объект эпика, который нужно добавить (может быть null) + * @return копия созданного эпика с присвоенным ID или null, если передан null + */ + @Override + public Epic addEpic(Epic epic) { + Epic addedEpic = super.addEpic(epic); + save(); + return addedEpic; + } + + /** + * Вызывает родительский метод обновления эпика + * Сохраняет состояние в файл + * + * @param epic объект эпика, который нужно обновить (может быть null) + */ + @Override + public void updateEpic(Epic epic) { + super.updateEpic(epic); + save(); + } + + /** + * Вызывает родительский метод удаления эпика по id + * Сохраняет состояние в файл + */ + @Override + public void deleteEpicById(int id) { + super.deleteEpicById(id); + save(); + } + + /** + * Вызывает родительский метод удаления всех подзадач + * Сохраняет состояние в файл + */ + @Override + public void deleteAllSubtasks() { + super.deleteAllSubtasks(); + save(); + } + + /** + * Вызывает родительский метод добавления подзадачи + * Сохраняет состояние в файл + * + * @param subtask объект подзадачи, которую нужно добавить (может быть null) + * @return копия созданной подзадачи с присвоенным ID или null, если передан null + */ + @Override + public Subtask addSubtask(Subtask subtask) { + Subtask addedSubtask = super.addSubtask(subtask); + save(); + return addedSubtask; + } + + /** + * Вызывает родительский метод обновления подзадачи + * Сохраняет состояние в файл + * + * @param subtask объект подзадачи, которую нужно обновить (может быть null) + */ + @Override + public void updateSubtask(Subtask subtask) { + super.updateSubtask(subtask); + save(); + } + + /** + * Вызывает родительский метод удаления подзадачи по id + * Сохраняет состояние в файл + */ + @Override + public void deleteSubtaskById(int id) { + super.deleteSubtaskById(id); + save(); + } + + /** + * Преобразует входную задачу в строку в зависимости от типа задачи + * Сохраняет состояние в файл + */ + public String toString(Task task) { + if (task == null) { + return ""; + } + + TaskType taskType; + String epicId = ""; + if (task instanceof Subtask subtask) { + taskType = TaskType.SUBTASK; + epicId = Integer.toString(subtask.getEpicId()); + } else if (task instanceof Epic) { + taskType = TaskType.EPIC; + } else { + taskType = TaskType.TASK; + } + return String.format("%s,%s,%s,%s,%s,%s", + task.getId(), taskType, task.getName(), task.getStatus(), task.getDescription(), epicId); + } + + /** + * Преобразует строку в конкретный тип задачи в зависимости от типа + */ + public Task fromString(String value) { + String[] splitValues = value.split(","); + int taskId = Integer.parseInt(splitValues[0]); + TaskType taskType = TaskType.valueOf(splitValues[1]); + String taskName = splitValues[2]; + TaskStatus taskStatus = TaskStatus.valueOf(splitValues[3]); + String taskDescription = splitValues[4]; + + if (taskType.equals(TaskType.SUBTASK)) { + int epicId = Integer.parseInt(splitValues[5]); + Subtask subTask = new Subtask(taskName, taskDescription, taskStatus, epicId); + subTask.setId(taskId); + return subTask; + } else if (taskType.equals(TaskType.EPIC)) { + Epic epic = new Epic(taskName, taskDescription); + epic.setId(taskId); + return epic; + } else { + Task task = new Task(taskName, taskDescription); + task.setId(taskId); + task.setStatus(taskStatus); + return task; + } + } + + /** + * Заполняет менеджер задач информацией из файла + * + * @param file файл, из которого надо получить все задачи, подзадачи и эпики + * @return созданный новый менеджер, который заполнен + */ + public static FileBackedTaskManager loadFromFile(File file) throws IOException { + FileBackedTaskManager fileBackedTaskManager = Managers.getDefaultFileBacked(file); + int lastId = 0; + if (!file.exists() || !file.isFile()) { + throw new IOException("Указанный файл не существует"); + } + + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + br.readLine(); + while (br.ready()) { + String fileString = br.readLine(); + if (!fileString.isBlank()) { + Task task = fileBackedTaskManager.fromString(fileString); + lastId = Integer.max(lastId, task.getId()); + if (task instanceof Subtask subtask) { + fileBackedTaskManager.subtasks.put(subtask.getId(), subtask); + } else if (task instanceof Epic epic) { + fileBackedTaskManager.epics.put(epic.getId(), epic); + } else { + fileBackedTaskManager.tasks.put(task.getId(), task); + } + } + } + } catch (IOException exception) { + System.out.println("Возникла проблема при восстановлении менеджера из файла"); + } + + for (Subtask subtask : fileBackedTaskManager.getAllSubtasks()) { + Epic epic = fileBackedTaskManager.epics.get(subtask.getEpicId()); + epic.addSubtaskId(subtask.getId()); + } + + for (Epic epic : fileBackedTaskManager.getAllEpics()) { + fileBackedTaskManager.updateEpic(epic); + } + + fileBackedTaskManager.idCount = lastId; + return fileBackedTaskManager; + } +} diff --git a/src/ru/yandex/javacourse/service/InMemoryTaskManager.java b/src/ru/yandex/javacourse/service/InMemoryTaskManager.java index 2172fe3..da2ee2e 100644 --- a/src/ru/yandex/javacourse/service/InMemoryTaskManager.java +++ b/src/ru/yandex/javacourse/service/InMemoryTaskManager.java @@ -6,10 +6,10 @@ import java.util.List; public class InMemoryTaskManager implements TaskManager { - private static int idCount = 0; - private final HashMap tasks = new HashMap<>(); - private final HashMap epics = new HashMap<>(); - private final HashMap subtasks = new HashMap<>(); + protected int idCount = 0; + protected final HashMap tasks = new HashMap<>(); + protected final HashMap epics = new HashMap<>(); + protected final HashMap subtasks = new HashMap<>(); private final HistoryManager historyManager = Managers.getDefaultHistory(); /** diff --git a/src/ru/yandex/javacourse/service/Managers.java b/src/ru/yandex/javacourse/service/Managers.java index 16cda02..fe585a9 100644 --- a/src/ru/yandex/javacourse/service/Managers.java +++ b/src/ru/yandex/javacourse/service/Managers.java @@ -1,5 +1,7 @@ package ru.yandex.javacourse.service; +import java.io.File; + public class Managers { public static TaskManager getDefault() { @@ -9,4 +11,8 @@ public static TaskManager getDefault() { public static HistoryManager getDefaultHistory() { return new InMemoryHistoryManager(); } + + public static FileBackedTaskManager getDefaultFileBacked(File file) { + return new FileBackedTaskManager(file); + } } diff --git a/test/ru/yandex/javacourse/service/FileBackedTaskManagerTest.java b/test/ru/yandex/javacourse/service/FileBackedTaskManagerTest.java new file mode 100644 index 0000000..e4ea181 --- /dev/null +++ b/test/ru/yandex/javacourse/service/FileBackedTaskManagerTest.java @@ -0,0 +1,135 @@ +package ru.yandex.javacourse.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import ru.yandex.javacourse.model.Epic; +import ru.yandex.javacourse.model.Subtask; +import ru.yandex.javacourse.model.Task; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FileBackedTaskManagerTest { + private static final String TASK_NAME = "Задача"; + private static final String TASK_DESCRIPTION = "Новая задача"; + + FileBackedTaskManager fileBackedTaskManager; + File file; + + @BeforeEach + @DisplayName("Cоздаёт перед каждым тестом новый менеджер и файл") + public void create() throws IOException { + file = File.createTempFile("tmpFile", ".txt"); + fileBackedTaskManager = new FileBackedTaskManager(file); + } + + @Test + @DisplayName("Должен успешно создавать пустой файл") + public void test_Save_WhenManagerSaved_FileShouldBeEmpty() { + //given + fileBackedTaskManager.save(); + List fileContent = new ArrayList<>(); + String firstString = ""; + + //when + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + firstString = br.readLine(); + while (br.ready()) { + String fileString = br.readLine(); + fileContent.add(fileString); + } + } catch (IOException exception) { + System.out.println("Не удалось прочитать информацию из файла"); + } + //then + assertNotNull(file, "Файл не создан"); + assertTrue(firstString.contains("id"), "В файле отсутствует строчка с параметрами"); + assertEquals(0, fileContent.size(), "В файле не корректное количество строк"); + } + + @Test + @DisplayName("Должен успешно добавлять задачу в файл") + public void test_AddTask_WhenTaskAddedToFileBacked_FileShouldBeNotEmpty() { + //given + Task task = fileBackedTaskManager.addTask(new Task(TASK_NAME, TASK_DESCRIPTION)); + + //when + int taskId = -1; + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + br.readLine(); + while (br.ready()) { + String taskInfo = br.readLine(); + String[] splitTaskInfo = taskInfo.split(","); + taskId = Integer.parseInt(splitTaskInfo[0]); + } + } catch (IOException exception) { + System.out.println("Не удалось прочитать информацию из файла"); + } + + //then + assertEquals(task.getId(), taskId, "Задачи не совпадают."); + } + + @Test + @DisplayName("Должен успешно добавлять несколько задач в файл") + public void test_AddTask_WhenTwoSubtaskAddedToFileBacked_FileShouldStoreAll() { + //given + Task task = fileBackedTaskManager.addTask(new Task(TASK_NAME, TASK_DESCRIPTION)); + Task secondTask = fileBackedTaskManager.addTask(new Task(TASK_NAME, TASK_DESCRIPTION)); + + //when + List fileContent = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + br.readLine(); + while (br.ready()) { + String taskInfo = br.readLine(); + fileContent.add(taskInfo); + } + } catch (IOException exception) { + System.out.println("Не удалось прочитать информацию из файла"); + } + + //then + assertNotNull(fileContent, "Файл должен быть не пустым"); + assertEquals(2, fileContent.size(), "В файл сохранилось не корректное количество задач"); + } + + @Test + @DisplayName("Не должен ничего возвращать из пустого файла") + public void test_LoadFromFile_WhenReadFromFile_ShouldBeEmpty() throws IOException { + //given + FileBackedTaskManager newFileBackedTaskManager = FileBackedTaskManager.loadFromFile(file); + + //when + ArrayList tasks = newFileBackedTaskManager.getAllTasks(); + ArrayList subtasks = newFileBackedTaskManager.getAllSubtasks(); + ArrayList epics = newFileBackedTaskManager.getAllEpics(); + + //then + assertEquals(0, tasks.size(), "Из должно быть получено 0 задач"); + assertEquals(0, subtasks.size(), "Из должно быть получено 0 подзадач"); + assertEquals(0, epics.size(), "Из должно быть получено 0 эпиков"); + } + + @Test + @DisplayName("Должен возвращать все задачи, добавленные в файл") + public void test_LoadFromFile_WhenReadFromFile_ShouldReturnAllTasks() throws IOException { + //given + Task task = fileBackedTaskManager.addTask(new Task(TASK_NAME, TASK_DESCRIPTION)); + Task secondTask = fileBackedTaskManager.addTask(new Task(TASK_NAME, TASK_DESCRIPTION)); + FileBackedTaskManager newFileBackedTaskManager = FileBackedTaskManager.loadFromFile(file); + + //when + ArrayList tasks = newFileBackedTaskManager.getAllTasks(); + + //then + assertEquals(2, tasks.size(), "Из должно быть получено 0 задач"); + } +} \ No newline at end of file diff --git a/test/ru/yandex/javacourse/service/ManagersTest.java b/test/ru/yandex/javacourse/service/ManagersTest.java index df47060..7180a1d 100644 --- a/test/ru/yandex/javacourse/service/ManagersTest.java +++ b/test/ru/yandex/javacourse/service/ManagersTest.java @@ -3,6 +3,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.io.File; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.*; class ManagersTest { @@ -27,4 +30,15 @@ public void test_getDefaultHistory_WhenCreateNewHistoryManager_ShouldReturnNotNu assertNotNull(historyManager); } + @Test + @DisplayName("Должен возвращать не-null менеджер файла при вызове метода создания") + public void test_getDefaultFileBacked_WhenCreateNewBackedManager_ShouldReturnNotNull() throws IOException { + //given & when + File file = File.createTempFile("tmpFile", ".txt"); + FileBackedTaskManager fileBackedTaskManager = Managers.getDefaultFileBacked(file); + + //then + assertNotNull(fileBackedTaskManager); + } + } \ No newline at end of file