From 3481f8689550437abd012d863c83fef0dd7039d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Wed, 23 Apr 2025 00:26:20 +0300 Subject: [PATCH 1/9] =?UTF-8?q?8=20=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java-kanban.iml | 16 ++ src/Main.java | 7 +- src/managers/FileBackedTaskManager.java | 187 +++++++++++-------- src/managers/InMemoryTaskManager.java | 88 +++++---- src/tasks/Epic.java | 74 +++++++- src/tasks/Subtask.java | 28 ++- src/tasks/Task.java | 56 +++++- test/EpicTest.java | 123 +++++++++++- test/SubtaskTest.java | 86 ++++++++- test/TaskTest.java | 89 +++++++-- test/managers/FileBackedTaskManagerTest.java | 98 ++++++---- test/managers/TaskManagerTest.java | 123 ++++++++++++ test_tasks.csv | 2 + 13 files changed, 782 insertions(+), 195 deletions(-) create mode 100644 test/managers/TaskManagerTest.java create mode 100644 test_tasks.csv 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..b011759 100644 --- a/src/managers/FileBackedTaskManager.java +++ b/src/managers/FileBackedTaskManager.java @@ -10,46 +10,31 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; -import java.util.List; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; +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; + private final TreeSet prioritizedTasks; public FileBackedTaskManager(File file, InMemoryHistoryManager inMemoryHistoryManager) { super(inMemoryHistoryManager); this.file = file; + this.prioritizedTasks = new TreeSet<>(Comparator.comparing( + Task::getStartTime, + Comparator.nullsLast(Comparator.naturalOrder()) + )); } @Override public void addTask(Task task) { + if (isOverlapping(task)) { + throw new IllegalArgumentException("Задача пересекается по времени"); + } super.addTask(task); + prioritizedTasks.add(task); save(); } @@ -79,6 +64,10 @@ public void removeAllEpics() { @Override public void removeTaskById(int id) { + Task task = getTask(id); + if (task != null) { + prioritizedTasks.remove(task); + } super.removeTaskById(id); save(); } @@ -109,85 +98,129 @@ public void updateStatusForEpic(Epic epic) { @Override public void addSubtask(Subtask subtask) { + if (isOverlapping(subtask)) { + throw new IllegalArgumentException("Подзадача пересекается по времени"); + } super.addSubtask(subtask); + prioritizedTasks.add(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; - } + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); + } - 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; - } + /** + * Загружает менеджер из CSV. Бросает IOException при проблемах с файлом. + */ + public static FileBackedTaskManager loadFromFile(File file, + InMemoryHistoryManager inMemoryHistoryManager) + throws IOException { + FileBackedTaskManager manager = new FileBackedTaskManager(file, inMemoryHistoryManager); + 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; } + @Override + public boolean isOverlapping(Task newTask) { + if (newTask.getStartTime() == null || newTask.getDuration() == null) { + return false; + } + return prioritizedTasks.stream() + .filter(task -> task.getStartTime() != null && task.getDuration() != null) + .anyMatch(existing -> existing.overlapsWith(newTask)); + } 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]); + TypeOfTask type; + try { + type = TypeOfTask.valueOf(parts[1]); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Неизвестный тип задачи: " + parts[1]); + } String name = parts[2]; Status status = Status.valueOf(parts[3]); String description = parts[4]; + String startTimeStr = parts[5]; + String durationStr = parts[6]; + String epicIdStr = parts[7]; + + LocalDateTime startTime = startTimeStr.equals("null") ? null : LocalDateTime.parse(startTimeStr); + long minutes = Long.parseLong(durationStr); + Duration duration = Duration.ofMinutes(minutes); 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/InMemoryTaskManager.java b/src/managers/InMemoryTaskManager.java index f804bb8..19f40c8 100644 --- a/src/managers/InMemoryTaskManager.java +++ b/src/managers/InMemoryTaskManager.java @@ -5,9 +5,9 @@ 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; +import java.util.stream.Stream; public class InMemoryTaskManager implements TaskManager { private int idCounter = 0; @@ -27,17 +27,19 @@ public int createId() { @Override public void addTask(Task task) { + if (isOverlapping(task)) { + throw new IllegalArgumentException("Задача пересекается по времени с существующей"); + } task.setId(createId()); tasks.put(task.getId(), task); } @Override public void updateTask(Task task) { - if (tasks.containsKey(task.getId())) { - tasks.put(task.getId(), task); - } else { - System.out.println("Error"); + if (isOverlapping(task)) { + throw new IllegalArgumentException("Обновлённая задача пересекается по времени"); } + tasks.put(task.getId(), task); } @Override @@ -122,21 +124,14 @@ public void updateStatusForEpic(Epic epic) { return; } - boolean isAllNew = true; - boolean isAllDone = true; + List subtaskStatuses = epic.getSubtasks().stream() + .map(subtasks::get) + .filter(Objects::nonNull) + .map(Subtask::getStatus) + .collect(Collectors.toList()); - 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; - } - } - } + boolean isAllNew = subtaskStatuses.stream().allMatch(status -> status == Status.NEW); + boolean isAllDone = subtaskStatuses.stream().allMatch(status -> status == Status.DONE); if (isAllNew) { epic.setStatus(Status.NEW); @@ -145,24 +140,25 @@ public void updateStatusForEpic(Epic epic) { } else { epic.setStatus(Status.IN_PROGRESS); } - } @Override public void addSubtask(Subtask subtask) { + if (subtask.getEpicId() == subtask.getId()) { + throw new IllegalArgumentException("Эпик не может быть подзадачей самого себя"); + } + if (isOverlapping(subtask)) { + throw new IllegalArgumentException("Подзадача пересекается по времени"); + } subtask.setId(createId()); subtasks.put(subtask.getId(), 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); } @@ -179,16 +175,14 @@ public Subtask getSubtaskById(int id) { @Override public void updateSubtask(Subtask subtask) { - if (subtasks.containsKey(subtask.getId())) { - subtasks.put(subtask.getId(), subtask); - - Epic epic = epics.get(subtask.getEpicId()); + if (isOverlapping(subtask)) { + throw new IllegalArgumentException("Обновлённая подзадача пересекается по времени"); + } + subtasks.put(subtask.getId(), subtask); - if (epic != null) { - updateStatusForEpic(epic); - } - } else { - System.out.println("Error"); + Epic epic = epics.get(subtask.getEpicId()); + if (epic != null) { + updateStatusForEpic(epic); } } @@ -205,18 +199,16 @@ public void updateEpic(Epic epic) { @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 new ArrayList<>(); } + historyManager.add(epic); - return epicSubtasks; + return epic.getSubtasks().stream() + .map(subtasks::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } @@ -229,4 +221,10 @@ public List getAllEpic() { public List getHistory() { return historyManager.getHistory(); } + + public boolean isOverlapping(Task newTask) { + return Stream.concat(tasks.values().stream(), subtasks.values().stream()) + .filter(task -> task.getStartTime() != null && task.getDuration() != null) + .anyMatch(existingTask -> existingTask.overlapsWith(newTask)); + } } 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..09245dd 100644 --- a/src/tasks/Subtask.java +++ b/src/tasks/Subtask.java @@ -2,27 +2,51 @@ import managers.TypeOfTask; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Objects; + public class Subtask extends Task { private 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; + } + + // Исправленный порядок параметров: сначала duration, потом startTime + 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() + '}'; } } diff --git a/src/tasks/Task.java b/src/tasks/Task.java index 2b3ce13..70791db 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,22 @@ 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; + } + + // Делегируем в основной, чтобы type всегда = TASK + public Task(String name, String description, Status status) { + this(name, description, status, null, null); } public String getName() { @@ -54,6 +66,36 @@ 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() { + return startTime.plus(duration); + } + + public boolean overlapsWith(Task otherTask) { + if (this.startTime == null || otherTask.startTime == null || this.duration == null || otherTask.duration == null) { + return false; + } + LocalDateTime thisEnd = this.startTime.plus(this.duration); + LocalDateTime otherEnd = otherTask.startTime.plus(otherTask.duration); + return this.startTime.isBefore(otherEnd) && thisEnd.isAfter(otherTask.startTime); + } + + @Override public String toString() { return "tasks.Task{" + @@ -61,6 +103,9 @@ public String toString() { ", description='" + description + '\'' + ", status=" + status + ", id=" + id + + ", duration=" + duration + + ", startTime=" + startTime + + ", endTime=" + getEndTime() + '}'; } @@ -69,14 +114,17 @@ 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 && // Сравнение по id + Objects.equals(name, task.name) && Objects.equals(description, task.description) && - Objects.equals(status, task.status); + Objects.equals(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/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..cafc7f0 100644 --- a/test/managers/FileBackedTaskManagerTest.java +++ b/test/managers/FileBackedTaskManagerTest.java @@ -1,5 +1,6 @@ package managers; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tasks.Status; import tasks.Task; @@ -7,65 +8,86 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.time.Duration; +import java.time.LocalDateTime; 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"); + private File file; + private FileBackedTaskManager manager; + + @BeforeEach + void setUp() throws IOException { + file = File.createTempFile("tempFile", ".csv"); file.deleteOnExit(); + manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); + } - FileBackedTaskManager manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); + @Test + void shouldSaveAndLoadEmptyFile() throws IOException { manager.save(); + FileBackedTaskManager loaded = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - FileBackedTaskManager loaderManager = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - - assertTrue(loaderManager.getAllTasks().isEmpty()); - assertTrue(loaderManager.getAllSubtask().isEmpty()); - assertTrue(loaderManager.getAllEpic().isEmpty()); + assertTrue(loaded.getAllTasks().isEmpty(), "tasks должен быть пустым"); + assertTrue(loaded.getAllSubtask().isEmpty(), "subtasks должен быть пустым"); + assertTrue(loaded.getAllEpic().isEmpty(), "epics должен быть пустым"); } @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); + void saveAndLoadMultipleTasks() throws IOException { + Task t1 = new Task("Task1", " ", Status.NEW); + Task t2 = new Task("Task2", " ", Status.NEW); + manager.addTask(t1); + manager.addTask(t2); List lines = Files.readAllLines(file.toPath()); - - assertEquals(3, lines.size()); + // шапка + 2 задачи + assertEquals(3, lines.size(), "В файле должно быть 3 строки"); 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); + void loadTasksSuccessfullyFromFile() throws IOException { + Task t1 = new Task("Таск1", " ", Status.NEW); + Task t2 = new Task("Таск2", " ", Status.NEW); + manager.addTask(t1); + manager.addTask(t2); manager.save(); - FileBackedTaskManager loaderManager = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); + FileBackedTaskManager loaded = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); + List loadedTasks = loaded.getAllTasks(); - assertEquals(2, loaderManager.getAllTasks().size()); - assertEquals("Task1", loaderManager.getAllTasks().get(0).getName()); - assertEquals("Task2", loaderManager.getAllTasks().get(1).getName()); + assertEquals(2, loadedTasks.size(), "Должны загрузиться 2 задачи"); + assertEquals("Таск1", loadedTasks.get(0).getName()); + assertEquals("Таск2", loadedTasks.get(1).getName()); } -} \ No newline at end of file + @Test + void testGetPrioritizedTasks() { + Task t1 = new Task("Таск1", "Описание", Status.NEW, + Duration.ofMinutes(60), LocalDateTime.of(2025,4,1,10,0)); + Task t2 = new Task("Таск2", "Описание", Status.NEW, + Duration.ofMinutes(30), LocalDateTime.of(2025,4,1,9,0)); + Task t3 = new Task("Таск3", "Описание", Status.NEW, + Duration.ofMinutes(45), LocalDateTime.of(2025,4,1,11,0)); + + manager.addTask(t1); + manager.addTask(t2); + manager.addTask(t3); + + List ordered = manager.getPrioritizedTasks(); + assertEquals(List.of(t2, t1, t3), ordered, + "Приоритизация по startTime должна работать"); + } + + @Test + void loadFromNonexistentFileShouldThrow() { + File bad = new File("i_dont_exist.csv"); + assertThrows(IOException.class, () -> { + FileBackedTaskManager.loadFromFile(bad, new InMemoryHistoryManager()); + }, "Загрузка из несуществующего файла должна выкидывать IOException"); + } +} diff --git a/test/managers/TaskManagerTest.java b/test/managers/TaskManagerTest.java new file mode 100644 index 0000000..e062bfa --- /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); + } +} 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, From f6a3e0c4217a36d7d396a667d433dad831777429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Wed, 23 Apr 2025 13:30:16 +0300 Subject: [PATCH 2/9] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B9=20=D0=B8?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D1=80=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tasks/Subtask.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tasks/Subtask.java b/src/tasks/Subtask.java index 09245dd..c9b98e1 100644 --- a/src/tasks/Subtask.java +++ b/src/tasks/Subtask.java @@ -4,11 +4,10 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.util.Objects; 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, Duration.ZERO, null); @@ -16,7 +15,6 @@ public Subtask(String name, String description, Status status, int epicId) { this.type = TypeOfTask.SUBTASK; } - // Исправленный порядок параметров: сначала duration, потом startTime public Subtask(String name, String description, Status status, int epicId, Duration duration, LocalDateTime startTime) { super(name, description, status, duration, startTime); @@ -49,4 +47,4 @@ public String toString() { ", duration=" + getDuration() + '}'; } -} +} \ No newline at end of file From cff174dfab976aba7fb16face26172c9d97c3b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 22:55:17 +0300 Subject: [PATCH 3/9] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/managers/FileBackedTaskManager.java | 59 +++++++------------------ 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/src/managers/FileBackedTaskManager.java b/src/managers/FileBackedTaskManager.java index b011759..b01770e 100644 --- a/src/managers/FileBackedTaskManager.java +++ b/src/managers/FileBackedTaskManager.java @@ -12,29 +12,20 @@ import java.nio.file.Files; import java.time.Duration; import java.time.LocalDateTime; -import java.util.*; +import java.util.List; import java.util.stream.Stream; public class FileBackedTaskManager extends InMemoryTaskManager { private final File file; - private final TreeSet prioritizedTasks; - public FileBackedTaskManager(File file, InMemoryHistoryManager inMemoryHistoryManager) { - super(inMemoryHistoryManager); + public FileBackedTaskManager(File file, InMemoryHistoryManager historyManager) { + super(historyManager); this.file = file; - this.prioritizedTasks = new TreeSet<>(Comparator.comparing( - Task::getStartTime, - Comparator.nullsLast(Comparator.naturalOrder()) - )); } @Override public void addTask(Task task) { - if (isOverlapping(task)) { - throw new IllegalArgumentException("Задача пересекается по времени"); - } super.addTask(task); - prioritizedTasks.add(task); save(); } @@ -64,10 +55,6 @@ public void removeAllEpics() { @Override public void removeTaskById(int id) { - Task task = getTask(id); - if (task != null) { - prioritizedTasks.remove(task); - } super.removeTaskById(id); save(); } @@ -98,25 +85,27 @@ public void updateStatusForEpic(Epic epic) { @Override public void addSubtask(Subtask subtask) { - if (isOverlapping(subtask)) { - throw new IllegalArgumentException("Подзадача пересекается по времени"); - } super.addSubtask(subtask); - prioritizedTasks.add(subtask); save(); } + @Override + public void updateSubtask(Subtask subtask) { + super.updateSubtask(subtask); + save(); + } + + @Override public List getPrioritizedTasks() { - return new ArrayList<>(prioritizedTasks); + return super.getPrioritizedTasks(); } /** * Загружает менеджер из CSV. Бросает IOException при проблемах с файлом. */ public static FileBackedTaskManager loadFromFile(File file, - InMemoryHistoryManager inMemoryHistoryManager) - throws IOException { - FileBackedTaskManager manager = new FileBackedTaskManager(file, inMemoryHistoryManager); + InMemoryHistoryManager historyManager) throws IOException { + FileBackedTaskManager manager = new FileBackedTaskManager(file, historyManager); List lines = Files.readAllLines(file.toPath()); if (lines.size() < 2) { return manager; @@ -138,16 +127,6 @@ public static FileBackedTaskManager loadFromFile(File file, return manager; } - @Override - public boolean isOverlapping(Task newTask) { - if (newTask.getStartTime() == null || newTask.getDuration() == null) { - return false; - } - return prioritizedTasks.stream() - .filter(task -> task.getStartTime() != null && task.getDuration() != null) - .anyMatch(existing -> existing.overlapsWith(newTask)); - } - protected void save() { try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { writer.write("id,type,name,status,description,StartTime,duration,epic\n"); @@ -187,22 +166,16 @@ private Task fromString(String line) { throw new IllegalArgumentException("Недостаточно данных для разбора строки: " + line); } int id = Integer.parseInt(parts[0]); - TypeOfTask type; - try { - type = TypeOfTask.valueOf(parts[1]); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Неизвестный тип задачи: " + parts[1]); - } + TypeOfTask type = TypeOfTask.valueOf(parts[1]); String name = parts[2]; Status status = Status.valueOf(parts[3]); String description = parts[4]; String startTimeStr = parts[5]; - String durationStr = parts[6]; + long minutes = Long.parseLong(parts[6]); + Duration duration = Duration.ofMinutes(minutes); String epicIdStr = parts[7]; LocalDateTime startTime = startTimeStr.equals("null") ? null : LocalDateTime.parse(startTimeStr); - long minutes = Long.parseLong(durationStr); - Duration duration = Duration.ofMinutes(minutes); switch (type) { case TASK: From 325bdc32a37e8bac078ef011d8b342b3ba06d1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:09:29 +0300 Subject: [PATCH 4/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20getEndTime()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tasks/Task.java | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tasks/Task.java b/src/tasks/Task.java index 70791db..c0da58a 100644 --- a/src/tasks/Task.java +++ b/src/tasks/Task.java @@ -15,7 +15,6 @@ public class Task { private Duration duration; private LocalDateTime startTime; - // Основной конструктор public Task(String name, String description, Status status, Duration duration, LocalDateTime startTime) { this.name = name; this.description = description; @@ -25,7 +24,6 @@ public Task(String name, String description, Status status, Duration duration, L this.startTime = startTime; } - // Делегируем в основной, чтобы type всегда = TASK public Task(String name, String description, Status status) { this(name, description, status, null, null); } @@ -59,7 +57,7 @@ public int getId() { } public void setId(int newId) { - id = newId; + this.id = newId; } public TypeOfTask getType() { @@ -83,19 +81,22 @@ public void setStartTime(LocalDateTime startTime) { } public LocalDateTime getEndTime() { + if (startTime == null || duration == null) { + return null; + } return startTime.plus(duration); } public boolean overlapsWith(Task otherTask) { - if (this.startTime == null || otherTask.startTime == null || this.duration == null || otherTask.duration == null) { + if (this.getStartTime() == null || otherTask.getStartTime() == null + || this.getDuration() == null || otherTask.getDuration() == null) { return false; } - LocalDateTime thisEnd = this.startTime.plus(this.duration); - LocalDateTime otherEnd = otherTask.startTime.plus(otherTask.duration); - return this.startTime.isBefore(otherEnd) && thisEnd.isAfter(otherTask.startTime); + LocalDateTime thisEnd = this.getEndTime(); + LocalDateTime otherEnd = otherTask.getEndTime(); + return this.getStartTime().isBefore(otherEnd) && thisEnd.isAfter(otherTask.getStartTime()); } - @Override public String toString() { return "tasks.Task{" + @@ -114,13 +115,12 @@ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Task task = (Task) obj; - return id == task.id && // Сравнение по id + 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 From b42ed6825a1726be4ac24e7dc359284995142ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:12:53 +0300 Subject: [PATCH 5/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B0=D1=81=D0=BB=D0=B5=D0=B4=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=20TaskManagerTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/InMemoryTaskManagerTest.java | 48 ++++++------------------------- 1 file changed, 9 insertions(+), 39 deletions(-) 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 From ac8af5aa668b915558b1017c39f496554de93c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:14:52 +0300 Subject: [PATCH 6/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B0=D1=81=D0=BB=D0=B5=D0=B4=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=20TaskManagerTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/managers/FileBackedTaskManagerTest.java | 92 +++----------------- 1 file changed, 11 insertions(+), 81 deletions(-) diff --git a/test/managers/FileBackedTaskManagerTest.java b/test/managers/FileBackedTaskManagerTest.java index cafc7f0..a83c61e 100644 --- a/test/managers/FileBackedTaskManagerTest.java +++ b/test/managers/FileBackedTaskManagerTest.java @@ -1,93 +1,23 @@ package managers; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tasks.Status; -import tasks.Task; - import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +public class FileBackedTaskManagerTest + extends TaskManagerTest { -class FileBackedTaskManagerTest { private File file; - private FileBackedTaskManager manager; @BeforeEach - void setUp() throws IOException { - file = File.createTempFile("tempFile", ".csv"); - file.deleteOnExit(); - manager = new FileBackedTaskManager(file, new InMemoryHistoryManager()); - } - - @Test - void shouldSaveAndLoadEmptyFile() throws IOException { - manager.save(); - FileBackedTaskManager loaded = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - - assertTrue(loaded.getAllTasks().isEmpty(), "tasks должен быть пустым"); - assertTrue(loaded.getAllSubtask().isEmpty(), "subtasks должен быть пустым"); - assertTrue(loaded.getAllEpic().isEmpty(), "epics должен быть пустым"); - } - - @Test - void saveAndLoadMultipleTasks() throws IOException { - Task t1 = new Task("Task1", " ", Status.NEW); - Task t2 = new Task("Task2", " ", Status.NEW); - manager.addTask(t1); - manager.addTask(t2); - - List lines = Files.readAllLines(file.toPath()); - // шапка + 2 задачи - assertEquals(3, lines.size(), "В файле должно быть 3 строки"); - assertTrue(lines.get(1).contains("Task1")); - assertTrue(lines.get(2).contains("Task2")); - } - - @Test - void loadTasksSuccessfullyFromFile() throws IOException { - Task t1 = new Task("Таск1", " ", Status.NEW); - Task t2 = new Task("Таск2", " ", Status.NEW); - manager.addTask(t1); - manager.addTask(t2); - manager.save(); - - FileBackedTaskManager loaded = FileBackedTaskManager.loadFromFile(file, new InMemoryHistoryManager()); - List loadedTasks = loaded.getAllTasks(); - - assertEquals(2, loadedTasks.size(), "Должны загрузиться 2 задачи"); - assertEquals("Таск1", loadedTasks.get(0).getName()); - assertEquals("Таск2", loadedTasks.get(1).getName()); - } - - @Test - void testGetPrioritizedTasks() { - Task t1 = new Task("Таск1", "Описание", Status.NEW, - Duration.ofMinutes(60), LocalDateTime.of(2025,4,1,10,0)); - Task t2 = new Task("Таск2", "Описание", Status.NEW, - Duration.ofMinutes(30), LocalDateTime.of(2025,4,1,9,0)); - Task t3 = new Task("Таск3", "Описание", Status.NEW, - Duration.ofMinutes(45), LocalDateTime.of(2025,4,1,11,0)); - - manager.addTask(t1); - manager.addTask(t2); - manager.addTask(t3); - - List ordered = manager.getPrioritizedTasks(); - assertEquals(List.of(t2, t1, t3), ordered, - "Приоритизация по startTime должна работать"); - } - - @Test - void loadFromNonexistentFileShouldThrow() { - File bad = new File("i_dont_exist.csv"); - assertThrows(IOException.class, () -> { - FileBackedTaskManager.loadFromFile(bad, new InMemoryHistoryManager()); - }, "Загрузка из несуществующего файла должна выкидывать IOException"); + @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); + } } } From 8d29e9220003a0e682244d1a27872070b8ae6659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:17:09 +0300 Subject: [PATCH 7/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/managers/TaskManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/managers/TaskManagerTest.java b/test/managers/TaskManagerTest.java index e062bfa..94bd2af 100644 --- a/test/managers/TaskManagerTest.java +++ b/test/managers/TaskManagerTest.java @@ -120,4 +120,4 @@ private Epic getEpicById(int id) { .findFirst() .orElse(null); } -} +} \ No newline at end of file From 58a5a07cdda837ec34b89a623740da6c87491a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:19:19 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20?= =?UTF-8?q?=D0=B2=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B5=20add(Task=20task?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/managers/InMemoryHistoryManager.java | 4 ++++ 1 file changed, 4 insertions(+) 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()); } From 5d6a25572fe3354abf463505f1b9e565e1bccf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D1=83=D1=87=D0=BA=D0=BE=D0=B2?= Date: Thu, 24 Apr 2025 23:20:41 +0300 Subject: [PATCH 9/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D0=B5=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/managers/InMemoryTaskManager.java | 134 ++++++++++++++------------ 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/src/managers/InMemoryTaskManager.java b/src/managers/InMemoryTaskManager.java index 19f40c8..85680a2 100644 --- a/src/managers/InMemoryTaskManager.java +++ b/src/managers/InMemoryTaskManager.java @@ -7,15 +7,18 @@ import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; 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,27 +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 (isOverlapping(task)) { - throw new IllegalArgumentException("Задача пересекается по времени с существующей"); + if (isOverlappingInternal(task)) { + throw new IllegalArgumentException("Задача пересекается по времени"); } task.setId(createId()); tasks.put(task.getId(), task); + prioritizedTasks.add(task); } @Override public void updateTask(Task task) { - if (isOverlapping(task)) { + 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 @@ -55,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 @@ -84,58 +111,42 @@ 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()) { - epic.setStatus(Status.NEW); - return; - } - - List subtaskStatuses = epic.getSubtasks().stream() + List statuses = epic.getSubtasks().stream() .map(subtasks::get) .filter(Objects::nonNull) .map(Subtask::getStatus) .collect(Collectors.toList()); - - boolean isAllNew = subtaskStatuses.stream().allMatch(status -> status == Status.NEW); - boolean isAllDone = subtaskStatuses.stream().allMatch(status -> status == Status.DONE); - - if (isAllNew) { + if (statuses.isEmpty()) { + epic.setStatus(Status.NEW); + } 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); @@ -144,15 +155,16 @@ public void updateStatusForEpic(Epic epic) { @Override public void addSubtask(Subtask subtask) { - if (subtask.getEpicId() == subtask.getId()) { - throw new IllegalArgumentException("Эпик не может быть подзадачей самого себя"); + if (!epics.containsKey(subtask.getEpicId())) { + throw new IllegalArgumentException("Добавление подзадачи с несуществующим ID эпика"); } - if (isOverlapping(subtask)) { + + 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()); @@ -161,7 +173,6 @@ public void addSubtask(Subtask subtask) { } } - @Override public List getAllSubtask() { return new ArrayList<>(subtasks.values()); @@ -169,20 +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 (isOverlapping(subtask)) { + 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())); } } @@ -190,28 +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); if (epic == null) { - return new ArrayList<>(); + return Collections.emptyList(); } - historyManager.add(epic); - return epic.getSubtasks().stream() .map(subtasks::get) .filter(Objects::nonNull) .collect(Collectors.toList()); } - @Override public List getAllEpic() { return new ArrayList<>(epics.values()); @@ -222,9 +230,7 @@ public List getHistory() { return historyManager.getHistory(); } - public boolean isOverlapping(Task newTask) { - return Stream.concat(tasks.values().stream(), subtasks.values().stream()) - .filter(task -> task.getStartTime() != null && task.getDuration() != null) - .anyMatch(existingTask -> existingTask.overlapsWith(newTask)); + public List getPrioritizedTasks() { + return new ArrayList<>(prioritizedTasks); } }