Skip to content
Merged
1 change: 1 addition & 0 deletions resources/tasks.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
id,type,name,status,description,epic
203 changes: 203 additions & 0 deletions src/managers/FileBackedTaskManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package managers;

import managers.exceptions.ManagerSaveException;
import model.*;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

import static model.Type.*;

public class FileBackedTaskManager extends InMemoryTaskManager {

private final File filename;

public FileBackedTaskManager(File filename) {
this.filename = filename;
}

public static FileBackedTaskManager loadFromFile(File filename) {

if (!filename.exists()) {
/* Если файла не существует, то возвращается пустой менеджер
* с возможностью создать файл и записывать в него*/
return new FileBackedTaskManager(filename);
}

FileBackedTaskManager manager = new FileBackedTaskManager(filename);

try (BufferedReader reader = new BufferedReader(new FileReader(filename, StandardCharsets.UTF_8))) {
reader.readLine(); // пропуск первой служебной строки

/*конечный id, который будет присвоен idCount(далее счетчик) в manager*/
int maxId = 0;

while (reader.ready()) {
String line = reader.readLine();
if (line.isBlank()) {
continue;
}
String[] parts = line.split(",");

int id = Integer.parseInt(parts[0]);

maxId = Math.max(maxId, id); // сохраняется максимальный id

/* менеджер сам присваивает id,
* но не знает верного счетчика для каждого таска,
* поэтому счетчик устанавливается на id - 1 (то есть предыдущее значение),
* а уже в методе добавления счетчик увеличивается на один, устанавливая верное значение */
manager.setIdCount(id - 1);

Type type = Type.valueOf(parts[1]);
String title = parts[2];
Status status = Status.valueOf(parts[3]);
String description = parts[4];

if (type == TASK) {
manager.addTask(new Task(title, description, status));
} else if (type == EPIC) {
manager.addEpic(new Epic(title, description, status));
} else if (type == SUBTASK) {
manager.addSubTask(new SubTask(title, description, status, Integer.parseInt(parts[5])));
} else {
System.out.println("Такой формат не существует");
}
}
/*счетчик устанавливается на максимальный найденный id,
* теперь отсчет id всех новых тасков будет от этого значения*/
manager.setIdCount(maxId);

} catch (IOException e) {
e.printStackTrace();
}

return manager;
}

private void save() {

try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename, StandardCharsets.UTF_8))) {
bw.write("id,type,name,status,description,epic"); //первая служебная строка файла
bw.newLine();

writeTasks(bw, super.getTasks()); // записываются таски
writeTasks(bw, super.getEpics()); // записываются эпики
writeSubTasks(bw, super.getSubTasks()); // записываются сабтаски

} catch (IOException e) {
throw new ManagerSaveException("Ошибка сохранения файла", e);
}
}

private void writeTasks(BufferedWriter bw, ArrayList<? extends Task> tasks) throws IOException {
/* Для записи в файл таска или эпика требуются методы,
* которые реализованы в родительском классе Task,
* следовательно, можно воспользоваться дженериком
* и не создавать два одинаковых метода для тасков и эпиков*/

for (Task task : tasks) {
String line = String.format("%s,%s,%s,%s,%s",
task.getTaskId(),
task.getType(),
task.getTitle(),
task.getStatus(),
task.getDescription()
);

bw.write(line);
bw.newLine();
}
}

private void writeSubTasks(BufferedWriter bw, ArrayList<SubTask> subTasks) throws IOException {
/* Для записи сабтаска нам требуется метод getEpicId,
* поэтому метод writeSubTask отдельный*/
for (SubTask subTask : subTasks) {
String line = String.format("%s,%s,%s,%s,%s,%s",
subTask.getTaskId(),
subTask.getType(),
subTask.getTitle(),
subTask.getStatus(),
subTask.getDescription(),
subTask.getEpicId()
);

bw.write(line);
bw.newLine();
}
}

@Override
public void addTask(Task task) {
super.addTask(task);
save();
}

@Override
public void addEpic(Epic epic) {
super.addEpic(epic);
save();
}

@Override
public void addSubTask(SubTask subTask) {
super.addSubTask(subTask);
save();
}

@Override
public void updateTask(Task task) {
super.updateTask(task);
save();
}

@Override
public void updateEpic(Epic epic) {
super.updateTask(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();
}

@Override
public void clearTasks() {
super.clearTasks();
save();
}

@Override
public void clearEpics() {
super.clearEpics();
save();
}

@Override
public void clearSubTasks() {
super.clearSubTasks();
save();
}
}
8 changes: 8 additions & 0 deletions src/managers/InMemoryTaskManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ private void clearHistorySubTasks() {
}
}

