diff --git a/java-kanban.iml b/java-kanban.iml index 635e7f3..2642f22 100644 --- a/java-kanban.iml +++ b/java-kanban.iml @@ -40,5 +40,21 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Main.java b/src/Main.java index 428cc9c..0b824b0 100644 --- a/src/Main.java +++ b/src/Main.java @@ -5,6 +5,9 @@ import tasks.Subtask; import tasks.Task; +import java.time.Duration; +import java.time.LocalDateTime; + public class Main { public static void main(String[] args) { @@ -12,8 +15,8 @@ public static void main(String[] args) { TaskManager taskManager = Managers.getDefault(); // Создаем задачи и добавляем их в managers.TaskManager - Task task1 = new Task("Закупка продуктов", "Купить все для праздничного ужина.", Status.NEW); - Task task2 = new Task("Планирование тренировки", "Составить план тренировок на месяц.", Status.NEW); + Task task1 = new Task("Закупка продуктов", "Купить все для праздничного ужина.", Status.NEW, Duration.ofMinutes(60), LocalDateTime.now()); + Task task2 = new Task("Планирование тренировки", "Составить план тренировок на месяц.", Status.NEW, Duration.ofMinutes(60), LocalDateTime.now()); taskManager.addTask(task1); taskManager.addTask(task2); diff --git a/src/managers/FileBackedTaskManager.java b/src/managers/FileBackedTaskManager.java index a4a3b21..b01770e 100644 --- a/src/managers/FileBackedTaskManager.java +++ b/src/managers/FileBackedTaskManager.java @@ -10,40 +10,16 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Stream; public class FileBackedTaskManager extends InMemoryTaskManager { - public static void main(String[] args) { - File file = new File("tasks.csv"); - InMemoryHistoryManager inMemoryHistoryManager = new InMemoryHistoryManager(); - - FileBackedTaskManager manager = new FileBackedTaskManager(file, inMemoryHistoryManager); - - Task task = new Task("Task1", "Description task", Status.NEW); - Epic epic = new Epic("Epic1", "Description epic", Status.IN_PROGRESS); - Subtask subtask = new Subtask("Subtask", "Description subtask", Status.NEW, epic.getId()); - - manager.addTask(task); - manager.addEpic(epic); - manager.addSubtask(subtask); - - System.out.println("до сохранения:\n"); - System.out.println(manager.getAllTasks()); - System.out.println(manager.getAllEpic()); - System.out.println(manager.getAllSubtask()); - - FileBackedTaskManager loaderManager = FileBackedTaskManager.loadFromFile(file, inMemoryHistoryManager); - - System.out.println("после загрузки:\n"); - System.out.println(loaderManager.getAllTasks()); - System.out.println(loaderManager.getAllEpic()); - System.out.println(loaderManager.getAllSubtask()); - } - private final File file; - public FileBackedTaskManager(File file, InMemoryHistoryManager inMemoryHistoryManager) { - super(inMemoryHistoryManager); + public FileBackedTaskManager(File file, InMemoryHistoryManager historyManager) { + super(historyManager); this.file = file; } @@ -113,81 +89,111 @@ public void addSubtask(Subtask subtask) { save(); } - public static FileBackedTaskManager loadFromFile(File file, InMemoryHistoryManager inMemoryHistoryManager) { - FileBackedTaskManager manager = new FileBackedTaskManager(file, inMemoryHistoryManager); - try { - List lines = Files.readAllLines(file.toPath()); - - if (lines.size() < 2) { - return manager; - } + @Override + public void updateSubtask(Subtask subtask) { + super.updateSubtask(subtask); + save(); + } - for (int i = 1; i < lines.size(); i++) { - Task task = manager.fromString(lines.get(i)); - switch (task.getType()) { - case TASK: - manager.addTask(task); - break; - case SUBTASK: - manager.addSubtask((Subtask) task); - break; - case EPIC: - manager.addEpic((Epic) task); - break; - } + @Override + public List getPrioritizedTasks() { + return super.getPrioritizedTasks(); + } + + /** + * Загружает менеджер из CSV. Бросает IOException при проблемах с файлом. + */ + public static FileBackedTaskManager loadFromFile(File file, + InMemoryHistoryManager historyManager) throws IOException { + FileBackedTaskManager manager = new FileBackedTaskManager(file, historyManager); + List lines = Files.readAllLines(file.toPath()); + if (lines.size() < 2) { + return manager; + } + for (int i = 1; i < lines.size(); i++) { + Task task = manager.fromString(lines.get(i)); + switch (task.getType()) { + case TASK: + manager.addTask(task); + break; + case SUBTASK: + manager.addSubtask((Subtask) task); + break; + case EPIC: + manager.addEpic((Epic) task); + break; } - } catch (IOException e) { - throw new ManagerSaveException("При загрузке файла произошла ошибка: ", e); } return manager; } - protected void save() { - try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) { - bufferedWriter.write("id,type,name,status,description,epic\n"); - - for (Task task : getAllTasks()) { - bufferedWriter.write(toString(task) + "\n"); - } - - for (Subtask subtask : getAllSubtask()) { - bufferedWriter.write(toString(subtask) + "\n"); - } - - for (Epic epic : getAllEpic()) { - bufferedWriter.write(toString(epic) + "\n"); - } + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + writer.write("id,type,name,status,description,StartTime,duration,epic\n"); + Stream.concat( + Stream.concat( + getAllTasks().stream(), + getAllSubtask().stream() + ), + getAllEpic().stream() + ).forEach(task -> { + try { + writer.write(toString(task)); + writer.newLine(); + } catch (IOException e) { + throw new ManagerSaveException("Ошибка при записи задачи в файл", e); + } + }); } catch (IOException e) { - throw new ManagerSaveException("Ошибка в сохранении файла"); + throw new ManagerSaveException("Ошибка в сохранении файла", e); } } private String toString(Task task) { - String description = task.getDescription().replace(",", " "); //добавил в случае, если в описании будет запятая - return String.format("%d,%s,%s,%s,%s,%s", - task.getId(), task.getType(), task.getName(), task.getStatus(), description, - (task instanceof Subtask) ? ((Subtask) task).getEpicId() : "" + String description = task.getDescription().replace(",", " "); + String startTime = task.getStartTime() == null ? "null" : task.getStartTime().toString(); + String duration = task.getDuration() == null ? "0" : String.valueOf(task.getDuration().toMinutes()); + String epicId = task instanceof Subtask ? String.valueOf(((Subtask) task).getEpicId()) : ""; + return String.format("%d,%s,%s,%s,%s,%s,%s,%s", + task.getId(), task.getType(), task.getName(), task.getStatus(), + description, startTime, duration, epicId ); } private Task fromString(String line) { - String[] parts = line.split(",", 6); - + String[] parts = line.split(",", 8); + if (parts.length < 8) { + throw new IllegalArgumentException("Недостаточно данных для разбора строки: " + line); + } int id = Integer.parseInt(parts[0]); TypeOfTask type = TypeOfTask.valueOf(parts[1]); String name = parts[2]; Status status = Status.valueOf(parts[3]); String description = parts[4]; + String startTimeStr = parts[5]; + long minutes = Long.parseLong(parts[6]); + Duration duration = Duration.ofMinutes(minutes); + String epicIdStr = parts[7]; + + LocalDateTime startTime = startTimeStr.equals("null") ? null : LocalDateTime.parse(startTimeStr); switch (type) { case TASK: - return new Task(name, description, status); + Task task = new Task(name, description, status, duration, startTime); + task.setId(id); + return task; case SUBTASK: - int epicId = Integer.parseInt(parts[5]); - return new Subtask(name, description, status, epicId); + if (epicIdStr.isEmpty()) { + throw new IllegalArgumentException("ИД Эпика не может быть пустым"); + } + int epicId = Integer.parseInt(epicIdStr); + Subtask subtask = new Subtask(name, description, status, epicId, duration, startTime); + subtask.setId(id); + return subtask; case EPIC: - return new Epic(name, description, status); + Epic epic = new Epic(name, description, status); + epic.setId(id); + return epic; default: throw new IllegalArgumentException("Задача неизвестного типа: " + type); } diff --git a/src/managers/InMemoryHistoryManager.java b/src/managers/InMemoryHistoryManager.java index 88e5c64..0e3ac8d 100644 --- a/src/managers/InMemoryHistoryManager.java +++ b/src/managers/InMemoryHistoryManager.java @@ -28,6 +28,10 @@ private void addLast(Task task) { @Override public void add(Task task) { + if (task == null) { + return; + } + if (historyMap.containsKey(task.getId())) { remove(task.getId()); } diff --git a/src/managers/InMemoryTaskManager.java b/src/managers/InMemoryTaskManager.java index f804bb8..85680a2 100644 --- a/src/managers/InMemoryTaskManager.java +++ b/src/managers/InMemoryTaskManager.java @@ -5,17 +5,20 @@ import tasks.Subtask; import tasks.Task; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public class InMemoryTaskManager implements TaskManager { private int idCounter = 0; - private final HashMap tasks = new HashMap<>(); - private final HashMap epics = new HashMap<>(); - private final HashMap subtasks = new HashMap<>(); + private final Map tasks = new HashMap<>(); + private final Map epics = new HashMap<>(); + private final Map subtasks = new HashMap<>(); private final HistoryManager historyManager; + private final TreeSet prioritizedTasks = new TreeSet<>( + Comparator.comparing(Task::getStartTime, Comparator.nullsLast(Comparator.naturalOrder())) + ); + public InMemoryTaskManager(HistoryManager historyManager) { this.historyManager = historyManager; } @@ -25,25 +28,40 @@ public int createId() { return idCounter++; } + private boolean isOverlappingInternal(Task newTask) { + if (newTask.getStartTime() == null || newTask.getDuration() == null) { + return false; + } + return prioritizedTasks.stream() + .filter(t -> t.getStartTime() != null && t.getDuration() != null) + .anyMatch(existing -> existing.overlapsWith(newTask)); + } + @Override public void addTask(Task task) { + if (isOverlappingInternal(task)) { + throw new IllegalArgumentException("Задача пересекается по времени"); + } task.setId(createId()); tasks.put(task.getId(), task); + prioritizedTasks.add(task); } @Override public void updateTask(Task task) { - if (tasks.containsKey(task.getId())) { - tasks.put(task.getId(), task); - } else { - System.out.println("Error"); + if (isOverlappingInternal(task)) { + throw new IllegalArgumentException("Обновлённая задача пересекается по времени"); } + tasks.put(task.getId(), task); + prioritizedTasks.removeIf(t -> t.getId() == task.getId()); + prioritizedTasks.add(task); } @Override public Task getTask(int id) { - historyManager.add(tasks.get(id)); - return tasks.get(id); + Task t = tasks.get(id); + historyManager.add(t); + return t; } @Override @@ -53,24 +71,35 @@ public List getAllTasks() { @Override public void removeAllTasks() { + tasks.values().forEach(prioritizedTasks::remove); tasks.clear(); } @Override public void removeAllSubtasks() { + subtasks.values().forEach(prioritizedTasks::remove); subtasks.clear(); + epics.values().forEach(epic -> { + epic.getSubtasks().clear(); + updateStatusForEpic(epic); + epic.calculateTime(Collections.emptyList()); + }); } @Override public void removeAllEpics() { + epics.values().forEach(epic -> { + epic.getSubtasks().forEach(id -> prioritizedTasks.remove(subtasks.get(id))); + subtasks.keySet().removeAll(epic.getSubtasks()); + }); epics.clear(); - subtasks.clear(); } @Override public void removeTaskById(int id) { - historyManager.add(tasks.get(id)); - tasks.remove(id); + Task t = tasks.remove(id); + historyManager.add(t); + prioritizedTasks.remove(t); } @Override @@ -82,90 +111,68 @@ public void addEpic(Epic epic) { @Override public void removeEpicById(int id) { - Epic epic = epics.get(id); - + Epic epic = epics.remove(id); if (epic != null) { - for (int subtaskId : epic.getSubtasks()) { - subtasks.remove(subtaskId); - } - - epics.remove(id); - } else { - System.out.println("Error"); + epic.getSubtasks().forEach(subId -> { + Subtask st = subtasks.remove(subId); + prioritizedTasks.remove(st); + }); } } @Override public void removeSubtaskId(int id) { - Subtask subtask = subtasks.get(id); - - if (subtask != null) { - - Epic epic = epics.get(subtask.getEpicId()); - + Subtask sub = subtasks.remove(id); + if (sub != null) { + historyManager.add(sub); + prioritizedTasks.remove(sub); + Epic epic = epics.get(sub.getEpicId()); if (epic != null) { epic.getSubtasks().remove(Integer.valueOf(id)); updateStatusForEpic(epic); + epic.calculateTime(getEpicSubtasks(epic.getId())); } - - historyManager.add(subtask); - } else { - System.out.println("Error"); } } - @Override public void updateStatusForEpic(Epic epic) { - if (epic.getSubtasks().isEmpty()) { + List statuses = epic.getSubtasks().stream() + .map(subtasks::get) + .filter(Objects::nonNull) + .map(Subtask::getStatus) + .collect(Collectors.toList()); + if (statuses.isEmpty()) { epic.setStatus(Status.NEW); - return; - } - - boolean isAllNew = true; - boolean isAllDone = true; - - for (int id : epic.getSubtasks()) { - Subtask subtask = subtasks.get(id); - - if (subtask != null) { - if (subtask.getStatus() != Status.NEW) { - isAllNew = false; - } - if (subtask.getStatus() != Status.DONE) { - isAllDone = false; - } - } - } - - if (isAllNew) { + } else if (statuses.stream().allMatch(s -> s == Status.NEW)) { epic.setStatus(Status.NEW); - } else if (isAllDone) { + } else if (statuses.stream().allMatch(s -> s == Status.DONE)) { epic.setStatus(Status.DONE); } else { epic.setStatus(Status.IN_PROGRESS); } - } @Override public void addSubtask(Subtask subtask) { + if (!epics.containsKey(subtask.getEpicId())) { + throw new IllegalArgumentException("Добавление подзадачи с несуществующим ID эпика"); + } + + if (isOverlappingInternal(subtask)) { + throw new IllegalArgumentException("Подзадача пересекается по времени"); + } subtask.setId(createId()); subtasks.put(subtask.getId(), subtask); - - + prioritizedTasks.add(subtask); Epic epic = epics.get(subtask.getEpicId()); if (epic != null) { epic.getSubtasks().add(subtask.getId()); updateStatusForEpic(epic); - } else { - System.out.println("Ошибка: эпик не найден!"); + epic.calculateTime(getEpicSubtasks(epic.getId())); } - - historyManager.add(subtask); } - @Override public List getAllSubtask() { return new ArrayList<>(subtasks.values()); @@ -173,22 +180,23 @@ public List getAllSubtask() { @Override public Subtask getSubtaskById(int id) { - historyManager.add(subtasks.get(id)); - return subtasks.get(id); + Subtask s = subtasks.get(id); + historyManager.add(s); + return s; } @Override public void updateSubtask(Subtask subtask) { - if (subtasks.containsKey(subtask.getId())) { - subtasks.put(subtask.getId(), subtask); - - Epic epic = epics.get(subtask.getEpicId()); - - if (epic != null) { - updateStatusForEpic(epic); - } - } else { - System.out.println("Error"); + if (isOverlappingInternal(subtask)) { + throw new IllegalArgumentException("Обновлённая подзадача пересекается по времени"); + } + subtasks.put(subtask.getId(), subtask); + prioritizedTasks.removeIf(t -> t.getId() == subtask.getId()); + prioritizedTasks.add(subtask); + Epic epic = epics.get(subtask.getEpicId()); + if (epic != null) { + updateStatusForEpic(epic); + epic.calculateTime(getEpicSubtasks(epic.getId())); } } @@ -196,30 +204,22 @@ public void updateSubtask(Subtask subtask) { public void updateEpic(Epic epic) { if (epics.containsKey(epic.getId())) { epics.put(epic.getId(), epic); - } else { - System.out.println("Error"); } } - @Override public List getEpicSubtasks(int epicId) { Epic epic = epics.get(epicId); - List epicSubtasks = new ArrayList<>(); - if (epic != null) { - for (int subtaskId : epic.getSubtasks()) { - Subtask subtask = subtasks.get(subtaskId); - if (subtask != null) { - epicSubtasks.add(subtask); - } - } + if (epic == null) { + return Collections.emptyList(); } historyManager.add(epic); - - return epicSubtasks; + return epic.getSubtasks().stream() + .map(subtasks::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } - @Override public List getAllEpic() { return new ArrayList<>(epics.values()); @@ -229,4 +229,8 @@ public List getAllEpic() { public List getHistory() { return historyManager.getHistory(); } + + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); + } } diff --git a/src/tasks/Epic.java b/src/tasks/Epic.java index f66c8e8..abee1a0 100644 --- a/src/tasks/Epic.java +++ b/src/tasks/Epic.java @@ -2,16 +2,24 @@ import managers.TypeOfTask; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class Epic extends Task { private final List subtasks; + private Duration duration; + private LocalDateTime startTime; + private LocalDateTime endTime; public Epic(String name, String description, Status status) { - super(name, description, status); + super(name, description, status, Duration.ZERO, null); this.subtasks = new ArrayList<>(); this.type = TypeOfTask.EPIC; + this.startTime = null; + this.endTime = null; } public List getSubtasks() { @@ -19,10 +27,69 @@ public List getSubtasks() { } public void addSubtaskId(int id) { + System.out.println("Adding subtask ID: " + id + ", Epic ID: " + getId()); + if (id == getId()) { + throw new IllegalArgumentException("Эпик не может содержать себя в качестве подзадачи."); + } subtasks.add(id); } + public void calculateTime(List subtasks) { + if (subtasks == null || subtasks.isEmpty()) { + this.duration = Duration.ZERO; + this.startTime = null; + this.endTime = null; + return; + } + Duration totalSubtaskDuration = subtasks.stream() + .map(Subtask::getDuration) + .filter(Objects::nonNull) + .reduce(Duration.ZERO, Duration::plus); + + LocalDateTime minSubtaskStartTime = subtasks.stream() + .map(Subtask::getStartTime) + .filter(Objects::nonNull) + .min(LocalDateTime::compareTo) + .orElse(null); + + LocalDateTime maxSubtaskEndTime = subtasks.stream() + .filter(sub -> sub.getStartTime() != null && sub.getDuration() != null) + .map(sub -> sub.getStartTime().plus(sub.getDuration())) + .max(LocalDateTime::compareTo) + .orElse(null); + + setDuration(totalSubtaskDuration); + setStartTime(minSubtaskStartTime); + setEndTime(maxSubtaskEndTime); + } + + public void updateStatus(List subtasks) { + if (subtasks == null || subtasks.isEmpty()) { + setStatus(Status.NEW); + return; + } + + boolean allNew = subtasks.stream().allMatch(sub -> sub.getStatus() == Status.NEW); + boolean allDone = subtasks.stream().allMatch(sub -> sub.getStatus() == Status.DONE); + + if (allNew) { + setStatus(Status.NEW); + } else if (allDone) { + setStatus(Status.DONE); + } else { + setStatus(Status.IN_PROGRESS); + } + } + + @Override + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } @Override public String toString() { @@ -32,6 +99,9 @@ public String toString() { ", subTasks=" + subtasks + ", status=" + getStatus() + ", id=" + getId() + + ", duration=" + getDuration() + + ", startTime=" + getStartTime() + + ", endTime=" + getEndTime() + '}'; } -} +} \ No newline at end of file diff --git a/src/tasks/Subtask.java b/src/tasks/Subtask.java index b3f190e..c9b98e1 100644 --- a/src/tasks/Subtask.java +++ b/src/tasks/Subtask.java @@ -2,27 +2,49 @@ import managers.TypeOfTask; +import java.time.Duration; +import java.time.LocalDateTime; + public class Subtask extends Task { - private int epicId; + private final int epicId; public Subtask(String name, String description, Status status, int epicId) { - super(name, description, status); + super(name, description, status, Duration.ZERO, null); + this.epicId = epicId; + this.type = TypeOfTask.SUBTASK; + } + + public Subtask(String name, String description, Status status, int epicId, + Duration duration, LocalDateTime startTime) { + super(name, description, status, duration, startTime); this.epicId = epicId; this.type = TypeOfTask.SUBTASK; } + @Override + public void setId(int newId) { + // Не позволяем устанавливать ID подзадачи равным её epicId + if (newId == this.epicId) { + return; + } + super.setId(newId); + } + public int getEpicId() { return epicId; } @Override public String toString() { - return "tasks.Subtask{" + + return "Subtask{" + "name='" + getName() + '\'' + ", description='" + getDescription() + '\'' + ", status=" + getStatus() + ", id=" + getId() + + ", epicId=" + epicId + + ", startTime=" + getStartTime() + + ", duration=" + getDuration() + '}'; } -} +} \ No newline at end of file diff --git a/src/tasks/Task.java b/src/tasks/Task.java index 2b3ce13..c0da58a 100644 --- a/src/tasks/Task.java +++ b/src/tasks/Task.java @@ -2,6 +2,8 @@ import managers.TypeOfTask; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Objects; public class Task { @@ -10,12 +12,20 @@ public class Task { private Status status; private int id; protected TypeOfTask type; + private Duration duration; + private LocalDateTime startTime; - public Task(String name, String description, Status status) { + public Task(String name, String description, Status status, Duration duration, LocalDateTime startTime) { this.name = name; this.description = description; this.status = status; this.type = TypeOfTask.TASK; + this.duration = duration; + this.startTime = startTime; + } + + public Task(String name, String description, Status status) { + this(name, description, status, null, null); } public String getName() { @@ -47,13 +57,46 @@ public int getId() { } public void setId(int newId) { - id = newId; + this.id = newId; } public TypeOfTask getType() { return type; } + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + if (startTime == null || duration == null) { + return null; + } + return startTime.plus(duration); + } + + public boolean overlapsWith(Task otherTask) { + if (this.getStartTime() == null || otherTask.getStartTime() == null + || this.getDuration() == null || otherTask.getDuration() == null) { + return false; + } + LocalDateTime thisEnd = this.getEndTime(); + LocalDateTime otherEnd = otherTask.getEndTime(); + return this.getStartTime().isBefore(otherEnd) && thisEnd.isAfter(otherTask.getStartTime()); + } + @Override public String toString() { return "tasks.Task{" + @@ -61,6 +104,9 @@ public String toString() { ", description='" + description + '\'' + ", status=" + status + ", id=" + id + + ", duration=" + duration + + ", startTime=" + startTime + + ", endTime=" + getEndTime() + '}'; } @@ -69,14 +115,16 @@ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Task task = (Task) obj; - return Objects.equals(name, task.name) && + return id == task.id && + Objects.equals(name, task.name) && Objects.equals(description, task.description) && - Objects.equals(status, task.status); - + status == task.status && + Objects.equals(duration, task.duration) && + Objects.equals(startTime, task.startTime); } @Override public int hashCode() { - return Objects.hash(name, description, status, id); + return Objects.hash(name, description, status, id, duration, startTime); } } diff --git a/test/EpicTest.java b/test/EpicTest.java index 0fdb53f..6d7927e 100644 --- a/test/EpicTest.java +++ b/test/EpicTest.java @@ -1,17 +1,128 @@ +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Epic; import tasks.Status; +import tasks.Subtask; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; class EpicTest { - @Test - void epicCannotContainItselfAsSubtask() { - Epic epic = new Epic("Эпик", "Описание", Status.NEW); + private Epic epic; + private LocalDateTime base; + + @BeforeEach + void setUp() { + epic = new Epic("Эпик", "Описание", Status.NEW); epic.setId(1); + base = LocalDateTime.of(2025,4,1,10,0); + } + + @Test + void epicCannotContainItself() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + epic.addSubtaskId(epic.getId()) + ); + assertEquals("Эпик не может содержать себя в качестве подзадачи.", ex.getMessage()); + } + + @Test + void statusNewWhenNoSubtasks() { + epic.updateStatus(new ArrayList<>()); + assertEquals(Status.NEW, epic.getStatus()); + } + + @Test + void statusNewWhenAllNew() { + List subs = List.of( + new Subtask("Сабтаска1","", Status.NEW, 1, Duration.ofMinutes(30), base), + new Subtask("Сабтаска2","", Status.NEW, 1, Duration.ofMinutes(45), base.plusHours(1)) + ); + epic.updateStatus(subs); + assertEquals(Status.NEW, epic.getStatus()); + } + + @Test + void statusDoneWhenAllDone() { + List subs = List.of( + new Subtask("Сабтаска1","", Status.DONE, 1, Duration.ofMinutes(30), base), + new Subtask("Сабтаска2","", Status.DONE, 1, Duration.ofMinutes(45), base.plusHours(1)) + ); + epic.updateStatus(subs); + assertEquals(Status.DONE, epic.getStatus()); + } + + @Test + void statusInProgressWhenMixed() { + List subs = List.of( + new Subtask("Сабтаска1","", Status.NEW, 1, Duration.ofMinutes(30), base), + new Subtask("Сабтаска2","", Status.DONE, 1, Duration.ofMinutes(45), base.plusHours(1)) + ); + epic.updateStatus(subs); + assertEquals(Status.IN_PROGRESS, epic.getStatus()); + } - // Проверка через обычное условие - boolean isAdded = epic.getSubtasks().contains(epic.getId()); - assertFalse(isAdded, "Ошибка эпика."); + @Test + void statusInProgressWhenAnyInProgress() { + List subs = List.of( + new Subtask("Сабтаска1","", Status.IN_PROGRESS, 1, Duration.ofMinutes(30), base), + new Subtask("Сабтаска2","", Status.NEW, 1, Duration.ofMinutes(45), base.plusHours(1)) + ); + epic.updateStatus(subs); + assertEquals(Status.IN_PROGRESS, epic.getStatus()); + } + + @Test + void timeCalculationCorrect() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, Duration.ofMinutes(30), base); + Subtask b = new Subtask("Сабтаска2","", Status.DONE, 1, Duration.ofMinutes(45), base.plusHours(1)); + epic.calculateTime(List.of(a,b)); + + assertEquals(Duration.ofMinutes(75), epic.getDuration()); + assertEquals(base, epic.getStartTime()); + assertEquals(base.plusHours(1).plusMinutes(45), epic.getEndTime()); + } + + @Test + void timeEmptyWhenNoSubtasks() { + epic.calculateTime(new ArrayList<>()); + assertNull(epic.getStartTime()); + assertNull(epic.getEndTime()); + assertEquals(Duration.ZERO, epic.getDuration()); + } + + @Test + void timeIgnoresNulls() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1); + Subtask b = new Subtask("Сабтаска2","", Status.DONE, 1, Duration.ofMinutes(45), base); + epic.calculateTime(List.of(a,b)); + + assertEquals(Duration.ofMinutes(45), epic.getDuration()); + assertEquals(base, epic.getStartTime()); + assertEquals(base.plusMinutes(45), epic.getEndTime()); + } + + @Test + void addSubtaskIdUpdatesList() { + epic.addSubtaskId(5); + epic.addSubtaskId(7); + assertTrue(epic.getSubtasks().containsAll(List.of(5,7))); + } + + @Test + void toStringContainsAllFields() { + List subs = List.of( + new Subtask("Сабтаска1","", Status.NEW, 1, Duration.ofMinutes(30), base) + ); + epic.calculateTime(subs); + epic.updateStatus(subs); + String s = epic.toString(); + assertTrue(s.contains("E")); + assertTrue(s.contains("PT30M")); + assertTrue(s.contains(base.toString())); } } diff --git a/test/InMemoryTaskManagerTest.java b/test/InMemoryTaskManagerTest.java index 5816746..309aecc 100644 --- a/test/InMemoryTaskManagerTest.java +++ b/test/InMemoryTaskManagerTest.java @@ -1,44 +1,14 @@ import managers.InMemoryHistoryManager; import managers.InMemoryTaskManager; -import managers.TaskManager; -import org.junit.jupiter.api.Test; -import tasks.Status; -import tasks.Task; +import managers.TaskManagerTest; +import org.junit.jupiter.api.BeforeEach; -import static org.junit.jupiter.api.Assertions.*; +public class InMemoryTaskManagerTest extends TaskManagerTest { -class InMemoryTaskManagerTest { - @Test - void addAndGetTaskById() { - TaskManager taskManager = new InMemoryTaskManager(new InMemoryHistoryManager()); - Task task = new Task("Задача", "Описание", Status.NEW); - taskManager.addTask(task); - Task retrievedTask = taskManager.getTask(task.getId()); - assertEquals(task, retrievedTask, "Задача должна корректно находиться по ID."); + @BeforeEach + @Override + public void setUp() { + // История нужна только для TaskManager; она не влияет на логику add/update/remove/... + taskManager = new InMemoryTaskManager(new InMemoryHistoryManager()); } - - @Test - void tasksWithGeneratedAndSpecifiedIdsDoNotConflict() { - TaskManager taskManager = new InMemoryTaskManager(new InMemoryHistoryManager()); - Task task1 = new Task("Задача 1", "Описание 1", Status.NEW); - task1.setId(1); - taskManager.addTask(task1); - - Task task2 = new Task("Задача 2", "Описание 2", Status.NEW); - taskManager.addTask(task2); - - assertNotEquals(task1.getId(), task2.getId(), "ID не должны конфликтовать."); - } - - @Test - void taskFieldsRemainUnchangedAfterAddition() { - TaskManager taskManager = new InMemoryTaskManager(new InMemoryHistoryManager()); - Task task = new Task("Задача", "Описание", Status.NEW); - taskManager.addTask(task); - Task retrievedTask = taskManager.getTask(task.getId()); - - assertEquals("Задача", retrievedTask.getName(), "Имя задачи должно оставаться неизменным."); - assertEquals("Описание", retrievedTask.getDescription(), "Описание задачи должно оставаться неизменным."); - assertEquals(Status.NEW, retrievedTask.getStatus(), "Статус задачи должен оставаться неизменным."); - } -} +} \ No newline at end of file diff --git a/test/SubtaskTest.java b/test/SubtaskTest.java index 0002159..52107b6 100644 --- a/test/SubtaskTest.java +++ b/test/SubtaskTest.java @@ -1,17 +1,91 @@ +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Status; import tasks.Subtask; +import java.time.Duration; +import java.time.LocalDateTime; + import static org.junit.jupiter.api.Assertions.*; class SubtaskTest { + private LocalDateTime base; + private int epicId = 1; + + @BeforeEach + void setUp() { + base = LocalDateTime.of(2023,1,1,10,0); + } + + @Test + void cannotHaveItselfAsEpic() { + Subtask s = new Subtask("Сабтаска","", Status.NEW, epicId); + s.setId(epicId); + assertFalse(s.getEpicId() == s.getId(), + "Подзадача не может быть своим же эпиком."); + } + + @Test + void partialOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ofMinutes(60), base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), base.plusMinutes(30)); + assertTrue(a.overlapsWith(b)); + assertTrue(b.overlapsWith(a)); + } + + @Test + void fullOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ofMinutes(60), base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), base); + assertTrue(a.overlapsWith(b)); + } + + @Test + void containedOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ofMinutes(120), base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(30), base.plusMinutes(30)); + assertTrue(a.overlapsWith(b)); + } + + @Test + void noOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ofMinutes(60), base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), base.plusHours(2)); + assertFalse(a.overlapsWith(b)); + } + + @Test + void touchingNoOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ofMinutes(60), base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), base.plusMinutes(60)); + assertFalse(a.overlapsWith(b)); + } + @Test - void subtaskCannotHaveItselfAsEpic() { - Subtask subtask = new Subtask("Подзадача", "Описание", Status.NEW, 1); - subtask.setId(2); + void nullStartOrNullDurationNoOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, null, base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), null); + assertFalse(a.overlapsWith(b)); + assertFalse(b.overlapsWith(a)); + } - // Проверка на то, что эпик не равен id подзадачи - boolean isInvalidEpic = subtask.getEpicId() == subtask.getId(); - assertFalse(isInvalidEpic, "Подзадача не может быть своим же эпиком."); + @Test + void zeroDurationNoOverlap() { + Subtask a = new Subtask("Сабтаска1","", Status.NEW, 1, + Duration.ZERO, base); + Subtask b = new Subtask("Сабтаска2","", Status.NEW, 1, + Duration.ofMinutes(60), base); + assertFalse(a.overlapsWith(b)); } } diff --git a/test/TaskTest.java b/test/TaskTest.java index 4870aec..df587bb 100644 --- a/test/TaskTest.java +++ b/test/TaskTest.java @@ -1,26 +1,89 @@ +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Status; -import tasks.Subtask; import tasks.Task; +import java.time.Duration; +import java.time.LocalDateTime; + import static org.junit.jupiter.api.Assertions.*; class TaskTest { + private LocalDateTime base; + + @BeforeEach + void setUp() { + base = LocalDateTime.of(2025,4,1,10,0); + } + + @Test + void equalById() { + Task a = new Task("Таск","", Status.NEW); + Task b = new Task("Таск","", Status.NEW); + a.setId(5); + b.setId(5); + assertEquals(a, b); + } + + @Test + void partialOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ofMinutes(60), base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), base.plusMinutes(30)); + assertTrue(a.overlapsWith(b)); + } + + @Test + void fullOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ofMinutes(60), base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), base); + assertTrue(a.overlapsWith(b)); + } + + @Test + void containedOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ofMinutes(120), base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(30), base.plusMinutes(30)); + assertTrue(a.overlapsWith(b)); + } + + @Test + void noOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ofMinutes(60), base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), base.plusHours(2)); + assertFalse(a.overlapsWith(b)); + } + + @Test + void touchingNoOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ofMinutes(60), base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), base.plusMinutes(60)); + assertFalse(a.overlapsWith(b)); + } + @Test - void tasksAreEqualIfIdsAreEqual() { - Task task1 = new Task("tasks.Task 1", "", Status.NEW); - Task task2 = new Task("tasks.Task 1", "", Status.NEW); - task1.setId(1); - task2.setId(1); - assertEquals(task1, task2, "Tаски с одинаковым ID должны быть равны"); + void nullStartOrNullDurationNoOverlap() { + Task a = new Task("Таск1","", Status.NEW, null, base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), null); + assertFalse(a.overlapsWith(b)); } @Test - void subtasksAreEqualIfIdsAreEqual() { - Subtask subtask1 = new Subtask("tasks.Subtask 1", "", Status.NEW, 0); - Subtask subtask2 = new Subtask("tasks.Subtask 1", "", Status.NEW, 0); - subtask1.setId(1); - subtask2.setId(1); - assertEquals(subtask1, subtask2, "Субтаски с одинаковым ID должны быть равны"); + void zeroDurationNoOverlap() { + Task a = new Task("Таск1","", Status.NEW, + Duration.ZERO, base); + Task b = new Task("Таск2","", Status.NEW, + Duration.ofMinutes(60), base); + assertFalse(a.overlapsWith(b)); } } diff --git a/test/managers/FileBackedTaskManagerTest.java b/test/managers/FileBackedTaskManagerTest.java index 6872a37..a83c61e 100644 --- a/test/managers/FileBackedTaskManagerTest.java +++ b/test/managers/FileBackedTaskManagerTest.java @@ -1,71 +1,23 @@ package managers; -import org.junit.jupiter.api.Test; -import tasks.Status; -import tasks.Task; - +import org.junit.jupiter.api.BeforeEach; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class FileBackedTaskManagerTest { - @Test - public void shouldSaveAndLoadEmptyFile() throws IOException { - File file = File.createTempFile("tempFile", ".csv"); - file.deleteOnExit(); - - FileBackedTaskManager manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); - manager.save(); - - FileBackedTaskManager loaderManager = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - - assertTrue(loaderManager.getAllTasks().isEmpty()); - assertTrue(loaderManager.getAllSubtask().isEmpty()); - assertTrue(loaderManager.getAllEpic().isEmpty()); - } - - @Test - public void saveAndLoadMultipleTasks() throws IOException { - File file = File.createTempFile("tempFile", ".csv"); - file.deleteOnExit(); - - FileBackedTaskManager manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); - - Task task1 = new Task("Task1", " ", Status.NEW); - Task task2 = new Task("Task2", " ", Status.NEW); - manager.addTask(task1); - manager.addTask(task2); - - List lines = Files.readAllLines(file.toPath()); - - assertEquals(3, lines.size()); - assertTrue(lines.get(1).contains("Task1")); - assertTrue(lines.get(2).contains("Task2")); - } - - @Test - public void loadTasksSuccessfullyFromFile() throws IOException { - File file = File.createTempFile("tempFile", ".csv"); - file.deleteOnExit(); - - FileBackedTaskManager manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); - - Task task1 = new Task("Task1", " ", Status.NEW); - Task task2 = new Task("Task2", " ", Status.NEW); - - manager.addTask(task1); - manager.addTask(task2); - manager.save(); - - FileBackedTaskManager loaderManager = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - - assertEquals(2, loaderManager.getAllTasks().size()); - assertEquals("Task1", loaderManager.getAllTasks().get(0).getName()); - assertEquals("Task2", loaderManager.getAllTasks().get(1).getName()); +public class FileBackedTaskManagerTest + extends TaskManagerTest { + + private File file; + + @BeforeEach + @Override + public void setUp() { + try { + file = File.createTempFile("tempFile", ".csv"); + file.deleteOnExit(); + taskManager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); + } catch (IOException e) { + throw new RuntimeException(e); + } } - -} \ No newline at end of file +} diff --git a/test/managers/TaskManagerTest.java b/test/managers/TaskManagerTest.java new file mode 100644 index 0000000..94bd2af --- /dev/null +++ b/test/managers/TaskManagerTest.java @@ -0,0 +1,123 @@ +package managers; + +import tasks.Task; +import tasks.Epic; +import tasks.Subtask; +import tasks.Status; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public abstract class TaskManagerTest { + protected T taskManager; + protected LocalDateTime baseTime; + + @BeforeEach + public abstract void setUp(); + + @Test + public void testAddAndGetTask() { + Task task = new Task("Таск", "Описание", Status.NEW); + taskManager.addTask(task); + Task retrieved = taskManager.getTask(task.getId()); + assertEquals(task, retrieved, "Задача должна быть доступна по ID"); + } + + @Test + public void testUpdateTask() { + Task task = new Task("Таск", "Описание", Status.NEW); + taskManager.addTask(task); + task.setName("Обновленное название"); + taskManager.updateTask(task); + Task retrieved = taskManager.getTask(task.getId()); + assertEquals("Обновленное название", retrieved.getName(), "Имя задачи должно быть обновлено"); + } + + @Test + public void testDeleteTask() { + Task task = new Task("Таск", "Описание", Status.NEW); + taskManager.addTask(task); + taskManager.removeTaskById(task.getId()); + assertNull(taskManager.getTask(task.getId()), "Задача должна быть удалена"); + } + + @Test + public void testAddAndGetEpic() { + Epic epic = new Epic("Эпик", "Описание", Status.NEW); + taskManager.addEpic(epic); + Epic retrieved = getEpicById(epic.getId()); + assertEquals(epic, retrieved, "Эпик должен быть доступен по ID"); + } + + @Test + public void testAddSubtaskToEpic() { + Epic epic = new Epic("Эпик", "Описание", Status.NEW); + taskManager.addEpic(epic); + Subtask subtask = new Subtask("Сабтаска", "Описание", Status.NEW, epic.getId()); + taskManager.addSubtask(subtask); + List subtasks = taskManager.getEpicSubtasks(epic.getId()); + assertFalse(subtasks.isEmpty(), "Сабтаска должна быть добавлена к эпику"); + assertEquals(subtask, subtasks.get(0), "Сабтаска должна быть корректно связана с эпиком"); + } + + @Test + public void testSubtaskRequiresValidEpic() { + Subtask subtask = new Subtask("Сабтаска", "Описание", Status.NEW, 999); // Несуществующий ID у Эпика + assertThrows(IllegalArgumentException.class, () -> { + taskManager.addSubtask(subtask); + }, "Добавление подзадачи с несуществующим ID эпика"); + } + + @Test + public void testEpicStatusCalculation() { + Epic epic = new Epic("Эпик", "Описание", Status.NEW); + taskManager.addEpic(epic); + + Subtask subtask1 = new Subtask("Сабтаска1", "Описание", Status.NEW, epic.getId()); + Subtask subtask2 = new Subtask("Сабтаска2", "Описание", Status.NEW, epic.getId()); + taskManager.addSubtask(subtask1); + taskManager.addSubtask(subtask2); + Epic updatedEpic = getEpicById(epic.getId()); + assertEquals(Status.NEW, updatedEpic.getStatus(), "Статус должен быть NEW, если все подзадачи NEW"); + + subtask1.setStatus(Status.DONE); + subtask2.setStatus(Status.DONE); + taskManager.updateSubtask(subtask1); + taskManager.updateSubtask(subtask2); + updatedEpic = getEpicById(epic.getId()); + assertEquals(Status.DONE, updatedEpic.getStatus(), "Статус должен быть DONE, если все подзадачи DONE"); + + subtask1.setStatus(Status.NEW); + taskManager.updateSubtask(subtask1); + updatedEpic = getEpicById(epic.getId()); + assertEquals(Status.IN_PROGRESS, updatedEpic.getStatus(), "Статус должен быть IN_PROGRESS, если статусы NEW и DONE"); + + subtask2.setStatus(Status.IN_PROGRESS); + taskManager.updateSubtask(subtask2); + updatedEpic = getEpicById(epic.getId()); + assertEquals(Status.IN_PROGRESS, updatedEpic.getStatus(), "Статус олжен быть IN_PROGRESS, если есть подзадача IN_PROGRESS"); + } + + @Test + public void testTaskIntervalOverlap() { + baseTime = LocalDateTime.of(2025, 4, 1, 10, 0); + Task task1 = new Task("Таск1", "Описание", Status.NEW, Duration.ofMinutes(60), baseTime); + Task task2 = new Task("Таск2", "Описание", Status.NEW, Duration.ofMinutes(60), baseTime.plusMinutes(30)); + taskManager.addTask(task1); + assertThrows(IllegalArgumentException.class, () -> { + taskManager.addTask(task2); + }, "Добавление задачи с пересекающимся временным интервалом должно вызывать исключение"); + } + + private Epic getEpicById(int id) { + return taskManager.getAllEpic().stream() + .filter(epic -> epic.getId() == id) + .findFirst() + .orElse(null); + } +} \ No newline at end of file diff --git a/test_tasks.csv b/test_tasks.csv new file mode 100644 index 0000000..dbaf265 --- /dev/null +++ b/test_tasks.csv @@ -0,0 +1,2 @@ +id,type,name,status,description,StartTime,duration,epic +0,EPIC,Epic,NEW,Description,null,0,