diff --git a/.gitignore b/.gitignore
index f68d109..28cb4f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,29 +1,43 @@
### IntelliJ IDEA ###
-out/
-!**/src/main/**/out/
-!**/src/test/**/out/
+# Игнорируем всё содержимое .idea — это папка настроек среды разработки
+# Оставляем только нужные файлы, чтобы IDE могла корректно открыть проект
+.idea/
+!.idea/misc.xml # Уровень JDK, базовые настройки проекта
+!.idea/modules.xml # Структура модулей (если нет Maven/Gradle)
+!.idea/libraries/ # Подключённые библиотеки, например JUnit
+!.idea/vcs.xml # Настройки системы контроля версий (Git)
-### Eclipse ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-bin/
-!**/src/main/**/bin/
-!**/src/test/**/bin/
+# Игнорируем файлы модулей IntelliJ (.iml) — не нужны в репозитории
+*.iml
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
+### Build output ###
+# Исключаем папки, которые содержат скомпилированные классы и артефакты
+out/ # Папка вывода сборки IntelliJ
+bin/ # Папка вывода Eclipse/ручной сборки
+target/ # Папка сборки Maven (если появится)
+
+### OS ###
+# Системные файлы macOS, не должны попадать в репозиторий
+.DS_Store
### VS Code ###
+# Конфигурации Visual Studio Code (если кто-то откроет проект там)
.vscode/
-### Mac OS ###
-.DS_Store
\ No newline at end of file
+### Eclipse ###
+# Игнорируем все файлы и папки, связанные с Eclipse IDE
+.apt_generated # Автоматически сгенерированные исходники
+.classpath # Файл конфигурации путей классов
+.factorypath # Конфигурация аннотаций
+.project # Основной файл проекта Eclipse
+.settings # Папка с настройками проекта
+.springBeans # Конфиги Spring Beans (если используется)
+.sts4-cache # Кэш Spring Tool Suite (STS)
+
+### NetBeans ###
+# Всё, что создаёт NetBeans IDE
+nbproject/private/ # Личные настройки проекта
+nbbuild/ # Папка сборки NetBeans
+dist/ # Артефакты сборки (JAR и т.д.)
+nbdist/ # Расширенная папка вывода
+.nb-gradle/ # Кэш Gradle от NetBeans
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 7bc07ec..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Environment-dependent path to Maven home directory
-/mavenHomeManager.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/java-sprint4-hw.iml b/java-sprint4-hw.iml
deleted file mode 100644
index 547dd47..0000000
--- a/java-sprint4-hw.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/manager/HistoryManager.java b/src/manager/HistoryManager.java
index 084b9c5..c163693 100644
--- a/src/manager/HistoryManager.java
+++ b/src/manager/HistoryManager.java
@@ -1,9 +1,10 @@
package manager;
-//Интерфейс менеджера истории просмотров задач.
+
import model.Task;
import java.util.List;
public interface HistoryManager {
- void add(Task task);
- List getHistory();
-}
\ No newline at end of file
+ void add(Task task); // записать просмотр
+ void remove(int id); // удалить по id (нужно при удалении задач)
+ List getHistory();// вернуть историю в порядке просмотра
+}
diff --git a/src/manager/InMemoryHistoryManager.java b/src/manager/InMemoryHistoryManager.java
index d45efee..7ce16be 100644
--- a/src/manager/InMemoryHistoryManager.java
+++ b/src/manager/InMemoryHistoryManager.java
@@ -2,22 +2,99 @@
import model.Task;
import java.util.*;
-//Хранит максимум 10 последних задач. При превышении лимита
- // самая старая задача удаляется.
+
+/** HistoryManager на базе двусвязного списка + HashMap */
public class InMemoryHistoryManager implements HistoryManager {
- private static final int MAX_HISTORY = 10;
- private final Deque history = new ArrayDeque<>();
+
+ /* ───── узел списка ───── */
+ private static class Node {
+ Task data;
+ Node prev;
+ Node next;
+
+ Node(Node prev, Task data, Node next) {
+ this.prev = prev;
+ this.data = data;
+ this.next = next;
+ }
+ }
+
+ /* ───── поля ───── */
+ private final Map index = new HashMap<>();
+ private Node head;
+ private Node tail;
+
+ /* ───── вспомогательные ───── */
+
+ /** добавляем просмотр в хвост */
+ private void linkLast(Task task) {
+ Node oldTail = tail;
+ Node newNode = new Node(oldTail, task, null); // n → newNode
+ tail = newNode;
+
+ if (oldTail == null) {
+ head = newNode; // фигурные скобки
+ } else {
+ oldTail.next = newNode; // фигурные скобки
+ }
+ }
+
+ /** удаляем произвольный узел */
+ private void removeNode(Node target) {
+ if (target == null) {
+ return;
+ }
+
+ Node prev = target.prev;
+ Node next = target.next;
+
+ if (prev != null) {
+ prev.next = next;
+ } else {
+ head = next; // фигурные скобки
+ }
+
+ if (next != null) {
+ next.prev = prev;
+ } else {
+ tail = prev; // фигурные скобки
+ }
+ }
+
+ /** выгружаем историю списком */
+ private List getTasks() {
+ List list = new ArrayList<>();
+ for (Node current = head; current != null; current = current.next) {
+ list.add(current.data);
+ }
+ return list;
+ }
+
+ /* ───── HistoryManager API ───── */
@Override
public void add(Task task) {
- history.addLast(task);
- if (history.size() > MAX_HISTORY) {
- history.pollFirst();
+ if (task == null) {
+ return; // фигурные скобки
}
+
+ /* если id уже есть — убираем старый узел */
+ Node duplicate = index.remove(task.getId());
+ removeNode(duplicate);
+
+ /* вносим новый просмотр */
+ linkLast(task);
+ index.put(task.getId(), tail);
}
-//Возвращает список просмотренных задач (в порядке просмотра).
+
+ @Override
+ public void remove(int id) {
+ Node node = index.remove(id);
+ removeNode(node);
+ }
+
@Override
public List getHistory() {
- return new ArrayList<>(history);
+ return getTasks();
}
-}
\ No newline at end of file
+}
diff --git a/src/manager/InMemoryTaskManager.java b/src/manager/InMemoryTaskManager.java
index b299777..60019c2 100644
--- a/src/manager/InMemoryTaskManager.java
+++ b/src/manager/InMemoryTaskManager.java
@@ -3,21 +3,22 @@
import model.*;
import java.util.*;
-// InMemoryTaskManager — реализация интерфейса TaskManager,
-// хранящая задачи, эпики и подзадачи в оперативной памяти.
-// Поддерживает создание, обновление, удаление и получение задач всех типов,
-// а также отслеживает историю просмотров через HistoryManager.
+// InMemoryTaskManager хранит задачи, эпики и подзадачи в памяти
+// + ведёт историю просмотров через HistoryManager.
public class InMemoryTaskManager implements TaskManager {
- private final Map tasks = new HashMap<>();
- private final Map epics = new HashMap<>();
+
+ /* ---------- хранилища ---------- */
+ private final Map tasks = new HashMap<>();
+ private final Map epics = new HashMap<>();
private final Map subtasks = new HashMap<>();
+
+ /* ---------- менеджер истории ---------- */
private final HistoryManager historyManager = Managers.getDefaultHistory();
- private int nextId = 1;
- private int generateId() {
- return nextId++;
- }
+ private int nextId = 1;
+ private int generateId() { return nextId++; }
+ /* ---------- создание ---------- */
@Override
public int addNewTask(Task task) {
task.setId(generateId());
@@ -35,9 +36,8 @@ public int addNewEpic(Epic epic) {
@Override
public int addNewSubtask(Subtask subtask) {
Epic epic = epics.get(subtask.getEpicId());
- if (epic == null) {
- throw new IllegalArgumentException("Эпик не найден");
- }
+ if (epic == null) throw new IllegalArgumentException("Эпик не найден");
+
int id = generateId();
subtask.setId(id);
subtasks.put(id, subtask);
@@ -45,39 +45,28 @@ public int addNewSubtask(Subtask subtask) {
return id;
}
- @Override
- public void updateTask(Task task) {
- if (tasks.containsKey(task.getId())) {
- tasks.put(task.getId(), task);
- }
- }
-
- @Override
- public void updateEpic(Epic epic) {
- if (epics.containsKey(epic.getId())) {
- epics.put(epic.getId(), epic);
- }
- }
-
- @Override
- public void updateSubtask(Subtask subtask) {
- if (subtasks.containsKey(subtask.getId())) {
- subtasks.put(subtask.getId(), subtask);
- }
- }
+ /* ---------- обновление ---------- */
+ @Override public void updateTask(Task task) { if (tasks.containsKey(task.getId())) tasks.put(task.getId(), task); }
+ @Override public void updateEpic(Epic epic) { if (epics.containsKey(epic.getId())) epics.put(epic.getId(), epic); }
+ @Override public void updateSubtask(Subtask s) { if (subtasks.containsKey(s.getId())) subtasks.put(s.getId(), s); }
+ /* ---------- удаление ---------- */
@Override
public void removeTask(int id) {
tasks.remove(id);
+ historyManager.remove(id); // добавил удаляем из истории
}
@Override
public void removeEpic(int id) {
Epic epic = epics.remove(id);
if (epic != null) {
+ // удаляем все подзадачи эпика
for (int subId : epic.getSubtaskIds()) {
subtasks.remove(subId);
+ historyManager.remove(subId); // добавил подзадача из истории
}
+ historyManager.remove(id); // добавил сам эпик из истории
}
}
@@ -86,47 +75,23 @@ public void removeSubtask(int id) {
Subtask subtask = subtasks.remove(id);
if (subtask != null) {
Epic epic = epics.get(subtask.getEpicId());
- if (epic != null) {
- epic.getSubtaskIds().remove((Integer) id);
- }
+ if (epic != null) epic.getSubtaskIds().remove((Integer) id);
}
+ historyManager.remove(id); // добавил subtask из истории
}
+ /* ---------- получение + запись в историю ---------- */
@Override
- public Task getTask(int id) {
- Task t = tasks.get(id);
- if (t != null) historyManager.add(t);
- return t;
- }
-
- @Override
- public Epic getEpic(int id) {
- Epic e = epics.get(id);
- if (e != null) historyManager.add(e);
- return e;
- }
-
- @Override
- public Subtask getSubtask(int id) {
- Subtask s = subtasks.get(id);
- if (s != null) historyManager.add(s);
- return s;
- }
-
+ public Task getTask(int id) { Task t = tasks.get(id); if (t != null) historyManager.add(t); return t; }
@Override
- public List getTasks() {
- return new ArrayList<>(tasks.values());
- }
-
+ public Epic getEpic(int id) { Epic e = epics.get(id); if (e != null) historyManager.add(e); return e; }
@Override
- public List getEpics() {
- return new ArrayList<>(epics.values());
- }
+ public Subtask getSubtask(int id) { Subtask s = subtasks.get(id); if (s != null) historyManager.add(s); return s; }
- @Override
- public List getSubtasks() {
- return new ArrayList<>(subtasks.values());
- }
+ /* ---------- списки ---------- */
+ @Override public List getTasks() { return new ArrayList<>(tasks.values()); }
+ @Override public List getEpics() { return new ArrayList<>(epics.values()); }
+ @Override public List getSubtasks() { return new ArrayList<>(subtasks.values()); }
@Override
public List getEpicSubtasks(int epicId) {
@@ -141,6 +106,7 @@ public List getEpicSubtasks(int epicId) {
return result;
}
+ /* ---------- история ---------- */
@Override
public List getHistory() {
return historyManager.getHistory();
diff --git a/src/manager/TaskManager.java b/src/manager/TaskManager.java
index 6f31646..d05321e 100644
--- a/src/manager/TaskManager.java
+++ b/src/manager/TaskManager.java
@@ -18,10 +18,13 @@ public interface TaskManager {
void updateSubtask(Subtask subtask);
//Удаление задач всех типов
+
void removeTask(int id);
void removeEpic(int id);
void removeSubtask(int id);
+ /* ─── получение ─── */
+
Task getTask(int id);
Epic getEpic(int id);
Subtask getSubtask(int id);
@@ -31,5 +34,7 @@ public interface TaskManager {
List getSubtasks();
List getEpicSubtasks(int epicId);
+ /* ─── история ─── */
+
List getHistory();
}
\ No newline at end of file
diff --git a/src/test/java/manager/HistoryManagerTest.java b/src/test/java/manager/HistoryManagerTest.java
new file mode 100644
index 0000000..d185c3f
--- /dev/null
+++ b/src/test/java/manager/HistoryManagerTest.java
@@ -0,0 +1,61 @@
+package test.java.manager;
+
+import manager.HistoryManager;
+import manager.Managers;
+import model.Task;
+import model.Status;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Юнит-тесты самого HistoryManager
+ */
+class HistoryManagerTest {
+
+ private HistoryManager hm;
+
+ private Task t1;
+ private Task t2;
+ private Task t3;
+
+ @BeforeEach
+ void setUp() {
+ hm = Managers.getDefaultHistory(); // или new InMemoryHistoryManager()
+
+ // id задаём вручную, чтобы HistoryManager.remove(id) работал корректно
+ t1 = new Task("T-1", "descr1", Status.NEW); t1.setId(1);
+ t2 = new Task("T-2", "descr2", Status.NEW); t2.setId(2);
+ t3 = new Task("T-3", "descr3", Status.NEW); t3.setId(3);
+ }
+
+ /** add(): без дубликатов, последний просмотр переносится в конец */
+ @Test
+ void add_movesTaskToTail_withoutDuplicates() {
+ hm.add(t1);
+ hm.add(t2);
+ hm.add(t3);
+ hm.add(t2); // повторный просмотр t2
+
+ List history = hm.getHistory();
+ assertEquals(List.of(t1, t3, t2), history,
+ "Повторный просмотр должен перемещать задачу в конец истории без дублирования");
+ }
+
+ /** remove(): удаляет узел из середины за O(1) */
+ @Test
+ void remove_deletesNodeFromAnyPosition() {
+ hm.add(t1);
+ hm.add(t2);
+ hm.add(t3);
+
+ hm.remove(2); // удаляем t2 (из середины списка)
+
+ List history = hm.getHistory();
+ assertEquals(List.of(t1, t3), history,
+ "После удаления задачи из середины истории в списке должны остаться t1 и t3");
+ }
+}
diff --git a/src/test/java/manager/InMemoryTaskManagerTest.java b/src/test/java/manager/InMemoryTaskManagerTest.java
index aebf951..660cd50 100644
--- a/src/test/java/manager/InMemoryTaskManagerTest.java
+++ b/src/test/java/manager/InMemoryTaskManagerTest.java
@@ -1,7 +1,5 @@
-package test.java.manager;
+package manager;
-import manager.Managers;
-import manager.TaskManager;
import model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -10,56 +8,53 @@
import static org.junit.jupiter.api.Assertions.*;
-// Юнит-тесты для {InMemoryTaskManager}.
-// Проверяются добавление задач, история просмотров и корректная обработка ошибок.
-
+/**
+ * Юнит-тесты InMemoryTaskManager + HistoryManager.
+ */
class InMemoryTaskManagerTest {
- private TaskManager manager;
-//Создаёт новый экземпляр менеджера перед каждым тестом.
+
+ private TaskManager tm;
+
@BeforeEach
- void setup() {
- manager = Managers.getDefault();
+ void setUp() {
+ tm = Managers.getDefault(); // InMemoryTaskManager
}
-//Проверяет, что добавленная задача возвращается корректно по ID.
- @Test
- void shouldAddAndReturnTask() {
- Task task = new Task("Test task", "Desc", Status.NEW);
- int id = manager.addNewTask(task);
- Task returned = manager.getTask(id);
- assertNotNull(returned);
- assertEquals(task.getTitle(), returned.getTitle());
- }
-//Проверяет, что история просмотров сохраняет порядок и допускает повторы.
+ /* -------- 1. Дубликаты не сохраняются -------- */
@Test
- void shouldStoreHistoryCorrectly() {
- int id1 = manager.addNewTask(new Task("T1", "", Status.NEW));
- int id2 = manager.addNewTask(new Task("T2", "", Status.NEW));
+ void addDuplicates_keepsOnlyLastView() {
+ int id = tm.addNewTask(new Task("T", "d", Status.NEW));
- manager.getTask(id1);
- manager.getTask(id2);
- manager.getTask(id1);
+ tm.getTask(id);
+ tm.getTask(id);
+ tm.getTask(id);
- List history = manager.getHistory();
- assertEquals(3, history.size());
- assertEquals("T1", history.get(2).getTitle());
+ List history = tm.getHistory();
+ assertEquals(1, history.size(),
+ "В истории должен остаться единственный просмотр");
+ assertEquals(id, history.get(0).getId());
}
-//Проверяет, что при попытке привязать подзадачу к несуществующему эпику будет выброшено исключение.
+
+ /* -------- 2. История может быть > 10 -------- */
@Test
- void shouldThrowIfSubtaskReferencesMissingEpic() {
- Subtask subtask = new Subtask("Ошибка", "Нет эпика", 999); // несуществующий epicId
- assertThrows(IllegalArgumentException.class, () -> manager.addNewSubtask(subtask));
+ void historyCanGrowMoreThanTen() {
+ for (int i = 0; i < 20; i++) {
+ int id = tm.addNewTask(new Task("task-" + i, "", Status.NEW));
+ tm.getTask(id);
+ }
+ assertEquals(20, tm.getHistory().size(),
+ "История должна содержать все 20 просмотров");
}
-//Проверяет, что история просмотров не превышает 10 элементов.
+ /* -------- 3. Удаление чистит историю -------- */
@Test
- void historyShouldNotExceedTenEntries() {
- for (int i = 0; i < 12; i++) {
- int id = manager.addNewTask(new Task("T" + i, "", Status.NEW));
- manager.getTask(id);
- }
+ void deletingTask_removesItFromHistory() {
+ int id = tm.addNewTask(new Task("X", "", Status.NEW));
+ tm.getTask(id);
+
+ tm.removeTask(id);
- List history = manager.getHistory();
- assertEquals(10, history.size(), "История не должна превышать 10 элементов");
+ assertTrue(tm.getHistory().isEmpty(),
+ "После удаления задачи записи о ней в истории быть не должно");
}
}
diff --git a/src/test/java/manager/TaskManagerHistoryIntegrationTest.java b/src/test/java/manager/TaskManagerHistoryIntegrationTest.java
new file mode 100644
index 0000000..1a67eee
--- /dev/null
+++ b/src/test/java/manager/TaskManagerHistoryIntegrationTest.java
@@ -0,0 +1,37 @@
+package test.java.manager;
+
+import manager.Managers;
+import manager.TaskManager;
+import model.Task;
+import model.Status;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Интеграционный тест: TaskManager ↔ HistoryManager
+ */
+class TaskManagerHistoryIntegrationTest {
+
+ private TaskManager tm;
+
+ @BeforeEach
+ void setUp() {
+ tm = Managers.getDefault(); // new InMemoryTaskManager()
+ }
+
+ /** Удаление задачи очищает историю */
+ @Test
+ void deletingTask_removesItFromHistory() {
+ int id = tm.addNewTask(new Task("Task-1", "descr", Status.NEW));
+
+ tm.getTask(id); // помещаем в историю
+ assertEquals(1, tm.getHistory().size(),
+ "После просмотра история должна содержать одну запись");
+
+ tm.removeTask(id); // удаляем задачу
+ assertTrue(tm.getHistory().isEmpty(),
+ "После удаления задачи история должна быть пустой");
+ }
+}