public int getIdCount() {
return idCount;
}

public void setIdCount(int id) {
idCount = id;
}

@Override
public List<Task> getHistory() {
return historyManager.getHistory();
Expand Down
16 changes: 16 additions & 0 deletions src/managers/exceptions/ManagerSaveException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package managers.exceptions;

public class ManagerSaveException extends RuntimeException {

public ManagerSaveException() {
super();
}

public ManagerSaveException(String message) {
super(message);
}

public ManagerSaveException(String message, Throwable cause) {
super(message, cause);
}
}
4 changes: 4 additions & 0 deletions src/model/Epic.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public ArrayList<Integer> getSubtaskIds() {
return subtaskIds;
}

public Type getType() {
return Type.EPIC;
}

@Override
public String toString() {
return String.format("%s{id=%d, title=%s, description=%s, status=%s, subtasks=%s}",
Expand Down
4 changes: 4 additions & 0 deletions src/model/SubTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public int getEpicId() {
return epicId;
}

public Type getType() {
return Type.SUBTASK;
}

@Override
public String toString() {
return String.format("%s{id=%d, title=%s, description=%s, status=%s, epicId=%d}",
Expand Down
3 changes: 3 additions & 0 deletions src/model/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public void setStatus(Status status) {
this.status = status;
}

public Type getType() {
return Type.TASK;
}

@Override
public String toString() {
Expand Down
7 changes: 7 additions & 0 deletions src/model/Type.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package model;

public enum Type {
TASK,
EPIC,
SUBTASK
}
97 changes: 97 additions & 0 deletions test/managers/FileBackedTaskManagerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package managers;

import managers.exceptions.ManagerSaveException;
import model.Epic;
import model.Status;
import model.SubTask;
import model.Task;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import static org.junit.jupiter.api.Assertions.*;

public class FileBackedTaskManagerTest {
private File tempFile;
private FileBackedTaskManager manager;

@BeforeEach
public void beforeEach() throws IOException {
tempFile = Files.createTempFile("task", ".csv").toFile();
manager = new FileBackedTaskManager(tempFile);
}

@Test
public void shouldSaveAndLoad() {

Task task1 = new Task("task1", "demo1", Status.NEW); // id=1
manager.addTask(task1);

Epic epic1 = new Epic("epic1", "demo1", Status.NEW); // id=2
manager.addEpic(epic1);
Epic epic2 = new Epic("epic2", "demo2", Status.NEW); // id=3
manager.addEpic(epic2);

SubTask subTask1 = new SubTask("subtask1", "demo1", Status.NEW, 2); //id=4
manager.addSubTask(subTask1);

Task task2 = new Task("task2", "demo2", Status.NEW); // id=5
manager.addTask(task2);

manager.deleteTask(1);

FileBackedTaskManager manager1 = FileBackedTaskManager.loadFromFile(tempFile);

assertNull(manager1.getTask(1));
assertEquals(epic1, manager1.getEpic(2));
assertEquals(epic2, manager1.getEpic(3));
assertEquals(subTask1, manager1.getSubTask(4));
assertEquals(subTask1, manager1.getSubTasksFromEpic(2).getFirst());
assertEquals(task2, manager1.getTask(5));
assertEquals(manager.getIdCount(), manager1.getIdCount());
}

@Test
public void shouldThrowManagerSaveException() {
manager = new FileBackedTaskManager(new File("blablabla/test.txt"));

assertThrows(ManagerSaveException.class,
() -> manager.addTask(new Task("title", "demo", Status.NEW)));
}

@Test
public void shouldReturnEmptyManager() {
FileBackedTaskManager manager1 = FileBackedTaskManager.loadFromFile(new File("resources/test.txt"));

assertEquals(0, manager1.getIdCount());
assertEquals(0, manager1.getTasks().size());
assertEquals(0, manager1.getEpics().size());
assertEquals(0, manager1.getSubTasks().size());
}

@Test
public void shouldSupportUTF8() {
String[] words = {
"Hello", "World",
"Привет", "Мир",
"Selam", "Dünya",
"你好", "世界",
"👋", "🌎"
};

for (int i = 0; i < words.length; i += 2) {
manager.addTask(new Task(words[i], words[i + 1], Status.NEW));
}

FileBackedTaskManager manager1 = FileBackedTaskManager.loadFromFile(tempFile);
int id = 1;
for (int i = 0; i < words.length; i += 2) {
Task task = manager1.getTask(id++);
assertEquals(words[i], task.getTitle());
assertEquals(words[i + 1], task.getDescription());
}
}
}